mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-26 23:23:33 +00:00
merge mozilla-inbound to mozilla-central
This commit is contained in:
commit
9e48d03b18
@ -15,9 +15,9 @@
|
||||
#include "Logging.h"
|
||||
#endif
|
||||
|
||||
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
|
||||
#include "nsCURILoader.h"
|
||||
#include "nsDocShellLoadTypes.h"
|
||||
#include "nsDOMEvent.h"
|
||||
#include "nsIChannel.h"
|
||||
#include "nsIDOMDocument.h"
|
||||
#include "nsEventListenerManager.h"
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "nsIAccessibleRelation.h"
|
||||
#include "nsIDocShellTreeItem.h"
|
||||
#include "nsIDocShellTreeOwner.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "nsIDOMDataContainerEvent.h"
|
||||
#include "nsIDOMXULMultSelectCntrlEl.h"
|
||||
@ -39,7 +40,6 @@
|
||||
#include "nsIWebBrowserChrome.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsFocusManager.h"
|
||||
#include "nsDOMEvent.h"
|
||||
|
||||
#ifdef MOZ_XUL
|
||||
#include "nsIXULDocument.h"
|
||||
@ -234,7 +234,7 @@ NS_IMETHODIMP
|
||||
RootAccessible::HandleEvent(nsIDOMEvent* aDOMEvent)
|
||||
{
|
||||
MOZ_ASSERT(aDOMEvent);
|
||||
nsDOMEvent* event = aDOMEvent->InternalDOMEvent();
|
||||
Event* event = aDOMEvent->InternalDOMEvent();
|
||||
nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget());
|
||||
if (!origTargetNode)
|
||||
return NS_OK;
|
||||
@ -266,7 +266,7 @@ void
|
||||
RootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
|
||||
{
|
||||
MOZ_ASSERT(aDOMEvent);
|
||||
nsDOMEvent* event = aDOMEvent->InternalDOMEvent();
|
||||
Event* event = aDOMEvent->InternalDOMEvent();
|
||||
nsCOMPtr<nsINode> origTargetNode = do_QueryInterface(event->GetOriginalTarget());
|
||||
|
||||
nsAutoString eventType;
|
||||
|
@ -309,7 +309,7 @@ this.AccessFu = {
|
||||
case 'Accessibility:Focus':
|
||||
this._focused = JSON.parse(aData);
|
||||
if (this._focused) {
|
||||
this.showCurrent(true);
|
||||
this.autoMove({ forcePresent: true, noOpIfOnScreen: true });
|
||||
}
|
||||
break;
|
||||
case 'Accessibility:MoveByGranularity':
|
||||
@ -353,10 +353,11 @@ this.AccessFu = {
|
||||
// We delay this for half a second so the awesomebar could close,
|
||||
// and we could use the current coordinates for the content item.
|
||||
// XXX TODO figure out how to avoid magic wait here.
|
||||
Utils.win.setTimeout(
|
||||
function () {
|
||||
this.showCurrent(false);
|
||||
}.bind(this), 500);
|
||||
this.autoMove({
|
||||
delay: 500,
|
||||
forcePresent: true,
|
||||
noOpIfOnScreen: true,
|
||||
moveMethod: 'moveFirst' });
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -372,9 +373,9 @@ this.AccessFu = {
|
||||
}
|
||||
},
|
||||
|
||||
showCurrent: function showCurrent(aMove) {
|
||||
autoMove: function autoMove(aOptions) {
|
||||
let mm = Utils.getMessageManager(Utils.CurrentBrowser);
|
||||
mm.sendAsyncMessage('AccessFu:ShowCurrent', { move: aMove });
|
||||
mm.sendAsyncMessage('AccessFu:AutoMove', aOptions);
|
||||
},
|
||||
|
||||
announce: function announce(aAnnouncement) {
|
||||
|
269
accessible/src/jsat/ContentControl.jsm
Normal file
269
accessible/src/jsat/ContentControl.jsm
Normal file
@ -0,0 +1,269 @@
|
||||
/* 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/. */
|
||||
|
||||
let Ci = Components.interfaces;
|
||||
let Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Services',
|
||||
'resource://gre/modules/Services.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Logger',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
|
||||
'resource://gre/modules/accessibility/Constants.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'TraversalRules',
|
||||
'resource://gre/modules/accessibility/TraversalRules.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Presentation',
|
||||
'resource://gre/modules/accessibility/Presentation.jsm');
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['ContentControl'];
|
||||
|
||||
this.ContentControl = function ContentControl(aContentScope) {
|
||||
this._contentScope = Cu.getWeakReference(aContentScope);
|
||||
this._vcCache = new WeakMap();
|
||||
this._childMessageSenders = new WeakMap();
|
||||
};
|
||||
|
||||
this.ContentControl.prototype = {
|
||||
messagesOfInterest: ['AccessFu:MoveCursor',
|
||||
'AccessFu:ClearCursor',
|
||||
'AccessFu:MoveToPoint',
|
||||
'AccessFu:AutoMove'],
|
||||
|
||||
start: function ContentControl_start() {
|
||||
let cs = this._contentScope.get();
|
||||
for (let message of this.messagesOfInterest) {
|
||||
cs.addMessageListener(message, this);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function ContentControl_stop() {
|
||||
let cs = this._contentScope.get();
|
||||
for (let message of this.messagesOfInterest) {
|
||||
cs.removeMessageListener(message, this);
|
||||
}
|
||||
},
|
||||
|
||||
get document() {
|
||||
return this._contentScope.get().content.document;
|
||||
},
|
||||
|
||||
get window() {
|
||||
return this._contentScope.get().content;
|
||||
},
|
||||
|
||||
get vc() {
|
||||
return Utils.getVirtualCursor(this.document);
|
||||
},
|
||||
|
||||
receiveMessage: function ContentControl_receiveMessage(aMessage) {
|
||||
Logger.debug(() => {
|
||||
return ['ContentControl.receiveMessage',
|
||||
this.document.location.toString(),
|
||||
JSON.stringify(aMessage.json)];
|
||||
});
|
||||
|
||||
try {
|
||||
switch (aMessage.name) {
|
||||
case 'AccessFu:MoveCursor':
|
||||
this.handleMove(aMessage);
|
||||
break;
|
||||
case 'AccessFu:ClearCursor':
|
||||
this.handleClear(aMessage);
|
||||
break;
|
||||
case 'AccessFu:MoveToPoint':
|
||||
this.handleMoveToPoint(aMessage);
|
||||
break;
|
||||
case 'AccessFu:AutoMove':
|
||||
this.handleAutoMove(aMessage);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (x) {
|
||||
Logger.logException(
|
||||
x, 'Error handling message: ' + JSON.stringify(aMessage.json));
|
||||
}
|
||||
},
|
||||
|
||||
handleMove: function ContentControl_handleMove(aMessage) {
|
||||
let origin = aMessage.json.origin;
|
||||
let action = aMessage.json.action;
|
||||
let vc = this.vc;
|
||||
|
||||
if (origin != 'child' && this.sendToChild(vc, aMessage)) {
|
||||
// Forwarded succesfully to child cursor.
|
||||
return;
|
||||
}
|
||||
|
||||
let moved = vc[action](TraversalRules[aMessage.json.rule]);
|
||||
|
||||
if (moved) {
|
||||
if (origin === 'child') {
|
||||
// We just stepped out of a child, clear child cursor.
|
||||
Utils.getMessageManager(aMessage.target).sendAsyncMessage(
|
||||
'AccessFu:ClearCursor', {});
|
||||
} else {
|
||||
// We potentially landed on a new child cursor. If so, we want to
|
||||
// either be on the first or last item in the child doc.
|
||||
let childAction = action;
|
||||
if (action === 'moveNext') {
|
||||
childAction = 'moveFirst';
|
||||
} else if (action === 'movePrevious') {
|
||||
childAction = 'moveLast';
|
||||
}
|
||||
|
||||
// Attempt to forward move to a potential child cursor in our
|
||||
// new position.
|
||||
this.sendToChild(vc, aMessage, { action: childAction});
|
||||
}
|
||||
} else if (!this._childMessageSenders.has(aMessage.target)) {
|
||||
// We failed to move, and the message is not from a child, so forward
|
||||
// to parent.
|
||||
this.sendToParent(aMessage);
|
||||
}
|
||||
},
|
||||
|
||||
handleMoveToPoint: function ContentControl_handleMoveToPoint(aMessage) {
|
||||
let [x, y] = [aMessage.json.x, aMessage.json.y];
|
||||
let rule = TraversalRules[aMessage.json.rule];
|
||||
let vc = this.vc;
|
||||
let win = this.window;
|
||||
|
||||
let dpr = win.devicePixelRatio;
|
||||
this.vc.moveToPoint(rule, x * dpr, y * dpr, true);
|
||||
|
||||
let delta = Utils.isContentProcess ?
|
||||
{ x: x - win.mozInnerScreenX, y: y - win.mozInnerScreenY } : {};
|
||||
this.sendToChild(vc, aMessage, delta);
|
||||
},
|
||||
|
||||
handleClear: function ContentControl_handleClear(aMessage) {
|
||||
this.sendToChild(this.vc, aMessage);
|
||||
this.vc.position = null;
|
||||
},
|
||||
|
||||
handleAutoMove: function ContentControl_handleAutoMove(aMessage) {
|
||||
this.autoMove(null, aMessage.json);
|
||||
},
|
||||
|
||||
getChildCursor: function ContentControl_getChildCursor(aAccessible) {
|
||||
let acc = aAccessible || this.vc.position;
|
||||
if (Utils.isAliveAndVisible(acc) && acc.role === Roles.INTERNAL_FRAME) {
|
||||
let domNode = acc.DOMNode;
|
||||
let mm = this._childMessageSenders.get(domNode, null);
|
||||
if (!mm) {
|
||||
mm = Utils.getMessageManager(domNode);
|
||||
mm.addWeakMessageListener('AccessFu:MoveCursor', this);
|
||||
this._childMessageSenders.set(domNode, mm);
|
||||
}
|
||||
|
||||
return mm;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
sendToChild: function ContentControl_sendToChild(aVirtualCursor,
|
||||
aMessage,
|
||||
aReplacer) {
|
||||
let mm = this.getChildCursor(aVirtualCursor.position);
|
||||
if (!mm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
|
||||
newJSON.origin = 'parent';
|
||||
for (let attr in aReplacer) {
|
||||
newJSON[attr] = aReplacer[attr];
|
||||
}
|
||||
|
||||
mm.sendAsyncMessage(aMessage.name, newJSON);
|
||||
return true;
|
||||
},
|
||||
|
||||
sendToParent: function ContentControl_sendToParent(aMessage) {
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
let newJSON = JSON.parse(JSON.stringify(aMessage.json));
|
||||
newJSON.origin = 'child';
|
||||
aMessage.target.sendAsyncMessage(aMessage.name, newJSON);
|
||||
},
|
||||
|
||||
/**
|
||||
* Move cursor and/or present its location.
|
||||
* aOptions could have any of these fields:
|
||||
* - delay: in ms, before actual move is performed. Another autoMove call
|
||||
* would cancel it. Useful if we want to wait for a possible trailing
|
||||
* focus move. Default 0.
|
||||
* - noOpIfOnScreen: if accessible is alive and visible, don't do anything.
|
||||
* - forcePresent: present cursor location, whether we move or don't.
|
||||
* - moveToFocused: if there is a focused accessible move to that. This takes
|
||||
* precedence over given anchor.
|
||||
* - moveMethod: pivot move method to use, default is 'moveNext',
|
||||
*/
|
||||
autoMove: function ContentControl_autoMove(aAnchor, aOptions = {}) {
|
||||
let win = this.window;
|
||||
win.clearTimeout(this._autoMove);
|
||||
|
||||
let moveFunc = () => {
|
||||
let vc = this.vc;
|
||||
let acc = aAnchor;
|
||||
let rule = aOptions.onScreenOnly ?
|
||||
TraversalRules.SimpleOnScreen : TraversalRules.Simple;
|
||||
let forcePresentFunc = () => {
|
||||
if (aOptions.forcePresent) {
|
||||
this._contentScope.get().sendAsyncMessage(
|
||||
'AccessFu:Present', Presentation.pivotChanged(
|
||||
vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
|
||||
vc.startOffset, vc.endOffset));
|
||||
}
|
||||
};
|
||||
|
||||
if (aOptions.noOpIfOnScreen &&
|
||||
Utils.isAliveAndVisible(vc.position, true)) {
|
||||
forcePresentFunc();
|
||||
return;
|
||||
}
|
||||
|
||||
if (aOptions.moveToFocused) {
|
||||
acc = Utils.AccRetrieval.getAccessibleFor(
|
||||
this.document.activeElement) || acc;
|
||||
}
|
||||
|
||||
let moved = false;
|
||||
let moveMethod = aOptions.moveMethod || 'moveNext'; // default is moveNext
|
||||
let moveFirstOrLast = moveMethod in ['moveFirst', 'moveLast'];
|
||||
if (!moveFirstOrLast || acc) {
|
||||
// We either need next/previous or there is an anchor we need to use.
|
||||
moved = vc[moveFirstOrLast ? 'moveNext' : moveMethod](rule, acc, true);
|
||||
}
|
||||
if (moveFirstOrLast && !moved) {
|
||||
// We move to first/last after no anchor move happened or succeeded.
|
||||
moved = vc[moveMethod](rule);
|
||||
}
|
||||
|
||||
let sentToChild = this.sendToChild(vc, {
|
||||
name: 'AccessFu:AutoMove',
|
||||
json: aOptions
|
||||
});
|
||||
|
||||
if (!moved && !sentToChild) {
|
||||
forcePresentFunc();
|
||||
}
|
||||
};
|
||||
|
||||
if (aOptions.delay) {
|
||||
this._autoMove = win.setTimeout(moveFunc, aOptions.delay);
|
||||
} else {
|
||||
moveFunc();
|
||||
}
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
|
||||
Ci.nsIMessageListener
|
||||
])
|
||||
};
|
@ -98,18 +98,11 @@ this.EventManager.prototype = {
|
||||
case 'wheel':
|
||||
{
|
||||
let attempts = 0;
|
||||
let vc = Utils.getVirtualCursor(this.contentScope.content.document);
|
||||
let intervalId = this.contentScope.content.setInterval(() => {
|
||||
if (!Utils.isAliveAndVisible(vc.position, true)) {
|
||||
this.contentScope.content.clearInterval(intervalId);
|
||||
let delta = aEvent.deltaX || aEvent.deltaY;
|
||||
this.contentScope.content.setTimeout(() => {
|
||||
vc[delta > 0 ? 'moveNext' : 'movePrevious'](TraversalRules.SimpleOnScreen);
|
||||
}, 100);
|
||||
} else if (++attempts > 5) {
|
||||
this.contentScope.content.clearInterval(intervalId);
|
||||
}
|
||||
}, 150);
|
||||
let delta = aEvent.deltaX || aEvent.deltaY;
|
||||
this.contentScope.contentControl.autoMove(
|
||||
null,
|
||||
{ moveMethod: delta > 0 ? 'moveNext' : 'movePrevious',
|
||||
onScreenOnly: true, noOpIfOnScreen: true, delay: 500 });
|
||||
break;
|
||||
}
|
||||
case 'scroll':
|
||||
@ -162,11 +155,6 @@ this.EventManager.prototype = {
|
||||
let reason = event.reason;
|
||||
let oldAccessible = event.oldAccessible;
|
||||
|
||||
if (oldAccessible && oldAccessible.role == Roles.INTERNAL_FRAME) {
|
||||
let mm = Utils.getMessageManager(oldAccessible.DOMNode);
|
||||
mm.sendAsyncMessage('AccessFu:ClearCursor', {});
|
||||
}
|
||||
|
||||
if (this.editState.editing) {
|
||||
aEvent.accessibleDocument.takeFocus();
|
||||
}
|
||||
@ -195,8 +183,7 @@ this.EventManager.prototype = {
|
||||
}
|
||||
case Events.SCROLLING_START:
|
||||
{
|
||||
let vc = Utils.getVirtualCursor(this.contentScope.content.document);
|
||||
vc.moveNext(TraversalRules.Simple, aEvent.accessible, true);
|
||||
this.contentScope.contentControl.autoMove(aEvent.accessible);
|
||||
break;
|
||||
}
|
||||
case Events.TEXT_CARET_MOVED:
|
||||
@ -253,18 +240,25 @@ this.EventManager.prototype = {
|
||||
}
|
||||
case Events.HIDE:
|
||||
{
|
||||
let evt = aEvent.QueryInterface(Ci.nsIAccessibleHideEvent);
|
||||
let {liveRegion, isPolite} = this._handleLiveRegion(
|
||||
aEvent.QueryInterface(Ci.nsIAccessibleHideEvent),
|
||||
['removals', 'all']);
|
||||
// Only handle hide if it is a relevant live region.
|
||||
if (!liveRegion) {
|
||||
break;
|
||||
evt, ['removals', 'all']);
|
||||
if (liveRegion) {
|
||||
// Hide for text is handled by the EVENT_TEXT_REMOVED handler.
|
||||
if (aEvent.accessible.role === Roles.TEXT_LEAF) {
|
||||
break;
|
||||
}
|
||||
this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
|
||||
} else {
|
||||
let vc = Utils.getVirtualCursor(this.contentScope.content.document);
|
||||
if (vc.position &&
|
||||
(Utils.getState(vc.position).contains(States.DEFUNCT) ||
|
||||
Utils.isInSubtree(vc.position, aEvent.accessible))) {
|
||||
this.contentScope.contentControl.autoMove(
|
||||
evt.targetPrevSibling || evt.targetParent,
|
||||
{ moveToFocused: true, delay: 500 });
|
||||
}
|
||||
}
|
||||
// Hide for text is handled by the EVENT_TEXT_REMOVED handler.
|
||||
if (aEvent.accessible.role === Roles.TEXT_LEAF) {
|
||||
break;
|
||||
}
|
||||
this._queueLiveEvent(Events.HIDE, liveRegion, isPolite);
|
||||
break;
|
||||
}
|
||||
case Events.TEXT_INSERTED:
|
||||
@ -285,18 +279,14 @@ this.EventManager.prototype = {
|
||||
let acc = aEvent.accessible;
|
||||
let doc = aEvent.accessibleDocument;
|
||||
if (acc.role != Roles.DOCUMENT && doc.role != Roles.CHROME_WINDOW) {
|
||||
this.contentScope.content.clearTimeout(this._autoMove);
|
||||
let vc = Utils.getVirtualCursor(this.contentScope.content.document);
|
||||
vc.moveNext(TraversalRules.Simple, acc, true);
|
||||
}
|
||||
break;
|
||||
this.contentScope.contentControl.autoMove(acc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Events.DOCUMENT_LOAD_COMPLETE:
|
||||
{
|
||||
this._autoMove = this.contentScope.content.setTimeout(() => {
|
||||
Utils.getVirtualCursor(this.contentScope.content.document)
|
||||
.moveNext(TraversalRules.Simple, aEvent.accessible, true);
|
||||
}, 500);
|
||||
this.contentScope.contentControl.autoMove(
|
||||
aEvent.accessible, { delay: 500 });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -247,6 +247,24 @@ this.Utils = {
|
||||
return new Rect(objX.value, objY.value, objW.value, objH.value);
|
||||
},
|
||||
|
||||
isInSubtree: function isInSubtree(aAccessible, aSubTreeRoot) {
|
||||
let acc = aAccessible;
|
||||
while (acc) {
|
||||
if (acc == aSubTreeRoot) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
acc = acc.parent;
|
||||
} catch (x) {
|
||||
Logger.debug('Failed to get parent:', x);
|
||||
acc = null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
inHiddenSubtree: function inHiddenSubtree(aAccessible) {
|
||||
for (let acc=aAccessible; acc; acc=acc.parent) {
|
||||
let hidden = Utils.getAttributes(acc).hidden;
|
||||
@ -565,8 +583,13 @@ PivotContext.prototype = {
|
||||
_getAncestry: function _getAncestry(aAccessible) {
|
||||
let ancestry = [];
|
||||
let parent = aAccessible;
|
||||
while (parent && (parent = parent.parent)) {
|
||||
ancestry.push(parent);
|
||||
try {
|
||||
while (parent && (parent = parent.parent)) {
|
||||
ancestry.push(parent);
|
||||
}
|
||||
} catch (e) {
|
||||
// A defunct accessible will raise an exception geting parent.
|
||||
Logger.debug('Failed to get parent:', x);
|
||||
}
|
||||
return ancestry.reverse();
|
||||
},
|
||||
|
@ -20,118 +20,15 @@ XPCOMUtils.defineLazyModuleGetter(this, 'Utils',
|
||||
'resource://gre/modules/accessibility/Utils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'EventManager',
|
||||
'resource://gre/modules/accessibility/EventManager.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'ContentControl',
|
||||
'resource://gre/modules/accessibility/ContentControl.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'Roles',
|
||||
'resource://gre/modules/accessibility/Constants.jsm');
|
||||
|
||||
Logger.debug('content-script.js');
|
||||
|
||||
let eventManager = null;
|
||||
|
||||
function clearCursor(aMessage) {
|
||||
try {
|
||||
Utils.getVirtualCursor(content.document).position = null;
|
||||
forwardToChild(aMessage);
|
||||
} catch (x) {
|
||||
Logger.logException(x);
|
||||
}
|
||||
}
|
||||
|
||||
function moveCursor(aMessage) {
|
||||
if (Logger.logLevel >= Logger.DEBUG) {
|
||||
Logger.debug(aMessage.name, JSON.stringify(aMessage.json, null, ' '));
|
||||
}
|
||||
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
let origin = aMessage.json.origin;
|
||||
let action = aMessage.json.action;
|
||||
let rule = TraversalRules[aMessage.json.rule];
|
||||
|
||||
function moveCursorInner() {
|
||||
try {
|
||||
if (origin == 'parent' &&
|
||||
!Utils.isAliveAndVisible(vc.position)) {
|
||||
// We have a bad position in this frame, move vc to last or first item.
|
||||
if (action == 'moveNext') {
|
||||
return vc.moveFirst(rule);
|
||||
} else if (action == 'movePrevious') {
|
||||
return vc.moveLast(rule);
|
||||
}
|
||||
}
|
||||
|
||||
return vc[action](rule);
|
||||
} catch (x) {
|
||||
if (action == 'moveNext' || action == 'movePrevious') {
|
||||
// If we are trying to move next/prev put the vc on the focused item.
|
||||
let acc = Utils.AccRetrieval.
|
||||
getAccessibleFor(content.document.activeElement);
|
||||
return vc.moveNext(rule, acc, true);
|
||||
} else {
|
||||
throw x;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (origin != 'child' &&
|
||||
forwardToChild(aMessage, moveCursor, vc.position)) {
|
||||
// We successfully forwarded the move to the child document.
|
||||
return;
|
||||
}
|
||||
|
||||
if (moveCursorInner()) {
|
||||
// If we moved, try forwarding the message to the new position,
|
||||
// it may be a frame with a vc of its own.
|
||||
forwardToChild(aMessage, moveCursor, vc.position);
|
||||
} else {
|
||||
// If we did not move, we probably reached the end or start of the
|
||||
// document, go back to parent content and move us out of the iframe.
|
||||
if (origin == 'parent') {
|
||||
vc.position = null;
|
||||
}
|
||||
forwardToParent(aMessage);
|
||||
}
|
||||
} catch (x) {
|
||||
Logger.logException(x, 'Cursor move failed');
|
||||
}
|
||||
}
|
||||
|
||||
function moveToPoint(aMessage) {
|
||||
if (Logger.logLevel >= Logger.DEBUG) {
|
||||
Logger.debug(aMessage.name, JSON.stringify(aMessage.json, null, ' '));
|
||||
}
|
||||
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
let details = aMessage.json;
|
||||
let rule = TraversalRules[details.rule];
|
||||
|
||||
try {
|
||||
let dpr = content.devicePixelRatio;
|
||||
vc.moveToPoint(rule, details.x * dpr, details.y * dpr, true);
|
||||
forwardToChild(aMessage, moveToPoint, vc.position);
|
||||
} catch (x) {
|
||||
Logger.logException(x, 'Failed move to point');
|
||||
}
|
||||
}
|
||||
|
||||
function showCurrent(aMessage) {
|
||||
Logger.debug(() => {
|
||||
return [aMessage.name, JSON.stringify(aMessage.json, null, ' ')];
|
||||
});
|
||||
|
||||
let vc = Utils.getVirtualCursor(content.document);
|
||||
|
||||
if (!forwardToChild(vc, showCurrent, aMessage)) {
|
||||
if (!vc.position && aMessage.json.move) {
|
||||
vc.moveFirst(TraversalRules.Simple);
|
||||
} else {
|
||||
sendAsyncMessage('AccessFu:Present', Presentation.pivotChanged(
|
||||
vc.position, null, Ci.nsIAccessiblePivot.REASON_NONE,
|
||||
vc.startOffset, vc.endOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
let contentControl = null;
|
||||
|
||||
function forwardToParent(aMessage) {
|
||||
// XXX: This is a silly way to make a deep copy
|
||||
@ -385,22 +282,23 @@ addMessageListener(
|
||||
if (m.json.buildApp)
|
||||
Utils.MozBuildApp = m.json.buildApp;
|
||||
|
||||
addMessageListener('AccessFu:MoveToPoint', moveToPoint);
|
||||
addMessageListener('AccessFu:MoveCursor', moveCursor);
|
||||
addMessageListener('AccessFu:ShowCurrent', showCurrent);
|
||||
addMessageListener('AccessFu:Activate', activateCurrent);
|
||||
addMessageListener('AccessFu:ContextMenu', activateContextMenu);
|
||||
addMessageListener('AccessFu:Scroll', scroll);
|
||||
addMessageListener('AccessFu:AdjustRange', adjustRange);
|
||||
addMessageListener('AccessFu:MoveCaret', moveCaret);
|
||||
addMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
|
||||
addMessageListener('AccessFu:ClearCursor', clearCursor);
|
||||
|
||||
if (!eventManager) {
|
||||
eventManager = new EventManager(this);
|
||||
}
|
||||
eventManager.start();
|
||||
|
||||
if (!contentControl) {
|
||||
contentControl = new ContentControl(this);
|
||||
}
|
||||
contentControl.start();
|
||||
|
||||
sendAsyncMessage('AccessFu:ContentStarted');
|
||||
});
|
||||
|
||||
@ -409,17 +307,14 @@ addMessageListener(
|
||||
function(m) {
|
||||
Logger.debug('AccessFu:Stop');
|
||||
|
||||
removeMessageListener('AccessFu:MoveToPoint', moveToPoint);
|
||||
removeMessageListener('AccessFu:MoveCursor', moveCursor);
|
||||
removeMessageListener('AccessFu:ShowCurrent', showCurrent);
|
||||
removeMessageListener('AccessFu:Activate', activateCurrent);
|
||||
removeMessageListener('AccessFu:ContextMenu', activateContextMenu);
|
||||
removeMessageListener('AccessFu:Scroll', scroll);
|
||||
removeMessageListener('AccessFu:MoveCaret', moveCaret);
|
||||
removeMessageListener('AccessFu:MoveByGranularity', moveByGranularity);
|
||||
removeMessageListener('AccessFu:ClearCursor', clearCursor);
|
||||
|
||||
eventManager.stop();
|
||||
contentControl.stop();
|
||||
});
|
||||
|
||||
sendAsyncMessage('AccessFu:Ready');
|
||||
|
@ -9,6 +9,7 @@ JS_MODULES_PATH = 'modules/accessibility'
|
||||
EXTRA_JS_MODULES += [
|
||||
'AccessFu.jsm',
|
||||
'Constants.jsm',
|
||||
'ContentControl.jsm',
|
||||
'EventManager.jsm',
|
||||
'OutputGenerator.jsm',
|
||||
'Presentation.jsm',
|
||||
|
@ -12,18 +12,56 @@
|
||||
'</body>' +
|
||||
'</html>';
|
||||
|
||||
function showAlert() {
|
||||
document.getElementById('alert').hidden = false;
|
||||
}
|
||||
|
||||
function hideAlert() {
|
||||
document.getElementById('alert').hidden = true;
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
#windows > iframe {
|
||||
#windows {
|
||||
position: relative;
|
||||
width: 320px;
|
||||
height: 480px;
|
||||
}
|
||||
|
||||
#windows > iframe {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#windows > div[role='dialog'] {
|
||||
z-index: 2;
|
||||
background-color: pink;
|
||||
}
|
||||
|
||||
#windows > * {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div>Phone status bar</div>
|
||||
<div id="windows"></div>
|
||||
<button>Home</button>
|
||||
<div id="windows">
|
||||
<div id="appframe"></div>
|
||||
<div role="dialog" id="alert" hidden>
|
||||
<h1>This is an alert!</h1>
|
||||
<p>Do you agree?</p>
|
||||
<button onclick="hideAlert()">Yes</button>
|
||||
<button onclick="hideAlert()">No</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="home">Home</button>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -216,13 +216,17 @@ AccessFuContentTest.prototype = {
|
||||
if (this.currentPair) {
|
||||
if (this.currentPair[0] instanceof Function) {
|
||||
this.currentPair[0](this.mms[0]);
|
||||
} else {
|
||||
} else if (this.currentPair[0]) {
|
||||
this.mms[0].sendAsyncMessage(this.currentPair[0].name,
|
||||
this.currentPair[0].json);
|
||||
this.currentPair[0].json);
|
||||
}
|
||||
|
||||
if (!this.currentPair[1]) {
|
||||
this.pump();
|
||||
}
|
||||
} else if (this.finishedCallback) {
|
||||
for (var mm of this.mms) {
|
||||
mm.sendAsyncMessage('AccessFu:Stop');
|
||||
mm.sendAsyncMessage('AccessFu:Stop');
|
||||
}
|
||||
this.finishedCallback();
|
||||
}
|
||||
|
@ -125,10 +125,10 @@
|
||||
// Move cursor with focus in outside document
|
||||
[simpleMoveNext,
|
||||
{ speak: 'Phone status bar Traversal Rule test document' }],
|
||||
[ focusFunc('button', false), { speak: 'Home button' }],
|
||||
[ focusFunc('button#home', false), { speak: 'Home button' }],
|
||||
|
||||
// Blur button and reset cursor
|
||||
[focusFunc('button', true), null],
|
||||
[focusFunc('button#home', true), null],
|
||||
[clearCursor, null],
|
||||
|
||||
// Set focus on element outside of embedded frame while cursor is in frame
|
||||
@ -136,12 +136,53 @@
|
||||
{ speak: 'Phone status bar Traversal Rule test document' }],
|
||||
[simpleMoveNext,
|
||||
{ speak: 'wow heading level 1 such app' }],
|
||||
[focusFunc('button', false), { speak: 'Home button' }]
|
||||
[focusFunc('button#home', false), { speak: 'Home button' }]
|
||||
|
||||
// Blur button and reset cursor
|
||||
[focusFunc('button#home', true), null],
|
||||
[clearCursor, null],
|
||||
|
||||
// XXX: Set focus on iframe itself.
|
||||
// XXX: Set focus on element in iframe when cursor is outside of it.
|
||||
// XXX: Set focus on element in iframe when cursor is in iframe.
|
||||
]);
|
||||
|
||||
// Open dialog in outer doc, while cursor is also in outer doc
|
||||
[simpleMoveNext,
|
||||
{ speak: 'Phone status bar Traversal Rule test document' }],
|
||||
[doc.defaultView.showAlert,
|
||||
{ speak: 'This is an alert! heading level 1 dialog' }],
|
||||
|
||||
[function () { doc.defaultView.hideAlert() },
|
||||
{ speak: 'wow heading level 1 such app' }],
|
||||
|
||||
[clearCursor, null],
|
||||
|
||||
// Open dialog in outer doc, while cursor is in inner frame
|
||||
[simpleMoveNext,
|
||||
{ speak: 'Phone status bar Traversal Rule test document' }],
|
||||
[simpleMoveNext,
|
||||
{ speak: 'wow heading level 1 such app' }],
|
||||
[doc.defaultView.showAlert,
|
||||
{ speak: 'This is an alert! heading level 1 dialog' }],
|
||||
|
||||
// XXX: Place cursor back where it was.
|
||||
[doc.defaultView.hideAlert,
|
||||
{ speak: 'many option not checked check button such app' }],
|
||||
|
||||
[clearCursor, null],
|
||||
|
||||
// Open dialog, then focus on something when closing
|
||||
[simpleMoveNext,
|
||||
{ speak: 'Phone status bar Traversal Rule test document' }],
|
||||
[doc.defaultView.showAlert,
|
||||
{ speak: 'This is an alert! heading level 1 dialog' }],
|
||||
|
||||
[function () {
|
||||
doc.defaultView.hideAlert();
|
||||
doc.querySelector('button#home').focus();
|
||||
},
|
||||
{ speak: 'Home button Traversal Rule test document' }]
|
||||
]);
|
||||
|
||||
contentTest.start(function () {
|
||||
closeBrowserWindow();
|
||||
@ -150,7 +191,7 @@
|
||||
|
||||
});
|
||||
iframe.src = 'data:text/html;charset=utf-8,' + doc.defaultView.frameContents;
|
||||
doc.querySelector('#windows').appendChild(iframe);
|
||||
doc.getElementById('appframe').appendChild(iframe);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
@ -28,8 +28,6 @@ LIBS += \
|
||||
endif
|
||||
endif
|
||||
|
||||
STL_FLAGS=
|
||||
|
||||
LIBS += $(JEMALLOC_LIBS)
|
||||
|
||||
LIBS += \
|
||||
|
@ -41,3 +41,5 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
|
||||
]
|
||||
|
||||
LDFLAGS += ['-Wl,--export-dynamic']
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
@ -26,8 +26,6 @@ endif #} LIBXUL_SDK
|
||||
|
||||
# Build a binary bootstrapping with XRE_main
|
||||
|
||||
STL_FLAGS=
|
||||
|
||||
LIBS += \
|
||||
$(XPCOM_STANDALONE_GLUE_LDOPTS) \
|
||||
$(NULL)
|
||||
@ -55,7 +53,6 @@ NSDISTMODE = copy
|
||||
include $(topsrcdir)/config/config.mk
|
||||
|
||||
ifeq ($(OS_ARCH),WINNT)
|
||||
RCINCLUDE = splash.rc
|
||||
# Rebuild firefox.exe if the manifest changes - it's included by splash.rc.
|
||||
# (this dependency should really be just for firefox.exe, not other targets)
|
||||
# Note the manifest file exists in the tree, so we use the explicit filename
|
||||
|
@ -39,6 +39,7 @@ if CONFIG['_MSC_VER']:
|
||||
WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
|
||||
|
||||
if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
RCINCLUDE = 'splash.rc'
|
||||
DEFINES['MOZ_PHOENIX'] = True
|
||||
|
||||
# Control the default heap size.
|
||||
@ -51,3 +52,5 @@ if CONFIG['OS_ARCH'] == 'WINNT':
|
||||
# Set it to 256k. See bug 127069.
|
||||
if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
|
||||
LDFLAGS += ['/HEAP:0x40000']
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
@ -291,8 +291,8 @@ PlacesController.prototype = {
|
||||
* @param aIsMoveCommand
|
||||
* True if the command for which this method is called only moves the
|
||||
* selected items to another container, false otherwise.
|
||||
* @returns true if all nodes in the selection can be removed,
|
||||
* false otherwise.
|
||||
* @return true if all nodes in the selection can be removed,
|
||||
* false otherwise.
|
||||
*/
|
||||
_hasRemovableSelection: function PC__hasRemovableSelection(aIsMoveCommand) {
|
||||
var ranges = this._view.removableSelectionRanges;
|
||||
@ -356,9 +356,9 @@ PlacesController.prototype = {
|
||||
* Looks at the data on the clipboard to see if it is paste-able.
|
||||
* Paste-able data is:
|
||||
* - in a format that the view can receive
|
||||
* @returns true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor,
|
||||
- clipboard data is of type TEXT_UNICODE and
|
||||
is a valid URI.
|
||||
* @return true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor,
|
||||
* - clipboard data is of type TEXT_UNICODE and
|
||||
* is a valid URI.
|
||||
*/
|
||||
_isClipboardDataPasteable: function PC__isClipboardDataPasteable() {
|
||||
// if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
|
||||
@ -413,10 +413,10 @@ PlacesController.prototype = {
|
||||
* "separator" node is a separator line
|
||||
* "host" node is a host
|
||||
*
|
||||
* @returns an array of objects corresponding the selected nodes. Each
|
||||
* object has each of the properties above set if its corresponding
|
||||
* node matches the rule. In addition, the annotations names for each
|
||||
* node are set on its corresponding object as properties.
|
||||
* @return an array of objects corresponding the selected nodes. Each
|
||||
* object has each of the properties above set if its corresponding
|
||||
* node matches the rule. In addition, the annotations names for each
|
||||
* node are set on its corresponding object as properties.
|
||||
* Notes:
|
||||
* 1) This can be slow, so don't call it anywhere performance critical!
|
||||
* 2) A single-object array corresponding the root node is returned if
|
||||
@ -503,8 +503,8 @@ PlacesController.prototype = {
|
||||
* the context menu item
|
||||
* @param aMetaData
|
||||
* meta data about the selection
|
||||
* @returns true if the conditions (see buildContextMenu) are satisfied
|
||||
* and the item can be displayed, false otherwise.
|
||||
* @return true if the conditions (see buildContextMenu) are satisfied
|
||||
* and the item can be displayed, false otherwise.
|
||||
*/
|
||||
_shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
|
||||
var selectiontype = aMenuItem.getAttribute("selectiontype");
|
||||
@ -782,7 +782,7 @@ PlacesController.prototype = {
|
||||
* Node to check for containment.
|
||||
* @param pastFolders
|
||||
* List of folders the calling function has already traversed
|
||||
* @returns true if the node should be skipped, false otherwise.
|
||||
* @return true if the node should be skipped, false otherwise.
|
||||
*/
|
||||
_shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
|
||||
/**
|
||||
@ -791,7 +791,7 @@ PlacesController.prototype = {
|
||||
* The node to check for containment for
|
||||
* @param parent
|
||||
* The parent container to check for containment in
|
||||
* @returns true if node is a member of parent's children, false otherwise.
|
||||
* @return true if node is a member of parent's children, false otherwise.
|
||||
*/
|
||||
function isContainedBy(node, parent) {
|
||||
var cursor = node.parent;
|
||||
@ -1008,7 +1008,6 @@ PlacesController.prototype = {
|
||||
*/
|
||||
setDataTransfer: function PC_setDataTransfer(aEvent) {
|
||||
let dt = aEvent.dataTransfer;
|
||||
let doCopy = ["copyLink", "copy", "link"].indexOf(dt.effectAllowed) != -1;
|
||||
|
||||
let result = this._view.result;
|
||||
let didSuppressNotifications = result.suppressNotifications;
|
||||
@ -1016,7 +1015,7 @@ PlacesController.prototype = {
|
||||
result.suppressNotifications = true;
|
||||
|
||||
function addData(type, index, overrideURI) {
|
||||
let wrapNode = PlacesUtils.wrapNode(node, type, overrideURI, doCopy);
|
||||
let wrapNode = PlacesUtils.wrapNode(node, type, overrideURI);
|
||||
dt.mozSetDataAt(type, wrapNode, index);
|
||||
}
|
||||
|
||||
@ -1116,11 +1115,10 @@ PlacesController.prototype = {
|
||||
|
||||
let livemarkInfo = this.getCachedLivemarkInfo(node);
|
||||
let overrideURI = livemarkInfo ? livemarkInfo.feedURI.spec : null;
|
||||
let resolveShortcuts = !PlacesControllerDragHelper.canMoveNode(node);
|
||||
|
||||
contents.forEach(function (content) {
|
||||
content.entries.push(
|
||||
PlacesUtils.wrapNode(node, content.type, overrideURI, resolveShortcuts)
|
||||
PlacesUtils.wrapNode(node, content.type, overrideURI)
|
||||
);
|
||||
});
|
||||
}, this);
|
||||
@ -1262,6 +1260,13 @@ PlacesController.prototype = {
|
||||
if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
|
||||
insertionIndex = ip.index + i;
|
||||
|
||||
// If this is not a copy, check for safety that we can move the source,
|
||||
// otherwise report an error and fallback to a copy.
|
||||
if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) {
|
||||
Components.utils.reportError("Tried to move an unmovable Places node, " +
|
||||
"reverting to a copy operation.");
|
||||
action = "copy";
|
||||
}
|
||||
transactions.push(
|
||||
PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
|
||||
insertionIndex, action == "copy")
|
||||
@ -1304,7 +1309,7 @@ PlacesController.prototype = {
|
||||
* @param aNode
|
||||
* a places result node.
|
||||
* @return true if there's a cached mozILivemarkInfo object for
|
||||
* aNode, false otherwise.
|
||||
* aNode, false otherwise.
|
||||
*/
|
||||
hasCachedLivemarkInfo: function PC_hasCachedLivemarkInfo(aNode)
|
||||
this._cachedLivemarkInfoObjects.has(aNode),
|
||||
@ -1338,8 +1343,8 @@ let PlacesControllerDragHelper = {
|
||||
* mouse is dragging over one of its submenus
|
||||
* @param node
|
||||
* The container node
|
||||
* @returns true if the user is dragging over a node within the hierarchy of
|
||||
* the container, false otherwise.
|
||||
* @return true if the user is dragging over a node within the hierarchy of
|
||||
* the container, false otherwise.
|
||||
*/
|
||||
draggingOverChildNode: function PCDH_draggingOverChildNode(node) {
|
||||
let currentNode = this.currentDropTarget;
|
||||
@ -1352,7 +1357,7 @@ let PlacesControllerDragHelper = {
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns The current active drag session. Returns null if there is none.
|
||||
* @return The current active drag session. Returns null if there is none.
|
||||
*/
|
||||
getSession: function PCDH__getSession() {
|
||||
return this.dragService.getCurrentSession();
|
||||
@ -1438,13 +1443,28 @@ let PlacesControllerDragHelper = {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if an unwrapped node can be moved.
|
||||
*
|
||||
* @param aUnwrappedNode
|
||||
* A node unwrapped by PlacesUtils.unwrapNodes().
|
||||
* @return True if the node can be moved, false otherwise.
|
||||
*/
|
||||
canMoveUnwrappedNode: function (aUnwrappedNode) {
|
||||
return aUnwrappedNode.id > 0 &&
|
||||
!PlacesUtils.isRootItem(aUnwrappedNode.id) &&
|
||||
aUnwrappedNode.parent != PlacesUtils.placesRootId &&
|
||||
aUnwrappedNode.parent != PlacesUtils.tagsFolderId &&
|
||||
aUnwrappedNode.grandParentId != PlacesUtils.tagsFolderId &&
|
||||
!aUnwrappedNode.parentReadOnly;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if a node can be moved.
|
||||
*
|
||||
* @param aNode
|
||||
* A nsINavHistoryResultNode node.
|
||||
* @returns True if the node can be moved, false otherwise.
|
||||
* @return True if the node can be moved, false otherwise.
|
||||
*/
|
||||
canMoveNode:
|
||||
function PCDH_canMoveNode(aNode) {
|
||||
@ -1478,7 +1498,7 @@ let PlacesControllerDragHelper = {
|
||||
* A bookmark folder id.
|
||||
* @param [optional] aParentId
|
||||
* The parent id of the folder.
|
||||
* @returns True if the container can be moved to the target.
|
||||
* @return True if the container can be moved to the target.
|
||||
*/
|
||||
canMoveContainer:
|
||||
function PCDH_canMoveContainer(aId, aParentId) {
|
||||
@ -1555,6 +1575,13 @@ let PlacesControllerDragHelper = {
|
||||
transactions.push(tagTxn);
|
||||
}
|
||||
else {
|
||||
// If this is not a copy, check for safety that we can move the source,
|
||||
// otherwise report an error and fallback to a copy.
|
||||
if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
|
||||
Components.utils.reportError("Tried to move an unmovable Places node, " +
|
||||
"reverting to a copy operation.");
|
||||
doCopy = true;
|
||||
}
|
||||
transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
|
||||
flavor, insertionPoint.itemId,
|
||||
index, doCopy));
|
||||
|
@ -857,6 +857,8 @@ var gEditItemOverlay = {
|
||||
var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
this._folderTree.focus();
|
||||
this._folderTree.selectItems([ip.itemId]);
|
||||
PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true;
|
||||
this._folderTree.selectItems([this._lastNewItem]);
|
||||
this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
|
||||
this._folderTree.columns.getFirstColumn());
|
||||
|
@ -62,7 +62,7 @@ var PlacesOrganizer = {
|
||||
for (let container of hierarchy) {
|
||||
switch (typeof container) {
|
||||
case "number":
|
||||
this._places.selectItems([container]);
|
||||
this._places.selectItems([container], false);
|
||||
break;
|
||||
case "string":
|
||||
if (container.substr(0, 6) == "place:")
|
||||
@ -327,7 +327,7 @@ var PlacesOrganizer = {
|
||||
|
||||
openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) {
|
||||
if (aContainer.itemId != -1)
|
||||
this._places.selectItems([aContainer.itemId]);
|
||||
this._places.selectItems([aContainer.itemId], false);
|
||||
else if (PlacesUtils.nodeIsQuery(aContainer))
|
||||
this._places.selectPlaceURI(aContainer.uri);
|
||||
},
|
||||
|
@ -45,7 +45,7 @@
|
||||
<property name="view">
|
||||
<getter><![CDATA[
|
||||
try {
|
||||
return this.treeBoxObject.view.wrappedJSObject;
|
||||
return this.treeBoxObject.view.wrappedJSObject || null;
|
||||
}
|
||||
catch(e) {
|
||||
return null;
|
||||
@ -312,9 +312,9 @@
|
||||
for (let i = 0; i < rc; ++i) {
|
||||
let min = { }, max = { };
|
||||
selection.getRangeAt(i, min, max);
|
||||
|
||||
for (let j = min.value; j <= max.value; ++j)
|
||||
for (let j = min.value; j <= max.value; ++j) {
|
||||
nodes.push(resultview.nodeForTreeIndex(j));
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
]]></getter>
|
||||
@ -453,8 +453,6 @@
|
||||
//
|
||||
// If the sole selection is the bookmarks toolbar folder, we insert
|
||||
// into it even if it is not opened
|
||||
var itemId =
|
||||
PlacesUtils.getConcreteItemId(resultView.nodeForTreeIndex(max.value));
|
||||
if (selection.count == 1 && resultView.isContainer(max.value) &&
|
||||
!this.flatList)
|
||||
orientation = Ci.nsITreeView.DROP_ON;
|
||||
@ -546,13 +544,16 @@
|
||||
</method>
|
||||
|
||||
<!-- This method will select the first node in the tree that matches
|
||||
each given item id. It will open any parent nodes that it needs
|
||||
each given item id. It will open any folder nodes that it needs
|
||||
to in order to show the selected items.
|
||||
-->
|
||||
<method name="selectItems">
|
||||
<parameter name="aIDs"/>
|
||||
<parameter name="aOpenContainers"/>
|
||||
<body><![CDATA[
|
||||
// Never open containers in flat lists.
|
||||
if (this.flatList)
|
||||
aOpenContainers = false;
|
||||
// By default, we do search and select within containers which were
|
||||
// closed (note that containers in which nodes were not found are
|
||||
// closed).
|
||||
@ -601,8 +602,13 @@
|
||||
nodesURIChecked.indexOf(node.uri) != -1)
|
||||
return foundOne;
|
||||
|
||||
// Don't try to open a query or a shurtcut, since it may return
|
||||
// any duplicate data and be infinitely nested. Though, if it has
|
||||
// been explicitly opened by the caller, search into it.
|
||||
let shouldOpen = aOpenContainers &&
|
||||
node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
|
||||
PlacesUtils.asContainer(node);
|
||||
if (!aOpenContainers && !node.containerOpen)
|
||||
if (!node.containerOpen && !shouldOpen)
|
||||
return foundOne;
|
||||
|
||||
nodesURIChecked.push(node.uri);
|
||||
|
@ -452,7 +452,7 @@ PlacesTreeView.prototype = {
|
||||
for (let i = 0; i < aNodesInfo.length; i++) {
|
||||
let nodeInfo = aNodesInfo[i];
|
||||
let row = this._getNewRowForRemovedNode(aUpdatedContainer,
|
||||
aNodesInfo[i].node);
|
||||
nodeInfo.node);
|
||||
// Select the found node, if any.
|
||||
if (row != -1) {
|
||||
selection.rangedSelect(row, row, true);
|
||||
@ -465,9 +465,11 @@ PlacesTreeView.prototype = {
|
||||
// select the node at its old row, if any.
|
||||
if (aNodesInfo.length == 1 && selection.count == 0) {
|
||||
let row = Math.min(aNodesInfo[0].oldRow, this._rows.length - 1);
|
||||
selection.rangedSelect(row, row, true);
|
||||
if (aNodesInfo[0].wasVisible && scrollToRow == -1)
|
||||
scrollToRow = aNodesInfo[0].oldRow;
|
||||
if (row != -1) {
|
||||
selection.rangedSelect(row, row, true);
|
||||
if (aNodesInfo[0].wasVisible && scrollToRow == -1)
|
||||
scrollToRow = aNodesInfo[0].oldRow;
|
||||
}
|
||||
}
|
||||
|
||||
if (scrollToRow != -1)
|
||||
@ -705,7 +707,8 @@ PlacesTreeView.prototype = {
|
||||
|
||||
// Restore selection.
|
||||
let rowToSelect = Math.min(oldRow, this._rows.length - 1);
|
||||
this.selection.rangedSelect(rowToSelect, rowToSelect, true);
|
||||
if (rowToSelect != -1)
|
||||
this.selection.rangedSelect(rowToSelect, rowToSelect, true);
|
||||
},
|
||||
|
||||
nodeMoved:
|
||||
|
@ -45,4 +45,5 @@ skip-if = true
|
||||
[browser_416459_cut.js]
|
||||
[browser_library_downloads.js]
|
||||
[browser_library_left_pane_select_hierarchy.js]
|
||||
[browser_435851_copy_query.js]
|
||||
[browser_toolbarbutton_menu_context.js]
|
||||
|
@ -0,0 +1,59 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
/* test that copying a non movable query or folder shortcut makes a new query with the same url, not a deep copy */
|
||||
|
||||
const SHORTCUT_URL = "place:folder=2";
|
||||
const QUERY_URL = "place:sort=8&maxResults=10";
|
||||
|
||||
add_task(function copy_toolbar_shortcut() {
|
||||
let library = yield promiseLibrary();
|
||||
|
||||
registerCleanupFunction(function () {
|
||||
library.close();
|
||||
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
|
||||
});
|
||||
|
||||
library.PlacesOrganizer.selectLeftPaneQuery("BookmarksToolbar");
|
||||
|
||||
yield promiseClipboard(function () { library.PlacesOrganizer._places.controller.copy(); },
|
||||
PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
|
||||
library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
|
||||
library.ContentTree.view.controller.paste();
|
||||
|
||||
let toolbarCopyNode = library.ContentTree.view.view.nodeForTreeIndex(0);
|
||||
is(toolbarCopyNode.type,
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
|
||||
"copy is still a folder shortcut");
|
||||
|
||||
PlacesUtils.bookmarks.removeItem(toolbarCopyNode.itemId);
|
||||
library.PlacesOrganizer.selectLeftPaneQuery("BookmarksToolbar");
|
||||
is(library.PlacesOrganizer._places.selectedNode.type,
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
|
||||
"original is still a folder shortcut");
|
||||
});
|
||||
|
||||
add_task(function copy_history_query() {
|
||||
let library = yield promiseLibrary();
|
||||
|
||||
library.PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
|
||||
yield promiseClipboard(function () { library.PlacesOrganizer._places.controller.copy(); },
|
||||
PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
|
||||
library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
|
||||
library.ContentTree.view.controller.paste();
|
||||
|
||||
let historyCopyNode = library.ContentTree.view.view.nodeForTreeIndex(0);
|
||||
is(historyCopyNode.type,
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY,
|
||||
"copy is still a query");
|
||||
|
||||
PlacesUtils.bookmarks.removeItem(historyCopyNode.itemId);
|
||||
library.PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
is(library.PlacesOrganizer._places.selectedNode.type,
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY,
|
||||
"original is still a query");
|
||||
});
|
@ -173,6 +173,8 @@ gTests.push({
|
||||
},
|
||||
|
||||
selectNode: function(tree) {
|
||||
tree.selectItems([PlacesUtils.unfiledBookmarksFolderId]);
|
||||
PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
|
||||
tree.selectItems([this._itemId]);
|
||||
is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected");
|
||||
},
|
||||
@ -329,6 +331,8 @@ gTests.push({
|
||||
},
|
||||
|
||||
selectNode: function(tree) {
|
||||
tree.selectItems([PlacesUtils.unfiledBookmarksFolderId]);
|
||||
PlacesUtils.asContainer(tree.selectedNode).containerOpen = true;
|
||||
tree.selectItems([this._itemId]);
|
||||
is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected");
|
||||
},
|
||||
|
@ -29,6 +29,46 @@ function openLibrary(callback, aLeftPaneRoot) {
|
||||
return library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a handle to a Library window.
|
||||
* If one is opens returns itm otherwise it opens a new one.
|
||||
*
|
||||
* @param aLeftPaneRoot
|
||||
* Hierarchy to open and select in the left pane.
|
||||
*/
|
||||
function promiseLibrary(aLeftPaneRoot) {
|
||||
let deferred = Promise.defer();
|
||||
let library = Services.wm.getMostRecentWindow("Places:Organizer");
|
||||
if (library) {
|
||||
if (aLeftPaneRoot)
|
||||
library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
|
||||
deferred.resolve(library);
|
||||
}
|
||||
else {
|
||||
openLibrary(aLibrary => deferred.resolve(aLibrary), aLeftPaneRoot);
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a clipboard operation to complete, looking for the expected type.
|
||||
*
|
||||
* @see waitForClipboard
|
||||
*
|
||||
* @param aPopulateClipboardFn
|
||||
* Function to populate the clipboard.
|
||||
* @param aFlavor
|
||||
* Data flavor to expect.
|
||||
*/
|
||||
function promiseClipboard(aPopulateClipboardFn, aFlavor) {
|
||||
let deferred = Promise.defer();
|
||||
waitForClipboard(function (aData) !!aData,
|
||||
aPopulateClipboardFn,
|
||||
function () { deferred.resolve(); },
|
||||
aFlavor);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for completion of a clear history operation, before
|
||||
* proceeding with aCallback.
|
||||
|
@ -9,4 +9,5 @@ support-files = head.js
|
||||
[test_bug549491.xul]
|
||||
[test_bug631374_tags_selector_scroll.xul]
|
||||
[test_editBookmarkOverlay_tags_liveUpdate.xul]
|
||||
[test_selectItems_on_nested_tree.xul]
|
||||
[test_treeview_date.xul]
|
||||
|
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/licenses/publicdomain/
|
||||
-->
|
||||
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
|
||||
<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
title="549192: History view not updated after deleting entry"
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
|
||||
<script type="application/javascript" src="head.js" />
|
||||
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" />
|
||||
|
||||
<tree id="tree"
|
||||
type="places"
|
||||
flex="1">
|
||||
<treecols>
|
||||
<treecol label="Title" id="title" anonid="title" primary="true" ordinal="1" flex="1"/>
|
||||
</treecols>
|
||||
<treechildren flex="1"/>
|
||||
</tree>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
/**
|
||||
* Ensure that selectItems doesn't recurse infinitely in nested trees.
|
||||
*/
|
||||
|
||||
function runTest() {
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
NetUtil.newURI("place:folder=UNFILED_BOOKMARKS"),
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"shortcut");
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
NetUtil.newURI("place:folder=UNFILED_BOOKMARKS&maxResults=10"),
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"query");
|
||||
let folderId = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
|
||||
"folder",
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
let itemId = PlacesUtils.bookmarks.insertBookmark(folderId,
|
||||
NetUtil.newURI("http://www.mozilla.org/"),
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"bookmark");
|
||||
// Setup the places tree contents.
|
||||
var tree = document.getElementById("tree");
|
||||
tree.place = "place:folder=UNFILED_BOOKMARKS";
|
||||
|
||||
// Select the last bookmark.
|
||||
tree.selectItems([itemId]);
|
||||
is (tree.selectedNode.itemId, itemId, "The right node was selected");
|
||||
}
|
||||
|
||||
]]></script>
|
||||
</window>
|
@ -5,7 +5,6 @@
|
||||
include $(topsrcdir)/config/config.mk
|
||||
|
||||
DIST_PROGRAM = CommandExecuteHandler$(BIN_SUFFIX)
|
||||
RCINCLUDE = CommandExecuteHandler.rc
|
||||
|
||||
# Don't link against mozglue.dll
|
||||
MOZ_GLUE_LDFLAGS =
|
||||
|
@ -18,3 +18,5 @@ for var in ('UNICODE', '_UNICODE', 'NS_NO_XPCOM'):
|
||||
DEFINES[var] = True
|
||||
|
||||
NO_PGO = True
|
||||
|
||||
RCINCLUDE = 'CommandExecuteHandler.rc'
|
||||
|
@ -3,7 +3,6 @@
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
MODULES = stlport
|
||||
STL_FLAGS =
|
||||
|
||||
# Force to build a static library, instead of a fake library, without
|
||||
# installing it in dist/lib.
|
||||
|
@ -52,3 +52,4 @@ LOCAL_INCLUDES += [
|
||||
'stlport',
|
||||
]
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
@ -2,7 +2,6 @@
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
STL_FLAGS =
|
||||
NO_EXPAND_LIBS = 1
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
@ -17,3 +17,5 @@ if CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']:
|
||||
FORCE_STATIC_LIB = True
|
||||
|
||||
NO_PGO = True
|
||||
|
||||
DISABLE_STL_WRAPPING = True
|
||||
|
@ -590,7 +590,7 @@ endif
|
||||
endif
|
||||
|
||||
COMPILE_CFLAGS = $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_CPPFLAGS) $(OS_COMPILE_CFLAGS) $(CFLAGS) $(MOZBUILD_CFLAGS) $(EXTRA_COMPILE_FLAGS)
|
||||
COMPILE_CXXFLAGS = $(STL_FLAGS) $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_CPPFLAGS) $(OS_COMPILE_CXXFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(EXTRA_COMPILE_FLAGS)
|
||||
COMPILE_CXXFLAGS = $(if $(DISABLE_STL_WRAPPING),,$(STL_FLAGS)) $(VISIBILITY_FLAGS) $(DEFINES) $(INCLUDES) $(DSO_CFLAGS) $(DSO_PIC_CFLAGS) $(RTL_FLAGS) $(OS_CPPFLAGS) $(OS_COMPILE_CXXFLAGS) $(CXXFLAGS) $(MOZBUILD_CXXFLAGS) $(EXTRA_COMPILE_FLAGS)
|
||||
COMPILE_CMFLAGS = $(OS_COMPILE_CMFLAGS) $(MOZBUILD_CMFLAGS) $(EXTRA_COMPILE_FLAGS)
|
||||
COMPILE_CMMFLAGS = $(OS_COMPILE_CMMFLAGS) $(MOZBUILD_CMMFLAGS) $(EXTRA_COMPILE_FLAGS)
|
||||
ASFLAGS += $(EXTRA_ASSEMBLER_FLAGS)
|
||||
@ -900,7 +900,7 @@ DEFINES += -DUNICODE -D_UNICODE
|
||||
LOCAL_INCLUDES += -I'$(MOZ_DIRECTX_SDK_PATH)/include'
|
||||
endif
|
||||
|
||||
STL_FLAGS=
|
||||
DISABLE_STL_WRAPPING := 1
|
||||
# Skip most Mozilla-specific include locations.
|
||||
INCLUDES = -I. $(LOCAL_INCLUDES) -I$(DEPTH)/dist/include
|
||||
endif
|
||||
|
@ -1088,7 +1088,8 @@ protected:
|
||||
* Add/remove this element to the documents id cache
|
||||
*/
|
||||
void AddToIdTable(nsIAtom* aId);
|
||||
void RemoveFromIdTable();
|
||||
void RemoveFromIdTable(); // checks HasID() and uses DoGetID()
|
||||
void RemoveFromIdTable(nsIAtom* aId);
|
||||
|
||||
/**
|
||||
* Functions to carry out event default actions for links of all types
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "nsPIDOMWindow.h" // for use in inline functions
|
||||
#include "nsPropertyTable.h" // for member
|
||||
#include "nsTHashtable.h" // for member
|
||||
#include "nsWeakReference.h"
|
||||
#include "mozilla/dom/DocumentBinding.h"
|
||||
#include "mozilla/WeakPtr.h"
|
||||
#include "Units.h"
|
||||
@ -79,7 +80,6 @@ class nsWindowSizes;
|
||||
class nsSmallVoidArray;
|
||||
class nsDOMCaretPosition;
|
||||
class nsViewportInfo;
|
||||
class nsDOMEvent;
|
||||
class nsIGlobalObject;
|
||||
class nsCSSSelectorList;
|
||||
|
||||
@ -102,6 +102,7 @@ class DOMImplementation;
|
||||
class DOMStringList;
|
||||
class Element;
|
||||
struct ElementRegistrationOptions;
|
||||
class Event;
|
||||
class EventTarget;
|
||||
class FrameRequestCallback;
|
||||
class HTMLBodyElement;
|
||||
@ -2003,8 +2004,8 @@ public:
|
||||
|
||||
enum ElementCallbackType {
|
||||
eCreated,
|
||||
eEnteredView,
|
||||
eLeftView,
|
||||
eAttached,
|
||||
eDetached,
|
||||
eAttributeChanged
|
||||
};
|
||||
|
||||
@ -2065,8 +2066,8 @@ public:
|
||||
already_AddRefed<nsINode>
|
||||
ImportNode(nsINode& aNode, bool aDeep, mozilla::ErrorResult& rv) const;
|
||||
nsINode* AdoptNode(nsINode& aNode, mozilla::ErrorResult& rv);
|
||||
already_AddRefed<nsDOMEvent> CreateEvent(const nsAString& aEventType,
|
||||
mozilla::ErrorResult& rv) const;
|
||||
already_AddRefed<mozilla::dom::Event>
|
||||
CreateEvent(const nsAString& aEventType, mozilla::ErrorResult& rv) const;
|
||||
already_AddRefed<nsRange> CreateRange(mozilla::ErrorResult& rv);
|
||||
already_AddRefed<mozilla::dom::NodeIterator>
|
||||
CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow,
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include "nsNodeInfoManager.h" // for use in NodePrincipal()
|
||||
#include "nsPropertyTable.h" // for typedefs
|
||||
#include "nsTObserverArray.h" // for member
|
||||
#include "nsWindowMemoryReporter.h" // for NS_DECL_SIZEOF_EXCLUDING_THIS
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/dom/EventTarget.h" // for base class
|
||||
@ -244,13 +243,21 @@ private:
|
||||
// ever passed to Mutated().
|
||||
enum { eMaxMutations = 300 };
|
||||
|
||||
|
||||
|
||||
// sMutationCount is a global mutation counter which is decreased by one at
|
||||
// every mutation. It is capped at 0 to avoid wrapping.
|
||||
// Its value is always between 0 and 300, inclusive.
|
||||
static uint32_t sMutationCount;
|
||||
};
|
||||
|
||||
// This should be used for any nsINode sub-class that has fields of its own
|
||||
// that it needs to measure; any sub-class that doesn't use it will inherit
|
||||
// SizeOfExcludingThis from its super-class. SizeOfIncludingThis() need not be
|
||||
// defined, it is inherited from nsINode.
|
||||
// This macro isn't actually specific to nodes, and bug 956400 will move it into MFBT.
|
||||
#define NS_DECL_SIZEOF_EXCLUDING_THIS \
|
||||
virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
// Categories of node properties
|
||||
// 0 is global.
|
||||
#define DOM_USER_DATA 1
|
||||
|
@ -719,23 +719,29 @@ void
|
||||
Element::RemoveFromIdTable()
|
||||
{
|
||||
if (HasID()) {
|
||||
if (HasFlag(NODE_IS_IN_SHADOW_TREE)) {
|
||||
ShadowRoot* containingShadow = GetContainingShadow();
|
||||
// Check for containingShadow because it may have
|
||||
// been deleted during unlinking.
|
||||
if (containingShadow) {
|
||||
containingShadow->RemoveFromIdTable(this, DoGetID());
|
||||
}
|
||||
} else {
|
||||
nsIDocument* doc = GetCurrentDoc();
|
||||
if (doc) {
|
||||
nsIAtom* id = DoGetID();
|
||||
// id can be null during mutation events evilness. Also, XUL elements
|
||||
// loose their proto attributes during cc-unlink, so this can happen
|
||||
// during cc-unlink too.
|
||||
if (id) {
|
||||
doc->RemoveFromIdTable(this, DoGetID());
|
||||
}
|
||||
RemoveFromIdTable(DoGetID());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Element::RemoveFromIdTable(nsIAtom* aId)
|
||||
{
|
||||
NS_ASSERTION(HasID(), "Node doesn't have an ID?");
|
||||
if (HasFlag(NODE_IS_IN_SHADOW_TREE)) {
|
||||
ShadowRoot* containingShadow = GetContainingShadow();
|
||||
// Check for containingShadow because it may have
|
||||
// been deleted during unlinking.
|
||||
if (containingShadow) {
|
||||
containingShadow->RemoveFromIdTable(this, aId);
|
||||
}
|
||||
} else {
|
||||
nsIDocument* doc = GetCurrentDoc();
|
||||
if (doc && (!IsInAnonymousSubtree() || doc->IsXUL())) {
|
||||
// id can be null during mutation events evilness. Also, XUL elements
|
||||
// loose their proto attributes during cc-unlink, so this can happen
|
||||
// during cc-unlink too.
|
||||
if (aId) {
|
||||
doc->RemoveFromIdTable(this, aId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1158,8 +1164,8 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||
SetInDocument();
|
||||
|
||||
if (GetCustomElementData()) {
|
||||
// Enqueue an enteredView callback for the custom element.
|
||||
aDocument->EnqueueLifecycleCallback(nsIDocument::eEnteredView, this);
|
||||
// Enqueue an attached callback for the custom element.
|
||||
aDocument->EnqueueLifecycleCallback(nsIDocument::eAttached, this);
|
||||
}
|
||||
|
||||
// Unset this flag since we now really are in a document.
|
||||
@ -1321,8 +1327,8 @@ Element::UnbindFromTree(bool aDeep, bool aNullParent)
|
||||
document->ClearBoxObjectFor(this);
|
||||
|
||||
if (GetCustomElementData()) {
|
||||
// Enqueue a leftView callback for the custom element.
|
||||
document->EnqueueLifecycleCallback(nsIDocument::eLeftView, this);
|
||||
// Enqueue a detached callback for the custom element.
|
||||
document->EnqueueLifecycleCallback(nsIDocument::eDetached, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,6 +193,7 @@
|
||||
#include "nsIAppsService.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/DocumentFragment.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/HTMLBodyElement.h"
|
||||
#include "mozilla/dom/HTMLInputElement.h"
|
||||
#include "mozilla/dom/NodeFilterBinding.h"
|
||||
@ -203,7 +204,6 @@
|
||||
#include "nsDOMCaretPosition.h"
|
||||
#include "nsIDOMHTMLTextAreaElement.h"
|
||||
#include "nsViewportInfo.h"
|
||||
#include "nsDOMEvent.h"
|
||||
#include "nsIContentPermissionPrompt.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "nsITextControlElement.h"
|
||||
@ -221,6 +221,7 @@
|
||||
#include "nsIMutableArray.h"
|
||||
#include "nsContentPermissionHelper.h"
|
||||
#include "mozilla/dom/DOMStringList.h"
|
||||
#include "nsWindowMemoryReporter.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
@ -330,11 +331,11 @@ CustomElementCallback::Call()
|
||||
static_cast<LifecycleCreatedCallback *>(mCallback.get())->Call(mThisObject, rv);
|
||||
mOwnerData->mElementIsBeingCreated = false;
|
||||
break;
|
||||
case nsIDocument::eEnteredView:
|
||||
static_cast<LifecycleEnteredViewCallback *>(mCallback.get())->Call(mThisObject, rv);
|
||||
case nsIDocument::eAttached:
|
||||
static_cast<LifecycleAttachedCallback *>(mCallback.get())->Call(mThisObject, rv);
|
||||
break;
|
||||
case nsIDocument::eLeftView:
|
||||
static_cast<LifecycleLeftViewCallback *>(mCallback.get())->Call(mThisObject, rv);
|
||||
case nsIDocument::eDetached:
|
||||
static_cast<LifecycleDetachedCallback *>(mCallback.get())->Call(mThisObject, rv);
|
||||
break;
|
||||
case nsIDocument::eAttributeChanged:
|
||||
static_cast<LifecycleAttributeChangedCallback *>(mCallback.get())->Call(mThisObject,
|
||||
@ -1749,16 +1750,16 @@ CustomDefinitionsTraverse(CustomElementHashKey* aKey,
|
||||
cb->NoteXPCOMChild(aDefinition->mCallbacks->mCreatedCallback.Value());
|
||||
}
|
||||
|
||||
if (callbacks->mEnteredViewCallback.WasPassed()) {
|
||||
if (callbacks->mAttachedCallback.WasPassed()) {
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
|
||||
"mCustomDefinitions->mCallbacks->mEnteredViewCallback");
|
||||
cb->NoteXPCOMChild(aDefinition->mCallbacks->mEnteredViewCallback.Value());
|
||||
"mCustomDefinitions->mCallbacks->mAttachedCallback");
|
||||
cb->NoteXPCOMChild(aDefinition->mCallbacks->mAttachedCallback.Value());
|
||||
}
|
||||
|
||||
if (callbacks->mLeftViewCallback.WasPassed()) {
|
||||
if (callbacks->mDetachedCallback.WasPassed()) {
|
||||
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb,
|
||||
"mCustomDefinitions->mCallbacks->mLeftViewCallback");
|
||||
cb->NoteXPCOMChild(aDefinition->mCallbacks->mLeftViewCallback.Value());
|
||||
"mCustomDefinitions->mCallbacks->mDetachedCallback");
|
||||
cb->NoteXPCOMChild(aDefinition->mCallbacks->mDetachedCallback.Value());
|
||||
}
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
@ -5570,15 +5571,15 @@ nsDocument::EnqueueLifecycleCallback(nsIDocument::ElementCallbackType aType,
|
||||
}
|
||||
break;
|
||||
|
||||
case nsIDocument::eEnteredView:
|
||||
if (definition->mCallbacks->mEnteredViewCallback.WasPassed()) {
|
||||
func = definition->mCallbacks->mEnteredViewCallback.Value();
|
||||
case nsIDocument::eAttached:
|
||||
if (definition->mCallbacks->mAttachedCallback.WasPassed()) {
|
||||
func = definition->mCallbacks->mAttachedCallback.Value();
|
||||
}
|
||||
break;
|
||||
|
||||
case nsIDocument::eLeftView:
|
||||
if (definition->mCallbacks->mLeftViewCallback.WasPassed()) {
|
||||
func = definition->mCallbacks->mLeftViewCallback.Value();
|
||||
case nsIDocument::eDetached:
|
||||
if (definition->mCallbacks->mDetachedCallback.WasPassed()) {
|
||||
func = definition->mCallbacks->mDetachedCallback.Value();
|
||||
}
|
||||
break;
|
||||
|
||||
@ -5921,12 +5922,12 @@ nsDocument::RegisterElement(JSContext* aCx, const nsAString& aType,
|
||||
EnqueueLifecycleCallback(nsIDocument::eCreated, elem, nullptr, definition);
|
||||
if (elem->GetCurrentDoc()) {
|
||||
// Normally callbacks can not be enqueued until the created
|
||||
// callback has been invoked, however, the entered view callback
|
||||
// callback has been invoked, however, the attached callback
|
||||
// in element upgrade is an exception so pretend the created
|
||||
// callback has been invoked.
|
||||
elem->GetCustomElementData()->mCreatedCallbackInvoked = true;
|
||||
|
||||
EnqueueLifecycleCallback(nsIDocument::eEnteredView, elem, nullptr, definition);
|
||||
EnqueueLifecycleCallback(nsIDocument::eAttached, elem, nullptr, definition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7648,7 +7649,7 @@ nsDocument::CreateEvent(const nsAString& aEventType, nsIDOMEvent** aReturn)
|
||||
return rv.ErrorCode();
|
||||
}
|
||||
|
||||
already_AddRefed<nsDOMEvent>
|
||||
already_AddRefed<Event>
|
||||
nsIDocument::CreateEvent(const nsAString& aEventType, ErrorResult& rv) const
|
||||
{
|
||||
nsIPresShell *shell = GetShell();
|
||||
|
@ -313,7 +313,7 @@ private:
|
||||
// The this value to use for invocation of the callback.
|
||||
nsRefPtr<mozilla::dom::Element> mThisObject;
|
||||
nsRefPtr<mozilla::dom::CallbackFunction> mCallback;
|
||||
// The type of callback (eCreated, eEnteredView, etc.)
|
||||
// The type of callback (eCreated, eAttached, etc.)
|
||||
nsIDocument::ElementCallbackType mType;
|
||||
// Arguments to be passed to the callback,
|
||||
// used by the attribute changed callback.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsIMemoryReporter.h"
|
||||
#include "nsIProtocolHandler.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsIJSRuntimeService.h"
|
||||
|
@ -18,6 +18,9 @@
|
||||
#include "mozilla/Likely.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "nsAsyncDOMEvent.h"
|
||||
#include "nsAttrValueOrString.h"
|
||||
#include "nsBindingManager.h"
|
||||
@ -41,7 +44,6 @@
|
||||
#include "nsFocusManager.h"
|
||||
#include "nsFrameManager.h"
|
||||
#include "nsFrameSelection.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsIAnonymousContentCreator.h"
|
||||
@ -96,11 +98,9 @@
|
||||
#include "nsCSSParser.h"
|
||||
#include "HTMLLegendElement.h"
|
||||
#include "nsWrapperCacheInlines.h"
|
||||
#include "mozilla/dom/ShadowRoot.h"
|
||||
#include "WrapperFactory.h"
|
||||
#include "DocumentType.h"
|
||||
#include <algorithm>
|
||||
#include "nsDOMEvent.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
#include "nsDOMMutationObserver.h"
|
||||
|
||||
@ -2633,7 +2633,7 @@ nsINode::GetAttributes()
|
||||
}
|
||||
|
||||
bool
|
||||
EventTarget::DispatchEvent(nsDOMEvent& aEvent,
|
||||
EventTarget::DispatchEvent(Event& aEvent,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
bool result = false;
|
||||
|
@ -69,18 +69,18 @@
|
||||
#include "nsIContentSecurityPolicy.h"
|
||||
#include "nsIChannelPolicy.h"
|
||||
#include "nsChannelPolicy.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "GeckoProfiler.h"
|
||||
#include "nsObjectFrame.h"
|
||||
#include "nsDOMClassInfo.h"
|
||||
#include "nsWrapperCacheInlines.h"
|
||||
#include "nsDOMJSUtils.h"
|
||||
#include "nsDOMEvent.h"
|
||||
|
||||
#include "nsWidgetsCID.h"
|
||||
#include "nsContentCID.h"
|
||||
#include "mozilla/BasicEvents.h"
|
||||
#include "mozilla/dom/BindingUtils.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
@ -307,7 +307,7 @@ nsPluginCrashedEvent::Run()
|
||||
}
|
||||
|
||||
ErrorResult rv;
|
||||
nsRefPtr<nsDOMEvent> event =
|
||||
nsRefPtr<Event> event =
|
||||
doc->CreateEvent(NS_LITERAL_STRING("datacontainerevents"), rv);
|
||||
nsCOMPtr<nsIDOMDataContainerEvent> containerEvent(do_QueryObject(event));
|
||||
if (!containerEvent) {
|
||||
|
@ -2140,7 +2140,15 @@ HTMLInputElement::GetValueIfStepped(int32_t aStep,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (GetValidityState(VALIDITY_STATE_STEP_MISMATCH) &&
|
||||
// If the current value isn't aligned on a step, then shift the value to the
|
||||
// nearest step that will cause the addition of aStep steps (further below)
|
||||
// to |value| to hit the required value.
|
||||
// (Instead of using GetValidityState(VALIDITY_STATE_STEP_MISMATCH) we have
|
||||
// to check HasStepMismatch and pass true as its aUseZeroIfValueNaN argument
|
||||
// since we need to treat the value "" as zero for stepping purposes even
|
||||
// though we don't suffer from a step mismatch when our value is the empty
|
||||
// string.)
|
||||
if (HasStepMismatch(true) &&
|
||||
value != minimum && value != maximum) {
|
||||
if (aStep > 0) {
|
||||
value -= NS_floorModulo(value - GetStepBase(), step);
|
||||
@ -2778,7 +2786,7 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue,
|
||||
if (!mParserCreating) {
|
||||
SanitizeValue(value);
|
||||
}
|
||||
// else SanitizeValue will be called by DoneCreatingElement
|
||||
// else DoneCreatingElement calls us again once mParserCreating is false
|
||||
|
||||
if (aSetValueChanged) {
|
||||
SetValueChanged(true);
|
||||
@ -2803,7 +2811,10 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue,
|
||||
numberControlFrame->SetValueOfAnonTextControl(value);
|
||||
}
|
||||
}
|
||||
OnValueChanged(!mParserCreating);
|
||||
if (!mParserCreating) {
|
||||
OnValueChanged(true);
|
||||
}
|
||||
// else DoneCreatingElement calls us again once mParserCreating is false
|
||||
}
|
||||
|
||||
if (mType == NS_FORM_INPUT_COLOR) {
|
||||
@ -3641,12 +3652,20 @@ HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
|
||||
// want to wipe out what they typed if they try to increment/decrement the
|
||||
// value. Better is to highlight the value as being invalid so that they
|
||||
// can correct what they typed.
|
||||
// We pass 'true' for UpdateValidityUIBits' aIsFocused argument regardless
|
||||
// because we need the UI to update _now_ or the user will wonder why the
|
||||
// step behavior isn't functioning.
|
||||
UpdateValidityUIBits(true);
|
||||
UpdateState(true);
|
||||
return;
|
||||
// We only do this if there actually is a value typed in by/displayed to
|
||||
// the user. (IsValid() can return false if the 'required' attribute is
|
||||
// set and the value is the empty string.)
|
||||
nsNumberControlFrame* numberControlFrame =
|
||||
do_QueryFrame(GetPrimaryFrame());
|
||||
if (numberControlFrame &&
|
||||
!numberControlFrame->AnonTextControlIsEmpty()) {
|
||||
// We pass 'true' for UpdateValidityUIBits' aIsFocused argument
|
||||
// regardless because we need the UI to update _now_ or the user will
|
||||
// wonder why the step behavior isn't functioning.
|
||||
UpdateValidityUIBits(true);
|
||||
UpdateState(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Decimal newValue = Decimal::nan(); // unchanged if value will not change
|
||||
@ -6433,7 +6452,7 @@ HTMLInputElement::IsRangeUnderflow() const
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLInputElement::HasStepMismatch() const
|
||||
HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
|
||||
{
|
||||
if (!DoesStepApply()) {
|
||||
return false;
|
||||
@ -6441,8 +6460,12 @@ HTMLInputElement::HasStepMismatch() const
|
||||
|
||||
Decimal value = GetValueAsDecimal();
|
||||
if (value.isNaN()) {
|
||||
// The element can't suffer from step mismatch if it's value isn't a number.
|
||||
return false;
|
||||
if (aUseZeroIfValueNaN) {
|
||||
value = 0;
|
||||
} else {
|
||||
// The element can't suffer from step mismatch if it's value isn't a number.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Decimal step = GetStep();
|
||||
|
@ -256,7 +256,7 @@ public:
|
||||
bool HasPatternMismatch() const;
|
||||
bool IsRangeOverflow() const;
|
||||
bool IsRangeUnderflow() const;
|
||||
bool HasStepMismatch() const;
|
||||
bool HasStepMismatch(bool aUseZeroIfValueNaN = false) const;
|
||||
bool HasBadInput() const;
|
||||
void UpdateTooLongValidityState();
|
||||
void UpdateValueMissingValidityState();
|
||||
|
@ -6,8 +6,8 @@
|
||||
#include "mozilla/dom/UndoManager.h"
|
||||
#include "mozilla/dom/DOMTransactionBinding.h"
|
||||
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "nsDOMClassInfoID.h"
|
||||
#include "nsDOMEvent.h"
|
||||
#include "nsIClassInfo.h"
|
||||
#include "nsIDOMDocument.h"
|
||||
#include "nsIXPCScriptable.h"
|
||||
@ -1142,7 +1142,7 @@ UndoManager::DispatchTransactionEvent(JSContext* aCx, const nsAString& aType,
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<nsDOMEvent> event = mHostNode->OwnerDoc()->CreateEvent(
|
||||
nsRefPtr<Event> event = mHostNode->OwnerDoc()->CreateEvent(
|
||||
NS_LITERAL_STRING("domtransaction"), aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
|
@ -71,6 +71,18 @@ function getStepBase(element) {
|
||||
Number(element.getAttribute("value") || "NaN") || 0;
|
||||
}
|
||||
|
||||
function hasStepMismatch(element) {
|
||||
var value = element.value;
|
||||
if (value == "") {
|
||||
value = 0;
|
||||
}
|
||||
var step = getStep(element);
|
||||
if (step == "any") {
|
||||
return false;
|
||||
}
|
||||
return ((value - getStepBase(element)) % step) != 0;
|
||||
}
|
||||
|
||||
function floorModulo(x, y) {
|
||||
return (x - y * Math.floor(x / y));
|
||||
}
|
||||
@ -101,7 +113,7 @@ function expectedValueAfterStepUpOrDown(stepFactor, element) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (element.validity.stepMismatch &&
|
||||
if (hasStepMismatch(element) &&
|
||||
value != minimum && value != maximum) {
|
||||
if (stepFactor > 0) {
|
||||
value -= floorModulo(value - getStepBase(element), step);
|
||||
@ -138,8 +150,8 @@ function test() {
|
||||
var elem = document.getElementById("input");
|
||||
elem.focus();
|
||||
|
||||
elem.min = -3;
|
||||
elem.max = 3;
|
||||
elem.min = -5;
|
||||
elem.max = 5;
|
||||
elem.step = 2;
|
||||
var defaultValue = 0;
|
||||
var oldVal, expectedVal;
|
||||
@ -203,6 +215,22 @@ function test() {
|
||||
sendString("abc");
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, "", "Test " + key + " does nothing when the input is invalid");
|
||||
|
||||
// Test that no value does not block UI initiated stepping:
|
||||
oldVal = elem.value = "";
|
||||
elem.setAttribute("required", "required");
|
||||
elem.select();
|
||||
expectedVal = expectedValAfterKeyEvent(key, elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test " + key + " for number control with value set to the empty string and with the 'required' attribute set");
|
||||
|
||||
// Same again:
|
||||
expectedVal = expectedValAfterKeyEvent(key, elem);
|
||||
synthesizeKey(key, {});
|
||||
is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
|
||||
|
||||
// Reset 'required' attribute:
|
||||
elem.removeAttribute("required");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,6 @@
|
||||
|
||||
#include "nsICommandManager.h"
|
||||
#include "mozilla/dom/HTMLSharedElement.h"
|
||||
#include "nsDOMEvent.h"
|
||||
|
||||
class nsIEditor;
|
||||
class nsIParser;
|
||||
|
@ -255,20 +255,29 @@ public:
|
||||
* *aFinished is set to false by the caller. If the callee sets it to true,
|
||||
* we'll finish the stream and not call this again.
|
||||
*/
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
MOZ_ASSERT(mInputCount <= 1 && mOutputCount <= 1);
|
||||
*aOutput = aInput;
|
||||
}
|
||||
/**
|
||||
* Produce the next block of audio samples, before input is provided.
|
||||
* ProcessBlock() will be called later, and it then should not change
|
||||
* aOutput. This is used only for DelayNodeEngine in a feedback loop.
|
||||
*/
|
||||
virtual void ProduceBlockBeforeInput(AudioChunk* aOutput)
|
||||
{
|
||||
NS_NOTREACHED("ProduceBlockBeforeInput called on wrong engine\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce the next block of audio samples, given input samples in the aInput
|
||||
* array. There is one input sample per active port in aInput, in order.
|
||||
* This is the multi-input/output version of ProduceAudioBlock. Only one kind
|
||||
* of ProduceAudioBlock is called on each node, depending on whether the
|
||||
* This is the multi-input/output version of ProcessBlock. Only one kind
|
||||
* of ProcessBlock is called on each node, depending on whether the
|
||||
* number of inputs and outputs are both 1 or not.
|
||||
*
|
||||
* aInput is always guaranteed to not contain more input AudioChunks than the
|
||||
@ -279,10 +288,10 @@ public:
|
||||
* corresponding AudioNode, in which case it will be interpreted as a channel
|
||||
* of silence.
|
||||
*/
|
||||
virtual void ProduceAudioBlocksOnPorts(AudioNodeStream* aStream,
|
||||
const OutputChunks& aInput,
|
||||
OutputChunks& aOutput,
|
||||
bool* aFinished)
|
||||
virtual void ProcessBlocksOnPorts(AudioNodeStream* aStream,
|
||||
const OutputChunks& aInput,
|
||||
OutputChunks& aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
MOZ_ASSERT(mInputCount > 1 || mOutputCount > 1);
|
||||
// Only produce one output port, and drop all other input ports.
|
||||
|
@ -324,8 +324,8 @@ ConvertSegmentToAudioBlock(AudioSegment* aSegment, AudioChunk* aBlock)
|
||||
}
|
||||
|
||||
void
|
||||
AudioNodeExternalInputStream::ProduceOutput(GraphTime aFrom, GraphTime aTo,
|
||||
uint32_t aFlags)
|
||||
AudioNodeExternalInputStream::ProcessInput(GraphTime aFrom, GraphTime aTo,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
// According to spec, number of outputs is always 1.
|
||||
mLastChunks.SetLength(1);
|
||||
|
@ -25,7 +25,7 @@ public:
|
||||
AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate);
|
||||
~AudioNodeExternalInputStream();
|
||||
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
|
||||
virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
// For storing pointers and data about input tracks, like the last TrackTick which
|
||||
|
@ -399,7 +399,7 @@ AudioNodeStream::UpMixDownMixChunk(const AudioChunk* aChunk,
|
||||
// The MediaStreamGraph guarantees that this is actually one block, for
|
||||
// AudioNodeStreams.
|
||||
void
|
||||
AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
|
||||
AudioNodeStream::ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
|
||||
{
|
||||
EnsureTrack(AUDIO_TRACK, mSampleRate);
|
||||
// No more tracks will be coming
|
||||
@ -426,17 +426,10 @@ AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
|
||||
ObtainInputBlock(inputChunks[i], i);
|
||||
}
|
||||
bool finished = false;
|
||||
#ifdef DEBUG
|
||||
for (uint16_t i = 0; i < outputCount; ++i) {
|
||||
// Alter mDuration so we can detect if ProduceAudioBlock fails to set
|
||||
// chunks.
|
||||
mLastChunks[i].mDuration--;
|
||||
}
|
||||
#endif
|
||||
if (maxInputs <= 1 && mEngine->OutputCount() <= 1) {
|
||||
mEngine->ProduceAudioBlock(this, inputChunks[0], &mLastChunks[0], &finished);
|
||||
mEngine->ProcessBlock(this, inputChunks[0], &mLastChunks[0], &finished);
|
||||
} else {
|
||||
mEngine->ProduceAudioBlocksOnPorts(this, inputChunks, mLastChunks, &finished);
|
||||
mEngine->ProcessBlocksOnPorts(this, inputChunks, mLastChunks, &finished);
|
||||
}
|
||||
for (uint16_t i = 0; i < outputCount; ++i) {
|
||||
NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE,
|
||||
|
@ -33,6 +33,9 @@ class AudioNodeEngine;
|
||||
* integrates audio processing with the MediaStreamGraph.
|
||||
*/
|
||||
class AudioNodeStream : public ProcessedMediaStream {
|
||||
typedef dom::ChannelCountMode ChannelCountMode;
|
||||
typedef dom::ChannelInterpretation ChannelInterpretation;
|
||||
|
||||
public:
|
||||
typedef mozilla::dom::AudioContext AudioContext;
|
||||
|
||||
@ -56,8 +59,8 @@ public:
|
||||
mMuted(false)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mChannelCountMode = dom::ChannelCountMode::Max;
|
||||
mChannelInterpretation = dom::ChannelInterpretation::Speakers;
|
||||
mChannelCountMode = ChannelCountMode::Max;
|
||||
mChannelInterpretation = ChannelInterpretation::Speakers;
|
||||
// AudioNodes are always producing data
|
||||
mHasCurrentData = true;
|
||||
MOZ_COUNT_CTOR(AudioNodeStream);
|
||||
@ -79,8 +82,13 @@ public:
|
||||
// This consumes the contents of aData. aData will be emptied after this returns.
|
||||
void SetRawArrayData(nsTArray<float>& aData);
|
||||
void SetChannelMixingParameters(uint32_t aNumberOfChannels,
|
||||
dom::ChannelCountMode aChannelCountMoe,
|
||||
dom::ChannelInterpretation aChannelInterpretation);
|
||||
ChannelCountMode aChannelCountMoe,
|
||||
ChannelInterpretation aChannelInterpretation);
|
||||
ChannelInterpretation GetChannelInterpretation()
|
||||
{
|
||||
return mChannelInterpretation;
|
||||
}
|
||||
|
||||
void SetAudioParamHelperStream()
|
||||
{
|
||||
MOZ_ASSERT(!mAudioParamStream, "Can only do this once");
|
||||
@ -93,9 +101,9 @@ public:
|
||||
void SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
|
||||
double aStreamTime);
|
||||
void SetChannelMixingParametersImpl(uint32_t aNumberOfChannels,
|
||||
dom::ChannelCountMode aChannelCountMoe,
|
||||
dom::ChannelInterpretation aChannelInterpretation);
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
|
||||
ChannelCountMode aChannelCountMoe,
|
||||
ChannelInterpretation aChannelInterpretation);
|
||||
virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
|
||||
TrackTicks GetCurrentPosition();
|
||||
bool IsAudioParamStream() const
|
||||
{
|
||||
@ -171,8 +179,8 @@ protected:
|
||||
// The number of input channels that this stream requires. 0 means don't care.
|
||||
uint32_t mNumberOfInputChannels;
|
||||
// The mixing modes
|
||||
dom::ChannelCountMode mChannelCountMode;
|
||||
dom::ChannelInterpretation mChannelInterpretation;
|
||||
ChannelCountMode mChannelCountMode;
|
||||
ChannelInterpretation mChannelInterpretation;
|
||||
// Whether the stream should be marked as finished as soon
|
||||
// as the current time range has been computed block by block.
|
||||
bool mMarkAsFinishedAfterThisBlock;
|
||||
|
@ -99,6 +99,7 @@ struct AudioChunk {
|
||||
mDuration = aDuration;
|
||||
mVolume = 1.0f;
|
||||
}
|
||||
int ChannelCount() const { return mChannelData.Length(); }
|
||||
|
||||
TrackTicks mDuration; // in frames within the buffer
|
||||
nsRefPtr<ThreadSharedObject> mBuffer; // the buffer object whose lifetime is managed; null means data is all zeroes
|
||||
|
@ -1102,7 +1102,7 @@ MediaStreamGraphImpl::ProduceDataForStreamsBlockByBlock(uint32_t aStreamIndex,
|
||||
for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) {
|
||||
ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream();
|
||||
if (ps) {
|
||||
ps->ProduceOutput(t, next, (next == aTo) ? ProcessedMediaStream::ALLOW_FINISH : 0);
|
||||
ps->ProcessInput(t, next, (next == aTo) ? ProcessedMediaStream::ALLOW_FINISH : 0);
|
||||
}
|
||||
}
|
||||
t = next;
|
||||
@ -1211,7 +1211,7 @@ MediaStreamGraphImpl::RunThread()
|
||||
|
||||
// Play stream contents.
|
||||
bool allBlockedForever = true;
|
||||
// True when we've done ProduceOutput for all processed streams.
|
||||
// True when we've done ProcessInput for all processed streams.
|
||||
bool doneAllProducing = false;
|
||||
// Figure out what each stream wants to do
|
||||
for (uint32_t i = 0; i < mStreams.Length(); ++i) {
|
||||
@ -1237,8 +1237,8 @@ MediaStreamGraphImpl::RunThread()
|
||||
ticksProcessed += TimeToTicksRoundDown(n->SampleRate(), mStateComputedTime - prevComputedTime);
|
||||
doneAllProducing = true;
|
||||
} else {
|
||||
ps->ProduceOutput(prevComputedTime, mStateComputedTime,
|
||||
ProcessedMediaStream::ALLOW_FINISH);
|
||||
ps->ProcessInput(prevComputedTime, mStateComputedTime,
|
||||
ProcessedMediaStream::ALLOW_FINISH);
|
||||
NS_WARN_IF_FALSE(stream->mBuffer.GetEnd() >=
|
||||
GraphTimeToStreamTime(stream, mStateComputedTime),
|
||||
"Stream did not produce enough data");
|
||||
|
@ -906,7 +906,7 @@ protected:
|
||||
/**
|
||||
* This stream processes zero or more input streams in parallel to produce
|
||||
* its output. The details of how the output is produced are handled by
|
||||
* subclasses overriding the ProduceOutput method.
|
||||
* subclasses overriding the ProcessInput method.
|
||||
*/
|
||||
class ProcessedMediaStream : public MediaStream {
|
||||
public:
|
||||
@ -962,7 +962,7 @@ public:
|
||||
* This will be called on streams that have finished. Most stream types should
|
||||
* just return immediately if IsFinishedOnGraphThread(), but some may wish to
|
||||
* update internal state (see AudioNodeStream).
|
||||
* ProduceOutput is allowed to call FinishOnGraphThread only if ALLOW_FINISH
|
||||
* ProcessInput is allowed to call FinishOnGraphThread only if ALLOW_FINISH
|
||||
* is in aFlags. (This flag will be set when aTo >= mStateComputedTime, i.e.
|
||||
* when we've producing the last block of data we need to produce.) Otherwise
|
||||
* we can get into a situation where we've determined the stream should not
|
||||
@ -972,7 +972,7 @@ public:
|
||||
enum {
|
||||
ALLOW_FINISH = 0x01
|
||||
};
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) = 0;
|
||||
virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) = 0;
|
||||
void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; }
|
||||
|
||||
/**
|
||||
@ -1065,7 +1065,7 @@ public:
|
||||
* Dispatches a runnable that will run on the main thread after all
|
||||
* main-thread stream state has been next updated.
|
||||
* Should only be called during MediaStreamListener callbacks or during
|
||||
* ProcessedMediaStream::ProduceOutput().
|
||||
* ProcessedMediaStream::ProcessInput().
|
||||
*/
|
||||
void DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable)
|
||||
{
|
||||
|
@ -39,7 +39,7 @@ public:
|
||||
}
|
||||
ProcessedMediaStream::RemoveInput(aPort);
|
||||
}
|
||||
virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE
|
||||
virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE
|
||||
{
|
||||
if (IsFinishedOnGraphThread()) {
|
||||
return;
|
||||
|
@ -30,9 +30,9 @@ class JSObject;
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
PRLogModuleInfo* gMediaSourceLog;
|
||||
#define LOG(type, msg) PR_LOG(gMediaSourceLog, type, msg)
|
||||
#define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__))
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#define MSE_DEBUG(...)
|
||||
#endif
|
||||
|
||||
// Arbitrary limit.
|
||||
@ -40,6 +40,47 @@ static const unsigned int MAX_SOURCE_BUFFERS = 16;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
static const char* const gMediaSourceTypes[6] = {
|
||||
"video/webm",
|
||||
"audio/webm",
|
||||
"video/mp4",
|
||||
"audio/mp4",
|
||||
"audio/mpeg",
|
||||
nullptr
|
||||
};
|
||||
|
||||
static nsresult
|
||||
IsTypeSupported(const nsAString& aType)
|
||||
{
|
||||
if (aType.IsEmpty()) {
|
||||
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
|
||||
}
|
||||
// TODO: Further restrict this to formats in the spec.
|
||||
nsContentTypeParser parser(aType);
|
||||
nsAutoString mimeType;
|
||||
nsresult rv = parser.GetType(mimeType);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
}
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; gMediaSourceTypes[i]; ++i) {
|
||||
if (mimeType.EqualsASCII(gMediaSourceTypes[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
}
|
||||
// Check aType against HTMLMediaElement list of MIME types. Since we've
|
||||
// already restricted the container format, this acts as a specific check
|
||||
// of any specified "codecs" parameter of aType.
|
||||
if (dom::HTMLMediaElement::GetCanPlay(aType) == CANPLAY_NO) {
|
||||
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
/* static */ already_AddRefed<MediaSource>
|
||||
@ -103,7 +144,9 @@ MediaSource::SetDuration(double aDuration, ErrorResult& aRv)
|
||||
already_AddRefed<SourceBuffer>
|
||||
MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
|
||||
{
|
||||
if (!IsTypeSupportedInternal(aType, aRv)) {
|
||||
nsresult rv = mozilla::IsTypeSupported(aType);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
if (mSourceBuffers->Length() >= MAX_SOURCE_BUFFERS) {
|
||||
@ -116,15 +159,15 @@ MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
|
||||
}
|
||||
nsContentTypeParser parser(aType);
|
||||
nsAutoString mimeType;
|
||||
nsresult rv = parser.GetType(mimeType);
|
||||
rv = parser.GetType(mimeType);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
nsRefPtr<SourceBuffer> sourceBuffer = new SourceBuffer(this, NS_ConvertUTF16toUTF8(mimeType));
|
||||
mSourceBuffers->Append(sourceBuffer);
|
||||
LOG(PR_LOG_DEBUG, ("%p AddSourceBuffer(Type=%s) -> %p", this,
|
||||
NS_ConvertUTF16toUTF8(mimeType).get(), sourceBuffer.get()));
|
||||
MSE_DEBUG("%p AddSourceBuffer(Type=%s) -> %p", this,
|
||||
NS_ConvertUTF16toUTF8(mimeType).get(), sourceBuffer.get());
|
||||
return sourceBuffer.forget();
|
||||
}
|
||||
|
||||
@ -165,21 +208,45 @@ MediaSource::EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, Er
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
EndOfStreamInternal(aError, aRv);
|
||||
|
||||
SetReadyState(MediaSourceReadyState::Ended);
|
||||
mSourceBuffers->Ended();
|
||||
if (!aError.WasPassed()) {
|
||||
// TODO:
|
||||
// Run duration change algorithm.
|
||||
// DurationChange(highestDurationOfSourceBuffers, aRv);
|
||||
// if (aRv.Failed()) {
|
||||
// return;
|
||||
// }
|
||||
// Notify media element that all data is now available.
|
||||
return;
|
||||
}
|
||||
switch (aError.Value()) {
|
||||
case MediaSourceEndOfStreamError::Network:
|
||||
// TODO: If media element has a readyState of:
|
||||
// HAVE_NOTHING -> run resource fetch algorithm
|
||||
// > HAVE_NOTHING -> run "interrupted" steps of resource fetch
|
||||
break;
|
||||
case MediaSourceEndOfStreamError::Decode:
|
||||
// TODO: If media element has a readyState of:
|
||||
// HAVE_NOTHING -> run "unsupported" steps of resource fetch
|
||||
// > HAVE_NOTHING -> run "corrupted" steps of resource fetch
|
||||
break;
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
MediaSource::IsTypeSupported(const GlobalObject& aGlobal,
|
||||
const nsAString& aType)
|
||||
MediaSource::IsTypeSupported(const GlobalObject&, const nsAString& aType)
|
||||
{
|
||||
ErrorResult unused;
|
||||
return IsTypeSupportedInternal(aType, unused);
|
||||
return NS_SUCCEEDED(mozilla::IsTypeSupported(aType));
|
||||
}
|
||||
|
||||
bool
|
||||
MediaSource::Attach(MediaSourceDecoder* aDecoder)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Attaching decoder %p owner %p", this, aDecoder, aDecoder->GetOwner()));
|
||||
MSE_DEBUG("%p Attaching decoder %p owner %p", this, aDecoder, aDecoder->GetOwner());
|
||||
MOZ_ASSERT(aDecoder);
|
||||
if (mReadyState != MediaSourceReadyState::Closed) {
|
||||
return false;
|
||||
@ -193,7 +260,7 @@ MediaSource::Attach(MediaSourceDecoder* aDecoder)
|
||||
void
|
||||
MediaSource::Detach()
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Detaching decoder %p owner %p", this, mDecoder.get(), mDecoder->GetOwner()));
|
||||
MSE_DEBUG("%p Detaching decoder %p owner %p", this, mDecoder.get(), mDecoder->GetOwner());
|
||||
MOZ_ASSERT(mDecoder);
|
||||
mDecoder->DetachMediaSource();
|
||||
mDecoder = nullptr;
|
||||
@ -253,14 +320,14 @@ MediaSource::SetReadyState(MediaSourceReadyState aState)
|
||||
void
|
||||
MediaSource::DispatchSimpleEvent(const char* aName)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Dispatching event %s to MediaSource", this, aName));
|
||||
MSE_DEBUG("%p Dispatching event %s to MediaSource", this, aName);
|
||||
DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
|
||||
}
|
||||
|
||||
void
|
||||
MediaSource::QueueAsyncSimpleEvent(const char* aName)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Queuing event %s to MediaSource", this, aName));
|
||||
MSE_DEBUG("%p Queuing event %s to MediaSource", this, aName);
|
||||
nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<MediaSource>(this, aName);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
@ -283,82 +350,6 @@ MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv)
|
||||
// TODO: Update media element's duration and run element's duration change algorithm.
|
||||
}
|
||||
|
||||
void
|
||||
MediaSource::EndOfStreamInternal(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv)
|
||||
{
|
||||
SetReadyState(MediaSourceReadyState::Ended);
|
||||
mSourceBuffers->Ended();
|
||||
if (!aError.WasPassed()) {
|
||||
// TODO:
|
||||
// Run duration change algorithm.
|
||||
// DurationChange(highestDurationOfSourceBuffers, aRv);
|
||||
// if (aRv.Failed()) {
|
||||
// return;
|
||||
// }
|
||||
// Notify media element that all data is now available.
|
||||
return;
|
||||
}
|
||||
switch (aError.Value()) {
|
||||
case MediaSourceEndOfStreamError::Network:
|
||||
// TODO: If media element has a readyState of:
|
||||
// HAVE_NOTHING -> run resource fetch algorithm
|
||||
// > HAVE_NOTHING -> run "interrupted" steps of resource fetch
|
||||
break;
|
||||
case MediaSourceEndOfStreamError::Decode:
|
||||
// TODO: If media element has a readyState of:
|
||||
// HAVE_NOTHING -> run "unsupported" steps of resource fetch
|
||||
// > HAVE_NOTHING -> run "corrupted" steps of resource fetch
|
||||
break;
|
||||
default:
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
static const char* const gMediaSourceTypes[6] = {
|
||||
"video/webm",
|
||||
"audio/webm",
|
||||
"video/mp4",
|
||||
"audio/mp4",
|
||||
"audio/mpeg",
|
||||
nullptr
|
||||
};
|
||||
|
||||
/* static */ bool
|
||||
MediaSource::IsTypeSupportedInternal(const nsAString& aType, ErrorResult& aRv)
|
||||
{
|
||||
if (aType.IsEmpty()) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
|
||||
return false;
|
||||
}
|
||||
// TODO: Further restrict this to formats in the spec.
|
||||
nsContentTypeParser parser(aType);
|
||||
nsAutoString mimeType;
|
||||
nsresult rv = parser.GetType(mimeType);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return false;
|
||||
}
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; gMediaSourceTypes[i]; ++i) {
|
||||
if (mimeType.EqualsASCII(gMediaSourceTypes[i])) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return false;
|
||||
}
|
||||
// Check aType against HTMLMediaElement list of MIME types. Since we've
|
||||
// already restricted the container format, this acts as a specific check
|
||||
// of any specified "codecs" parameter of aType.
|
||||
if (HTMLMediaElement::GetCanPlay(aType) == CANPLAY_NO) {
|
||||
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
nsPIDOMWindow*
|
||||
MediaSource::GetParentObject() const
|
||||
{
|
||||
|
@ -60,8 +60,7 @@ public:
|
||||
void RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv);
|
||||
|
||||
void EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv);
|
||||
static bool IsTypeSupported(const GlobalObject& aGlobal,
|
||||
const nsAString& aType);
|
||||
static bool IsTypeSupported(const GlobalObject&, const nsAString& aType);
|
||||
/** End WebIDL Methods. */
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
@ -98,9 +97,6 @@ private:
|
||||
void QueueAsyncSimpleEvent(const char* aName);
|
||||
|
||||
void DurationChange(double aNewDuration, ErrorResult& aRv);
|
||||
void EndOfStreamInternal(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv);
|
||||
|
||||
static bool IsTypeSupportedInternal(const nsAString& aType, ErrorResult& aRv);
|
||||
|
||||
double mDuration;
|
||||
|
||||
|
@ -23,9 +23,9 @@
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gMediaSourceLog;
|
||||
#define LOG(type, msg) PR_LOG(gMediaSourceLog, type, msg)
|
||||
#define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__))
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#define MSE_DEBUG(...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
@ -202,7 +202,7 @@ MediaSourceDecoder::CreateSubDecoder(const nsACString& aType)
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mDecoders.AppendElement(decoder);
|
||||
mReaders.AppendElement(reader);
|
||||
LOG(PR_LOG_DEBUG, ("Registered subdecoder %p subreader %p", decoder.get(), reader.get()));
|
||||
MSE_DEBUG("Registered subdecoder %p subreader %p", decoder.get(), reader.get());
|
||||
mon.NotifyAll();
|
||||
|
||||
decoder->SetReader(reader.forget());
|
||||
@ -221,7 +221,7 @@ MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
|
||||
MediaDecoderReader* reader = readers[i];
|
||||
MediaInfo mi;
|
||||
nsresult rv = reader->ReadMetadata(&mi, aTags);
|
||||
LOG(PR_LOG_DEBUG, ("ReadMetadata on SB reader %p", reader));
|
||||
MSE_DEBUG("ReadMetadata on SB reader %p", reader);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -26,9 +26,9 @@ class JSObject;
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gMediaSourceLog;
|
||||
#define LOG(type, msg) PR_LOG(gMediaSourceLog, type, msg)
|
||||
#define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__))
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#define MSE_DEBUG(...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
@ -289,14 +289,14 @@ SourceBuffer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
|
||||
void
|
||||
SourceBuffer::DispatchSimpleEvent(const char* aName)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Dispatching event %s to SourceBuffer", this, aName));
|
||||
MSE_DEBUG("%p Dispatching event %s to SourceBuffer", this, aName);
|
||||
DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
|
||||
}
|
||||
|
||||
void
|
||||
SourceBuffer::QueueAsyncSimpleEvent(const char* aName)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Queuing event %s to SourceBuffer", this, aName));
|
||||
MSE_DEBUG("%p Queuing event %s to SourceBuffer", this, aName);
|
||||
nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBuffer>(this, aName);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
@ -339,7 +339,7 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR
|
||||
}
|
||||
// TODO: Run coded frame eviction algorithm.
|
||||
// TODO: Test buffer full flag.
|
||||
LOG(PR_LOG_DEBUG, ("%p Append(ArrayBuffer=%u)", this, aLength));
|
||||
MSE_DEBUG("%p Append(ArrayBuffer=%u)", this, aLength);
|
||||
StartUpdating();
|
||||
// XXX: For future reference: NDA call must run on the main thread.
|
||||
mDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
|
||||
|
@ -22,9 +22,9 @@ class JSObject;
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gMediaSourceLog;
|
||||
#define LOG(type, msg) PR_LOG(gMediaSourceLog, type, msg)
|
||||
#define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__))
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#define MSE_DEBUG(...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
@ -122,14 +122,14 @@ SourceBufferList::Ended()
|
||||
void
|
||||
SourceBufferList::DispatchSimpleEvent(const char* aName)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Dispatching event %s to SourceBufferList", this, aName));
|
||||
MSE_DEBUG("%p Dispatching event %s to SourceBufferList", this, aName);
|
||||
DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
|
||||
}
|
||||
|
||||
void
|
||||
SourceBufferList::QueueAsyncSimpleEvent(const char* aName)
|
||||
{
|
||||
LOG(PR_LOG_DEBUG, ("%p Queuing event %s to SourceBufferList", this, aName));
|
||||
MSE_DEBUG("%p Queuing event %s to SourceBufferList", this, aName);
|
||||
nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<SourceBufferList>(this, aName);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gMediaSourceLog;
|
||||
#define LOG(type, msg) PR_LOG(gMediaSourceLog, type, msg)
|
||||
#define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__))
|
||||
#else
|
||||
#define LOG(type, msg)
|
||||
#define MSE_DEBUG(...)
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
@ -33,7 +33,7 @@ nsresult
|
||||
SourceBufferResource::Close()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mMonitor);
|
||||
LOG(PR_LOG_DEBUG, ("%p SBR::Close", this));
|
||||
MSE_DEBUG("%p SBR::Close", this);
|
||||
//MOZ_ASSERT(!mClosed);
|
||||
mClosed = true;
|
||||
mon.NotifyAll();
|
||||
@ -49,20 +49,20 @@ SourceBufferResource::Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
while (blockingRead &&
|
||||
!mEnded &&
|
||||
mOffset + aCount > static_cast<uint64_t>(GetLength())) {
|
||||
LOG(PR_LOG_DEBUG, ("%p SBR::Read waiting for data", this));
|
||||
MSE_DEBUG("%p SBR::Read waiting for data", this);
|
||||
mon.Wait();
|
||||
}
|
||||
|
||||
uint32_t available = GetLength() - mOffset;
|
||||
uint32_t count = std::min(aCount, available);
|
||||
if (!PR_GetEnv("MOZ_QUIET")) {
|
||||
LOG(PR_LOG_DEBUG, ("%p SBR::Read aCount=%u length=%u offset=%u "
|
||||
"available=%u count=%u, blocking=%d bufComplete=%d",
|
||||
this, aCount, GetLength(), mOffset, available, count,
|
||||
blockingRead, mEnded));
|
||||
MSE_DEBUG("%p SBR::Read aCount=%u length=%u offset=%u "
|
||||
"available=%u count=%u, blocking=%d bufComplete=%d",
|
||||
this, aCount, GetLength(), mOffset, available, count,
|
||||
blockingRead, mEnded);
|
||||
}
|
||||
if (available == 0) {
|
||||
LOG(PR_LOG_DEBUG, ("%p SBR::Read EOF", this));
|
||||
MSE_DEBUG("%p SBR::Read EOF", this);
|
||||
*aBytes = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
@ -160,7 +160,7 @@ SourceBufferResource::Ended()
|
||||
SourceBufferResource::~SourceBufferResource()
|
||||
{
|
||||
MOZ_COUNT_DTOR(SourceBufferResource);
|
||||
LOG(PR_LOG_DEBUG, ("%p SBR::~SBR", this));
|
||||
MSE_DEBUG("%p SBR::~SBR", this);
|
||||
}
|
||||
|
||||
SourceBufferResource::SourceBufferResource(nsIPrincipal* aPrincipal,
|
||||
@ -173,7 +173,7 @@ SourceBufferResource::SourceBufferResource(nsIPrincipal* aPrincipal,
|
||||
, mEnded(false)
|
||||
{
|
||||
MOZ_COUNT_CTOR(SourceBufferResource);
|
||||
LOG(PR_LOG_DEBUG, ("%p SBR::SBR()", this));
|
||||
MSE_DEBUG("%p SBR::SBR()", this);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -57,10 +57,10 @@ public:
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
*aOutput = aInput;
|
||||
|
||||
|
@ -423,10 +423,10 @@ public:
|
||||
UpdateResampler(outRate, aChannels);
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
if (!mBuffer || !mBufferEnd) {
|
||||
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
|
@ -52,10 +52,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
// Do this just for the sake of political correctness; this output
|
||||
// will not go anywhere.
|
||||
@ -165,10 +165,10 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
*aOutput = aInput;
|
||||
aOutput->mVolume *= mVolume;
|
||||
|
@ -11,19 +11,19 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_3(AudioProcessingEvent, nsDOMEvent,
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_3(AudioProcessingEvent, Event,
|
||||
mInputBuffer, mOutputBuffer, mNode)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioProcessingEvent)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
|
||||
NS_INTERFACE_MAP_END_INHERITING(Event)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(AudioProcessingEvent, nsDOMEvent)
|
||||
NS_IMPL_RELEASE_INHERITED(AudioProcessingEvent, nsDOMEvent)
|
||||
NS_IMPL_ADDREF_INHERITED(AudioProcessingEvent, Event)
|
||||
NS_IMPL_RELEASE_INHERITED(AudioProcessingEvent, Event)
|
||||
|
||||
AudioProcessingEvent::AudioProcessingEvent(ScriptProcessorNode* aOwner,
|
||||
nsPresContext* aPresContext,
|
||||
WidgetEvent* aEvent)
|
||||
: nsDOMEvent(aOwner, aPresContext, aEvent)
|
||||
: Event(aOwner, aPresContext, aEvent)
|
||||
, mPlaybackTime(0.0)
|
||||
, mNode(aOwner)
|
||||
{
|
||||
|
@ -7,14 +7,14 @@
|
||||
#ifndef AudioProcessingEvent_h_
|
||||
#define AudioProcessingEvent_h_
|
||||
|
||||
#include "nsDOMEvent.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "ScriptProcessorNode.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class AudioProcessingEvent : public nsDOMEvent
|
||||
class AudioProcessingEvent : public Event
|
||||
{
|
||||
public:
|
||||
AudioProcessingEvent(ScriptProcessorNode* aOwner,
|
||||
@ -22,8 +22,8 @@ public:
|
||||
WidgetEvent* aEvent);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_FORWARD_TO_NSDOMEVENT
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioProcessingEvent, nsDOMEvent)
|
||||
NS_FORWARD_TO_EVENT
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioProcessingEvent, Event)
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
|
||||
|
@ -137,10 +137,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
float inputBuffer[WEBAUDIO_BLOCK_SIZE];
|
||||
|
||||
|
@ -23,10 +23,10 @@ public:
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlocksOnPorts(AudioNodeStream* aStream,
|
||||
const OutputChunks& aInput,
|
||||
OutputChunks& aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlocksOnPorts(AudioNodeStream* aStream,
|
||||
const OutputChunks& aInput,
|
||||
OutputChunks& aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(aInput.Length() >= 1, "Should have one or more input ports");
|
||||
|
||||
|
@ -23,10 +23,10 @@ public:
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlocksOnPorts(AudioNodeStream* aStream,
|
||||
const OutputChunks& aInput,
|
||||
OutputChunks& aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlocksOnPorts(AudioNodeStream* aStream,
|
||||
const OutputChunks& aInput,
|
||||
OutputChunks& aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(aInput.Length() == 1, "Should only have one input port");
|
||||
|
||||
|
@ -101,10 +101,10 @@ public:
|
||||
mNormalize, mSampleRate);
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
if (!mReverb) {
|
||||
*aOutput = aInput;
|
||||
|
249
content/media/webaudio/DelayBuffer.cpp
Normal file
249
content/media/webaudio/DelayBuffer.cpp
Normal file
@ -0,0 +1,249 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "DelayBuffer.h"
|
||||
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "AudioChannelFormat.h"
|
||||
#include "AudioNodeEngine.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void
|
||||
DelayBuffer::Write(const AudioChunk& aInputChunk)
|
||||
{
|
||||
// We must have a reference to the buffer if there are channels
|
||||
MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.mChannelData.Length());
|
||||
|
||||
if (!EnsureBuffer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mCurrentChunk == mLastReadChunk) {
|
||||
mLastReadChunk = -1; // invalidate cache
|
||||
}
|
||||
mChunks[mCurrentChunk] = aInputChunk;
|
||||
}
|
||||
|
||||
void
|
||||
DelayBuffer::Read(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
|
||||
AudioChunk* aOutputChunk,
|
||||
ChannelInterpretation aChannelInterpretation)
|
||||
{
|
||||
int chunkCount = mChunks.Length();
|
||||
if (!chunkCount) {
|
||||
aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the maximum number of contributing channels to determine the output
|
||||
// channel count that retains all signal information. Buffered blocks will
|
||||
// be upmixed if necessary.
|
||||
//
|
||||
// First find the range of "delay" offsets backwards from the current
|
||||
// position. Note that these may be negative for frames that are after the
|
||||
// current position (including i).
|
||||
double minDelay = aPerFrameDelays[0];
|
||||
double maxDelay = minDelay;
|
||||
for (unsigned i = 1; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
minDelay = std::min(minDelay, aPerFrameDelays[i] - i);
|
||||
maxDelay = std::max(maxDelay, aPerFrameDelays[i] - i);
|
||||
}
|
||||
|
||||
// Now find the chunks touched by this range and check their channel counts.
|
||||
int oldestChunk = ChunkForDelay(int(maxDelay) + 1);
|
||||
int youngestChunk = ChunkForDelay(minDelay);
|
||||
|
||||
uint32_t channelCount = 0;
|
||||
for (int i = oldestChunk; true; i = (i + 1) % chunkCount) {
|
||||
channelCount = GetAudioChannelsSuperset(channelCount,
|
||||
mChunks[i].ChannelCount());
|
||||
if (i == youngestChunk) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (channelCount) {
|
||||
AllocateAudioBlock(channelCount, aOutputChunk);
|
||||
ReadChannels(aPerFrameDelays, aOutputChunk,
|
||||
0, channelCount, aChannelInterpretation);
|
||||
} else {
|
||||
aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
// Remember currentDelayFrames for the next ProcessBlock call
|
||||
mCurrentDelay = aPerFrameDelays[WEBAUDIO_BLOCK_SIZE - 1];
|
||||
}
|
||||
|
||||
void
|
||||
DelayBuffer::ReadChannel(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
|
||||
const AudioChunk* aOutputChunk, uint32_t aChannel,
|
||||
ChannelInterpretation aChannelInterpretation)
|
||||
{
|
||||
if (!mChunks.Length()) {
|
||||
float* outputChannel = static_cast<float*>
|
||||
(const_cast<void*>(aOutputChunk->mChannelData[aChannel]));
|
||||
PodZero(outputChannel, WEBAUDIO_BLOCK_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
ReadChannels(aPerFrameDelays, aOutputChunk,
|
||||
aChannel, 1, aChannelInterpretation);
|
||||
}
|
||||
|
||||
void
|
||||
DelayBuffer::ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
|
||||
const AudioChunk* aOutputChunk,
|
||||
uint32_t aFirstChannel, uint32_t aNumChannelsToRead,
|
||||
ChannelInterpretation aChannelInterpretation)
|
||||
{
|
||||
uint32_t totalChannelCount = aOutputChunk->mChannelData.Length();
|
||||
uint32_t readChannelsEnd = aFirstChannel + aNumChannelsToRead;
|
||||
MOZ_ASSERT(readChannelsEnd <= totalChannelCount);
|
||||
|
||||
if (mUpmixChannels.Length() != totalChannelCount) {
|
||||
mLastReadChunk = -1; // invalidate cache
|
||||
}
|
||||
|
||||
float* const* outputChannels = reinterpret_cast<float* const*>
|
||||
(const_cast<void* const*>(aOutputChunk->mChannelData.Elements()));
|
||||
for (uint32_t channel = aFirstChannel;
|
||||
channel < readChannelsEnd; ++channel) {
|
||||
PodZero(outputChannels[channel], WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
double currentDelay = aPerFrameDelays[i];
|
||||
MOZ_ASSERT(currentDelay >= 0.0);
|
||||
MOZ_ASSERT(currentDelay <= static_cast<double>(mMaxDelayTicks));
|
||||
|
||||
// Interpolate two input frames in case the read position does not match
|
||||
// an integer index.
|
||||
// Use the larger delay, for the older frame, first, as this is more
|
||||
// likely to use the cached upmixed channel arrays.
|
||||
int floorDelay = int(currentDelay);
|
||||
double interpolationFactor = currentDelay - floorDelay;
|
||||
int positions[2];
|
||||
positions[1] = PositionForDelay(floorDelay) + i;
|
||||
positions[0] = positions[1] - 1;
|
||||
|
||||
for (unsigned tick = 0; tick < ArrayLength(positions); ++tick) {
|
||||
int readChunk = ChunkForPosition(positions[tick]);
|
||||
// mVolume is not set on default initialized chunks so handle null
|
||||
// chunks specially.
|
||||
if (!mChunks[readChunk].IsNull()) {
|
||||
int readOffset = OffsetForPosition(positions[tick]);
|
||||
UpdateUpmixChannels(readChunk, totalChannelCount,
|
||||
aChannelInterpretation);
|
||||
double multiplier = interpolationFactor * mChunks[readChunk].mVolume;
|
||||
for (uint32_t channel = aFirstChannel;
|
||||
channel < readChannelsEnd; ++channel) {
|
||||
outputChannels[channel][i] += multiplier *
|
||||
static_cast<const float*>(mUpmixChannels[channel])[readOffset];
|
||||
}
|
||||
}
|
||||
|
||||
interpolationFactor = 1.0 - interpolationFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DelayBuffer::Read(double aDelayTicks, AudioChunk* aOutputChunk,
|
||||
ChannelInterpretation aChannelInterpretation)
|
||||
{
|
||||
const bool firstTime = mCurrentDelay < 0.0;
|
||||
double currentDelay = firstTime ? aDelayTicks : mCurrentDelay;
|
||||
|
||||
double computedDelay[WEBAUDIO_BLOCK_SIZE];
|
||||
|
||||
for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
// If the value has changed, smoothly approach it
|
||||
currentDelay += (aDelayTicks - currentDelay) * mSmoothingRate;
|
||||
computedDelay[i] = currentDelay;
|
||||
}
|
||||
|
||||
Read(computedDelay, aOutputChunk, aChannelInterpretation);
|
||||
}
|
||||
|
||||
bool
|
||||
DelayBuffer::EnsureBuffer()
|
||||
{
|
||||
if (mChunks.Length() == 0) {
|
||||
// The length of the buffer is at least one block greater than the maximum
|
||||
// delay so that writing an input block does not overwrite the block that
|
||||
// would subsequently be read at maximum delay. Also round up to the next
|
||||
// block size, so that no block of writes will need to wrap.
|
||||
const int chunkCount = (mMaxDelayTicks + 2 * WEBAUDIO_BLOCK_SIZE - 1) >>
|
||||
WEBAUDIO_BLOCK_SIZE_BITS;
|
||||
if (!mChunks.SetLength(chunkCount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mLastReadChunk = -1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
DelayBuffer::PositionForDelay(int aDelay) {
|
||||
// Adding mChunks.Length() keeps integers positive for defined and
|
||||
// appropriate bitshift, remainder, and bitwise operations.
|
||||
return ((mCurrentChunk + mChunks.Length()) * WEBAUDIO_BLOCK_SIZE) - aDelay;
|
||||
}
|
||||
|
||||
int
|
||||
DelayBuffer::ChunkForPosition(int aPosition)
|
||||
{
|
||||
MOZ_ASSERT(aPosition >= 0);
|
||||
return (aPosition >> WEBAUDIO_BLOCK_SIZE_BITS) % mChunks.Length();
|
||||
}
|
||||
|
||||
int
|
||||
DelayBuffer::OffsetForPosition(int aPosition)
|
||||
{
|
||||
MOZ_ASSERT(aPosition >= 0);
|
||||
return aPosition & (WEBAUDIO_BLOCK_SIZE - 1);
|
||||
}
|
||||
|
||||
int
|
||||
DelayBuffer::ChunkForDelay(int aDelay)
|
||||
{
|
||||
return ChunkForPosition(PositionForDelay(aDelay));
|
||||
}
|
||||
|
||||
void
|
||||
DelayBuffer::UpdateUpmixChannels(int aNewReadChunk, uint32_t aChannelCount,
|
||||
ChannelInterpretation aChannelInterpretation)
|
||||
{
|
||||
if (aNewReadChunk == mLastReadChunk) {
|
||||
MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount);
|
||||
return;
|
||||
}
|
||||
|
||||
static const float silenceChannel[WEBAUDIO_BLOCK_SIZE] = {};
|
||||
|
||||
mLastReadChunk = aNewReadChunk;
|
||||
// Missing assignment operator is bug 976927
|
||||
mUpmixChannels.ReplaceElementsAt(0, mUpmixChannels.Length(),
|
||||
mChunks[aNewReadChunk].mChannelData);
|
||||
MOZ_ASSERT(mUpmixChannels.Length() <= aChannelCount);
|
||||
if (mUpmixChannels.Length() < aChannelCount) {
|
||||
if (aChannelInterpretation == ChannelInterpretation::Speakers) {
|
||||
AudioChannelsUpMix(&mUpmixChannels, aChannelCount, silenceChannel);
|
||||
MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount,
|
||||
"We called GetAudioChannelsSuperset to avoid this");
|
||||
} else {
|
||||
// Fill up the remaining channels with zeros
|
||||
for (uint32_t channel = mUpmixChannels.Length();
|
||||
channel < aChannelCount; ++channel) {
|
||||
mUpmixChannels.AppendElement(silenceChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // mozilla
|
96
content/media/webaudio/DelayBuffer.h
Normal file
96
content/media/webaudio/DelayBuffer.h
Normal file
@ -0,0 +1,96 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 DelayBuffer_h_
|
||||
#define DelayBuffer_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "AudioSegment.h"
|
||||
#include "mozilla/dom/AudioNodeBinding.h" // for ChannelInterpretation
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class DelayBuffer {
|
||||
typedef dom::ChannelInterpretation ChannelInterpretation;
|
||||
|
||||
public:
|
||||
// See WebAudioUtils::ComputeSmoothingRate() for frame to frame exponential
|
||||
// |smoothingRate| multiplier.
|
||||
DelayBuffer(int aMaxDelayTicks, double aSmoothingRate)
|
||||
: mSmoothingRate(aSmoothingRate)
|
||||
, mCurrentDelay(-1.0)
|
||||
, mMaxDelayTicks(aMaxDelayTicks)
|
||||
, mCurrentChunk(0)
|
||||
// mLastReadChunk is initialized in EnsureBuffer
|
||||
{
|
||||
}
|
||||
|
||||
// Write a WEBAUDIO_BLOCK_SIZE block for aChannelCount channels.
|
||||
void Write(const AudioChunk& aInputChunk);
|
||||
|
||||
// Read a block with an array of delays, in ticks, for each sample frame.
|
||||
// Each delay must be > 0 and < MaxDelayTicks().
|
||||
void Read(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
|
||||
AudioChunk* aOutputChunk,
|
||||
ChannelInterpretation aChannelInterpretation);
|
||||
// Read a block with a constant delay, which will be smoothed with the
|
||||
// previous delay. The delay must be > 0 and < MaxDelayTicks().
|
||||
void Read(double aDelayTicks, AudioChunk* aOutputChunk,
|
||||
ChannelInterpretation aChannelInterpretation);
|
||||
|
||||
// Read into one of the channels of aOutputChunk, given an array of
|
||||
// delays in ticks. This is useful when delays are different on different
|
||||
// channels. aOutputChunk must have already been allocated with at least as
|
||||
// many channels as were in any of the blocks passed to Write().
|
||||
void ReadChannel(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
|
||||
const AudioChunk* aOutputChunk, uint32_t aChannel,
|
||||
ChannelInterpretation aChannelInterpretation);
|
||||
|
||||
// Advance the buffer pointer
|
||||
void NextBlock()
|
||||
{
|
||||
mCurrentChunk = (mCurrentChunk + 1) % mChunks.Length();
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
mChunks.Clear();
|
||||
mCurrentDelay = -1.0;
|
||||
};
|
||||
|
||||
int MaxDelayTicks() const { return mMaxDelayTicks; }
|
||||
|
||||
private:
|
||||
void ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
|
||||
const AudioChunk* aOutputChunk,
|
||||
uint32_t aFirstChannel, uint32_t aNumChannelsToRead,
|
||||
ChannelInterpretation aChannelInterpretation);
|
||||
bool EnsureBuffer();
|
||||
int PositionForDelay(int aDelay);
|
||||
int ChunkForPosition(int aPosition);
|
||||
int OffsetForPosition(int aPosition);
|
||||
int ChunkForDelay(int aDelay);
|
||||
void UpdateUpmixChannels(int aNewReadChunk, uint32_t channelCount,
|
||||
ChannelInterpretation aChannelInterpretation);
|
||||
|
||||
// Circular buffer for capturing delayed samples.
|
||||
FallibleTArray<AudioChunk> mChunks;
|
||||
// Cache upmixed channel arrays.
|
||||
nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> mUpmixChannels;
|
||||
double mSmoothingRate;
|
||||
// Current delay, in fractional ticks
|
||||
double mCurrentDelay;
|
||||
// Maximum delay, in ticks
|
||||
int mMaxDelayTicks;
|
||||
// The current position in the circular buffer. The next write will be to
|
||||
// this chunk, and the next read may begin before this chunk.
|
||||
int mCurrentChunk;
|
||||
// The chunk owning the pointers in mUpmixChannels
|
||||
int mLastReadChunk;
|
||||
};
|
||||
|
||||
} // mozilla
|
||||
|
||||
#endif // DelayBuffer_h_
|
@ -10,7 +10,7 @@
|
||||
#include "AudioNodeStream.h"
|
||||
#include "AudioDestinationNode.h"
|
||||
#include "WebAudioUtils.h"
|
||||
#include "DelayProcessor.h"
|
||||
#include "DelayBuffer.h"
|
||||
#include "PlayingRefChangeHandler.h"
|
||||
|
||||
namespace mozilla {
|
||||
@ -30,16 +30,17 @@ class DelayNodeEngine : public AudioNodeEngine
|
||||
typedef PlayingRefChangeHandler PlayingRefChanged;
|
||||
public:
|
||||
DelayNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
|
||||
int aMaxDelayFrames)
|
||||
int aMaxDelayTicks)
|
||||
: AudioNodeEngine(aNode)
|
||||
, mSource(nullptr)
|
||||
, mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
|
||||
// Keep the default value in sync with the default value in DelayNode::DelayNode.
|
||||
, mDelay(0.f)
|
||||
// Use a smoothing range of 20ms
|
||||
, mProcessor(aMaxDelayFrames,
|
||||
WebAudioUtils::ComputeSmoothingRate(0.02,
|
||||
mDestination->SampleRate()))
|
||||
, mBuffer(aMaxDelayTicks,
|
||||
WebAudioUtils::ComputeSmoothingRate(0.02,
|
||||
mDestination->SampleRate()))
|
||||
, mLastOutputPosition(-1)
|
||||
, mLeftOverData(INT32_MIN)
|
||||
{
|
||||
}
|
||||
@ -72,18 +73,14 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(mSource == aStream, "Invalid source stream");
|
||||
MOZ_ASSERT(aStream->SampleRate() == mDestination->SampleRate());
|
||||
|
||||
const uint32_t numChannels = aInput.IsNull() ?
|
||||
mProcessor.BufferChannelCount() :
|
||||
aInput.mChannelData.Length();
|
||||
|
||||
if (!aInput.IsNull()) {
|
||||
if (mLeftOverData <= 0) {
|
||||
nsRefPtr<PlayingRefChanged> refchanged =
|
||||
@ -91,14 +88,14 @@ public:
|
||||
aStream->Graph()->
|
||||
DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
|
||||
}
|
||||
mLeftOverData = mProcessor.MaxDelayFrames();
|
||||
mLeftOverData = mBuffer.MaxDelayTicks();
|
||||
} else if (mLeftOverData > 0) {
|
||||
mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
|
||||
} else {
|
||||
if (mLeftOverData != INT32_MIN) {
|
||||
mLeftOverData = INT32_MIN;
|
||||
// Delete our buffered data now we no longer need it
|
||||
mProcessor.Reset();
|
||||
mBuffer.Reset();
|
||||
|
||||
nsRefPtr<PlayingRefChanged> refchanged =
|
||||
new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
|
||||
@ -109,56 +106,60 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
AllocateAudioBlock(numChannels, aOutput);
|
||||
mBuffer.Write(aInput);
|
||||
|
||||
AudioChunk input = aInput;
|
||||
if (!aInput.IsNull() && aInput.mVolume != 1.0f) {
|
||||
// Pre-multiply the input's volume
|
||||
AllocateAudioBlock(numChannels, &input);
|
||||
for (uint32_t i = 0; i < numChannels; ++i) {
|
||||
const float* src = static_cast<const float*>(aInput.mChannelData[i]);
|
||||
float* dest = static_cast<float*>(const_cast<void*>(input.mChannelData[i]));
|
||||
AudioBlockCopyChannelWithScale(src, aInput.mVolume, dest);
|
||||
}
|
||||
UpdateOutputBlock(aOutput);
|
||||
mBuffer.NextBlock();
|
||||
}
|
||||
|
||||
void UpdateOutputBlock(AudioChunk* aOutput)
|
||||
{
|
||||
TrackTicks tick = mSource->GetCurrentPosition();
|
||||
if (tick == mLastOutputPosition) {
|
||||
return; // mLastChunks is already set on the stream
|
||||
}
|
||||
|
||||
const float* const* inputChannels = input.IsNull() ? nullptr :
|
||||
reinterpret_cast<const float* const*>(input.mChannelData.Elements());
|
||||
float* const* outputChannels = reinterpret_cast<float* const*>
|
||||
(const_cast<void* const*>(aOutput->mChannelData.Elements()));
|
||||
|
||||
|
||||
bool inCycle = aStream->AsProcessedStream()->InCycle();
|
||||
double sampleRate = aStream->SampleRate();
|
||||
mLastOutputPosition = tick;
|
||||
bool inCycle = mSource->AsProcessedStream()->InCycle();
|
||||
double minDelay = inCycle ? static_cast<double>(WEBAUDIO_BLOCK_SIZE) : 0.0;
|
||||
double maxDelay = mBuffer.MaxDelayTicks();
|
||||
double sampleRate = mSource->SampleRate();
|
||||
ChannelInterpretation channelInterpretation =
|
||||
mSource->GetChannelInterpretation();
|
||||
if (mDelay.HasSimpleValue()) {
|
||||
// If this DelayNode is in a cycle, make sure the delay value is at least
|
||||
// one block.
|
||||
float delayFrames = mDelay.GetValue() * sampleRate;
|
||||
float delayFramesClamped = inCycle ? std::max(static_cast<float>(WEBAUDIO_BLOCK_SIZE), delayFrames) :
|
||||
delayFrames;
|
||||
mProcessor.Process(delayFramesClamped, inputChannels, outputChannels,
|
||||
numChannels, WEBAUDIO_BLOCK_SIZE);
|
||||
double delayFrames = mDelay.GetValue() * sampleRate;
|
||||
double delayFramesClamped = clamped(delayFrames, minDelay, maxDelay);
|
||||
mBuffer.Read(delayFramesClamped, aOutput, channelInterpretation);
|
||||
} else {
|
||||
// Compute the delay values for the duration of the input AudioChunk
|
||||
// If this DelayNode is in a cycle, make sure the delay value is at least
|
||||
// one block.
|
||||
double computedDelay[WEBAUDIO_BLOCK_SIZE];
|
||||
TrackTicks tick = aStream->GetCurrentPosition();
|
||||
for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
|
||||
float delayAtTick = mDelay.GetValueAtTime(tick, counter) * sampleRate;
|
||||
float delayAtTickClamped = inCycle ? std::max(static_cast<float>(WEBAUDIO_BLOCK_SIZE), delayAtTick) :
|
||||
delayAtTick;
|
||||
double delayAtTick = mDelay.GetValueAtTime(tick, counter) * sampleRate;
|
||||
double delayAtTickClamped = clamped(delayAtTick, minDelay, maxDelay);
|
||||
computedDelay[counter] = delayAtTickClamped;
|
||||
}
|
||||
mProcessor.Process(computedDelay, inputChannels, outputChannels,
|
||||
numChannels, WEBAUDIO_BLOCK_SIZE);
|
||||
mBuffer.Read(computedDelay, aOutput, channelInterpretation);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceBlockBeforeInput(AudioChunk* aOutput) MOZ_OVERRIDE
|
||||
{
|
||||
if (mLeftOverData <= 0) {
|
||||
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
} else {
|
||||
UpdateOutputBlock(aOutput);
|
||||
}
|
||||
}
|
||||
|
||||
AudioNodeStream* mSource;
|
||||
AudioNodeStream* mDestination;
|
||||
AudioParamTimeline mDelay;
|
||||
DelayProcessor mProcessor;
|
||||
DelayBuffer mBuffer;
|
||||
TrackTicks mLastOutputPosition;
|
||||
// How much data we have in our buffer which needs to be flushed out when our inputs
|
||||
// finish.
|
||||
int32_t mLeftOverData;
|
||||
|
@ -1,127 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 "DelayProcessor.h"
|
||||
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "AudioSegment.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
void
|
||||
DelayProcessor::Process(const double *aPerFrameDelays,
|
||||
const float* const* aInputChannels,
|
||||
float* const* aOutputChannels,
|
||||
int aChannelCount, int aFramesToProcess)
|
||||
{
|
||||
if (!EnsureBuffer(aChannelCount)) {
|
||||
for (int channel = 0; channel < aChannelCount; ++channel) {
|
||||
PodZero(aOutputChannels[channel], aFramesToProcess);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int channel = 0; channel < aChannelCount; ++channel) {
|
||||
double currentDelayFrames = mCurrentDelay;
|
||||
int writeIndex = mWriteIndex;
|
||||
|
||||
float* buffer = mBuffer[channel].Elements();
|
||||
const uint32_t bufferLength = mBuffer[channel].Length();
|
||||
const float* input = aInputChannels ? aInputChannels[channel] : nullptr;
|
||||
float* output = aOutputChannels[channel];
|
||||
|
||||
for (int i = 0; i < aFramesToProcess; ++i) {
|
||||
currentDelayFrames = clamped(aPerFrameDelays[i],
|
||||
0.0, static_cast<double>(mMaxDelayFrames));
|
||||
|
||||
// Write the input sample to the correct location in our buffer
|
||||
buffer[writeIndex] = input ? input[i] : 0.0f;
|
||||
|
||||
// Now, determine the correct read position. We adjust the read position to be
|
||||
// from currentDelayFrames frames in the past. We also interpolate the two input
|
||||
// frames in case the read position does not match an integer index.
|
||||
double readPosition = writeIndex + bufferLength - currentDelayFrames;
|
||||
if (readPosition >= bufferLength) {
|
||||
readPosition -= bufferLength;
|
||||
}
|
||||
MOZ_ASSERT(readPosition >= 0.0, "Why are we reading before the beginning of the buffer?");
|
||||
|
||||
// Here is a the reason why readIndex1 and readIndex will never be out
|
||||
// of bounds. The maximum value for bufferLength is
|
||||
// 180 * AudioContext.samplerate (see AudioContext::CreateDelay). The
|
||||
// maximum value for mCurrentDelay is 180.0, so initially readPosition
|
||||
// cannot be more than bufferLength + a fraction less than 1. Then we
|
||||
// take care of that case by subtracting bufferLength from it if needed.
|
||||
// So, if |bufferLength-readPosition<1.0|, readIndex1 will end up being
|
||||
// zero. If |1.0<=bufferLength-readPosition<2.0|, readIndex1 will be
|
||||
// bufferLength-1 and readIndex2 will be 0.
|
||||
int readIndex1 = int(readPosition);
|
||||
int readIndex2 = (readIndex1 + 1) % bufferLength;
|
||||
double interpolationFactor = readPosition - readIndex1;
|
||||
|
||||
output[i] = (1.0 - interpolationFactor) * buffer[readIndex1] +
|
||||
interpolationFactor * buffer[readIndex2];
|
||||
writeIndex = (writeIndex + 1) % bufferLength;
|
||||
}
|
||||
|
||||
// Remember currentDelayFrames and writeIndex for the next ProduceAudioBlock
|
||||
// call when processing the last channel.
|
||||
if (channel == aChannelCount - 1) {
|
||||
mCurrentDelay = currentDelayFrames;
|
||||
mWriteIndex = writeIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DelayProcessor::Process(double aDelayFrames, const float* const* aInputChannels,
|
||||
float* const* aOutputChannels, int aChannelCount,
|
||||
int aFramesToProcess)
|
||||
{
|
||||
const bool firstTime = !mBuffer.Length();
|
||||
double currentDelay = firstTime ? aDelayFrames : mCurrentDelay;
|
||||
|
||||
nsAutoTArray<double, WEBAUDIO_BLOCK_SIZE> computedDelay;
|
||||
computedDelay.SetLength(aFramesToProcess);
|
||||
|
||||
for (int i = 0; i < aFramesToProcess; ++i) {
|
||||
// If the value has changed, smoothly approach it
|
||||
currentDelay += (aDelayFrames - currentDelay) * mSmoothingRate;
|
||||
computedDelay[i] = currentDelay;
|
||||
}
|
||||
|
||||
Process(computedDelay.Elements(), aInputChannels, aOutputChannels,
|
||||
aChannelCount, aFramesToProcess);
|
||||
}
|
||||
|
||||
bool
|
||||
DelayProcessor::EnsureBuffer(uint32_t aNumberOfChannels)
|
||||
{
|
||||
if (aNumberOfChannels == 0) {
|
||||
return false;
|
||||
}
|
||||
if (mBuffer.Length() == 0) {
|
||||
if (!mBuffer.SetLength(aNumberOfChannels)) {
|
||||
return false;
|
||||
}
|
||||
// The length of the buffer is one greater than the maximum delay so that
|
||||
// writing an input frame does not overwrite the frame that would
|
||||
// subsequently be read at maximum delay.
|
||||
const int numFrames = mMaxDelayFrames + 1;
|
||||
for (uint32_t channel = 0; channel < aNumberOfChannels; ++channel) {
|
||||
if (!mBuffer[channel].SetLength(numFrames)) {
|
||||
return false;
|
||||
}
|
||||
PodZero(mBuffer[channel].Elements(), numFrames);
|
||||
}
|
||||
} else if (mBuffer.Length() != aNumberOfChannels) {
|
||||
// TODO: Handle changes in the channel count
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // mozilla
|
@ -1,60 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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 DelayProcessor_h_
|
||||
#define DelayProcessor_h_
|
||||
|
||||
#include "nsTArray.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class DelayProcessor {
|
||||
public:
|
||||
// See WebAudioUtils::ComputeSmoothingRate() for frame to frame exponential
|
||||
// |smoothingRate| multiplier.
|
||||
DelayProcessor(int aMaxDelayFrames, double aSmoothingRate)
|
||||
: mSmoothingRate(aSmoothingRate)
|
||||
, mCurrentDelay(0.)
|
||||
, mMaxDelayFrames(aMaxDelayFrames)
|
||||
, mWriteIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Process with an array of delays, in frames, for each frame.
|
||||
void Process(const double *aPerFrameDelays,
|
||||
const float* const* aInputChannels,
|
||||
float* const* aOutputChannels,
|
||||
int aChannelCount, int aFramesToProcess);
|
||||
|
||||
// Process with a constant delay, which will be smoothed with the previous
|
||||
// delay.
|
||||
void Process(double aDelayFrames, const float* const* aInputChannels,
|
||||
float* const* aOutputChannels, int aChannelCount,
|
||||
int aFramesToProcess);
|
||||
|
||||
void Reset() { mBuffer.Clear(); };
|
||||
|
||||
int MaxDelayFrames() const { return mMaxDelayFrames; }
|
||||
int BufferChannelCount() const { return mBuffer.Length(); }
|
||||
|
||||
private:
|
||||
bool EnsureBuffer(uint32_t aNumberOfChannels);
|
||||
|
||||
// Circular buffer for capturing delayed samples.
|
||||
AutoFallibleTArray<FallibleTArray<float>, 2> mBuffer;
|
||||
double mSmoothingRate;
|
||||
// Current delay, in fractional frames
|
||||
double mCurrentDelay;
|
||||
// Maximum delay, in frames
|
||||
int mMaxDelayFrames;
|
||||
// Write index for the buffer, to write the frames to the correct index of the buffer
|
||||
// given the current delay.
|
||||
int mWriteIndex;
|
||||
};
|
||||
|
||||
} // mozilla
|
||||
|
||||
#endif // DelayProcessor_h_
|
@ -93,10 +93,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
if (aInput.IsNull()) {
|
||||
// Just output silence
|
||||
|
@ -58,10 +58,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
MOZ_ASSERT(mSource == aStream, "Invalid source stream");
|
||||
|
||||
|
@ -36,10 +36,10 @@ public:
|
||||
MOZ_ASSERT(mOutputStream);
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
*aOutput = aInput;
|
||||
StreamBuffer::Track* track = mOutputStream->EnsureTrack(MEDIA_STREAM_DEST_TRACK_ID,
|
||||
|
@ -11,19 +11,19 @@
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_1(OfflineAudioCompletionEvent, nsDOMEvent,
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED_1(OfflineAudioCompletionEvent, Event,
|
||||
mRenderedBuffer)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OfflineAudioCompletionEvent)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
|
||||
NS_INTERFACE_MAP_END_INHERITING(Event)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(OfflineAudioCompletionEvent, nsDOMEvent)
|
||||
NS_IMPL_RELEASE_INHERITED(OfflineAudioCompletionEvent, nsDOMEvent)
|
||||
NS_IMPL_ADDREF_INHERITED(OfflineAudioCompletionEvent, Event)
|
||||
NS_IMPL_RELEASE_INHERITED(OfflineAudioCompletionEvent, Event)
|
||||
|
||||
OfflineAudioCompletionEvent::OfflineAudioCompletionEvent(AudioContext* aOwner,
|
||||
nsPresContext* aPresContext,
|
||||
WidgetEvent* aEvent)
|
||||
: nsDOMEvent(aOwner, aPresContext, aEvent)
|
||||
: Event(aOwner, aPresContext, aEvent)
|
||||
{
|
||||
SetIsDOMBinding();
|
||||
}
|
||||
|
@ -7,15 +7,15 @@
|
||||
#ifndef OfflineAudioCompletionEvent_h_
|
||||
#define OfflineAudioCompletionEvent_h_
|
||||
|
||||
#include "nsDOMEvent.h"
|
||||
#include "AudioBuffer.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class AudioContext;
|
||||
|
||||
class OfflineAudioCompletionEvent : public nsDOMEvent
|
||||
class OfflineAudioCompletionEvent : public Event
|
||||
{
|
||||
public:
|
||||
OfflineAudioCompletionEvent(AudioContext* aOwner,
|
||||
@ -23,8 +23,8 @@ public:
|
||||
WidgetEvent* aEvent);
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_FORWARD_TO_NSDOMEVENT
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OfflineAudioCompletionEvent, nsDOMEvent)
|
||||
NS_FORWARD_TO_EVENT
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OfflineAudioCompletionEvent, Event)
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
|
||||
|
@ -410,10 +410,10 @@ public:
|
||||
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(mSource == aStream, "Invalid source stream");
|
||||
|
||||
|
@ -133,10 +133,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool *aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool *aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
if (aInput.IsNull()) {
|
||||
// mLeftOverData != INT_MIN means that the panning model was HRTF and a
|
||||
@ -279,8 +279,6 @@ void
|
||||
PannerNodeEngine::HRTFPanningFunction(const AudioChunk& aInput,
|
||||
AudioChunk* aOutput)
|
||||
{
|
||||
int numChannels = aInput.mChannelData.Length();
|
||||
|
||||
// The output of this node is always stereo, no matter what the inputs are.
|
||||
AllocateAudioBlock(2, aOutput);
|
||||
|
||||
@ -289,20 +287,9 @@ PannerNodeEngine::HRTFPanningFunction(const AudioChunk& aInput,
|
||||
|
||||
AudioChunk input = aInput;
|
||||
// Gain is applied before the delay and convolution of the HRTF
|
||||
if (!input.IsNull()) {
|
||||
float gain = ComputeConeGain() * ComputeDistanceGain() * aInput.mVolume;
|
||||
if (gain != 1.0f) {
|
||||
AllocateAudioBlock(numChannels, &input);
|
||||
for (int i = 0; i < numChannels; ++i) {
|
||||
const float* src = static_cast<const float*>(aInput.mChannelData[i]);
|
||||
float* dest =
|
||||
static_cast<float*>(const_cast<void*>(input.mChannelData[i]));
|
||||
AudioBlockCopyChannelWithScale(src, gain, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
input.mVolume *= ComputeConeGain() * ComputeDistanceGain();
|
||||
|
||||
mHRTFPanner->pan(azimuth, elevation, &input, aOutput, WEBAUDIO_BLOCK_SIZE);
|
||||
mHRTFPanner->pan(azimuth, elevation, &input, aOutput);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -239,10 +239,10 @@ public:
|
||||
mSource = aSource;
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished) MOZ_OVERRIDE
|
||||
{
|
||||
MutexAutoLock lock(NodeMutex());
|
||||
|
||||
|
@ -202,10 +202,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
virtual void ProcessBlock(AudioNodeStream* aStream,
|
||||
const AudioChunk& aInput,
|
||||
AudioChunk* aOutput,
|
||||
bool* aFinished)
|
||||
{
|
||||
uint32_t channelCount = aInput.mChannelData.Length();
|
||||
if (!mCurve.Length() || !channelCount) {
|
||||
|
@ -27,11 +27,10 @@
|
||||
|
||||
#include "FFTConvolver.h"
|
||||
#include "HRTFDatabase.h"
|
||||
#include "WebAudioUtils.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace mozilla;
|
||||
using mozilla::dom::WebAudioUtils;
|
||||
using dom::ChannelInterpretation;
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
@ -40,7 +39,7 @@ namespace WebCore {
|
||||
const double MaxDelayTimeSeconds = 0.002;
|
||||
|
||||
const int UninitializedAzimuth = -1;
|
||||
const unsigned RenderingQuantum = 128;
|
||||
const unsigned RenderingQuantum = WEBAUDIO_BLOCK_SIZE;
|
||||
|
||||
HRTFPanner::HRTFPanner(float sampleRate, mozilla::TemporaryRef<HRTFDatabaseLoader> databaseLoader)
|
||||
: m_databaseLoader(databaseLoader)
|
||||
@ -55,10 +54,7 @@ HRTFPanner::HRTFPanner(float sampleRate, mozilla::TemporaryRef<HRTFDatabaseLoade
|
||||
, m_convolverR1(m_convolverL1.fftSize())
|
||||
, m_convolverL2(m_convolverL1.fftSize())
|
||||
, m_convolverR2(m_convolverL1.fftSize())
|
||||
, m_delayLineL(ceilf(MaxDelayTimeSeconds * sampleRate),
|
||||
WebAudioUtils::ComputeSmoothingRate(0.02, sampleRate))
|
||||
, m_delayLineR(ceilf(MaxDelayTimeSeconds * sampleRate),
|
||||
WebAudioUtils::ComputeSmoothingRate(0.02, sampleRate))
|
||||
, m_delayLine(ceilf(MaxDelayTimeSeconds * sampleRate), 1.0)
|
||||
{
|
||||
MOZ_ASSERT(m_databaseLoader);
|
||||
MOZ_COUNT_CTOR(HRTFPanner);
|
||||
@ -86,8 +82,7 @@ void HRTFPanner::reset()
|
||||
m_convolverR1.reset();
|
||||
m_convolverL2.reset();
|
||||
m_convolverR2.reset();
|
||||
m_delayLineL.Reset();
|
||||
m_delayLineR.Reset();
|
||||
m_delayLine.Reset();
|
||||
}
|
||||
|
||||
int HRTFPanner::calculateDesiredAzimuthIndexAndBlend(double azimuth, double& azimuthBlend)
|
||||
@ -115,15 +110,15 @@ int HRTFPanner::calculateDesiredAzimuthIndexAndBlend(double azimuth, double& azi
|
||||
return desiredAzimuthIndex;
|
||||
}
|
||||
|
||||
void HRTFPanner::pan(double desiredAzimuth, double elevation, const AudioChunk* inputBus, AudioChunk* outputBus, TrackTicks framesToProcess)
|
||||
void HRTFPanner::pan(double desiredAzimuth, double elevation, const AudioChunk* inputBus, AudioChunk* outputBus)
|
||||
{
|
||||
unsigned numInputChannels =
|
||||
inputBus->IsNull() ? 0 : inputBus->mChannelData.Length();
|
||||
|
||||
MOZ_ASSERT(numInputChannels <= 2);
|
||||
MOZ_ASSERT(framesToProcess <= inputBus->mDuration);
|
||||
MOZ_ASSERT(inputBus->mDuration == WEBAUDIO_BLOCK_SIZE);
|
||||
|
||||
bool isOutputGood = outputBus && outputBus->mChannelData.Length() == 2 && framesToProcess <= outputBus->mDuration;
|
||||
bool isOutputGood = outputBus && outputBus->mChannelData.Length() == 2 && outputBus->mDuration == WEBAUDIO_BLOCK_SIZE;
|
||||
MOZ_ASSERT(isOutputGood);
|
||||
|
||||
if (!isOutputGood) {
|
||||
@ -151,11 +146,7 @@ void HRTFPanner::pan(double desiredAzimuth, double elevation, const AudioChunk*
|
||||
// Normally, we'll just be dealing with mono sources.
|
||||
// If we have a stereo input, implement stereo panning with left source processed by left HRTF, and right source by right HRTF.
|
||||
|
||||
// Get source and destination pointers.
|
||||
const float* sourceL = numInputChannels > 0 ?
|
||||
static_cast<const float*>(inputBus->mChannelData[0]) : nullptr;
|
||||
const float* sourceR = numInputChannels > 1 ?
|
||||
static_cast<const float*>(inputBus->mChannelData[1]) : sourceL;
|
||||
// Get destination pointers.
|
||||
float* destinationL =
|
||||
static_cast<float*>(const_cast<void*>(outputBus->mChannelData[0]));
|
||||
float* destinationR =
|
||||
@ -197,95 +188,94 @@ void HRTFPanner::pan(double desiredAzimuth, double elevation, const AudioChunk*
|
||||
}
|
||||
}
|
||||
|
||||
// This algorithm currently requires that we process in power-of-two size chunks at least RenderingQuantum.
|
||||
MOZ_ASSERT(framesToProcess && 0 == (framesToProcess & (framesToProcess - 1)));
|
||||
MOZ_ASSERT(framesToProcess >= RenderingQuantum);
|
||||
// Get the HRTFKernels and interpolated delays.
|
||||
HRTFKernel* kernelL1;
|
||||
HRTFKernel* kernelR1;
|
||||
HRTFKernel* kernelL2;
|
||||
HRTFKernel* kernelR2;
|
||||
double frameDelayL1;
|
||||
double frameDelayR1;
|
||||
double frameDelayL2;
|
||||
double frameDelayR2;
|
||||
database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex1, m_elevation1, kernelL1, kernelR1, frameDelayL1, frameDelayR1);
|
||||
database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex2, m_elevation2, kernelL2, kernelR2, frameDelayL2, frameDelayR2);
|
||||
|
||||
const unsigned framesPerSegment = RenderingQuantum;
|
||||
const unsigned numberOfSegments = framesToProcess / framesPerSegment;
|
||||
bool areKernelsGood = kernelL1 && kernelR1 && kernelL2 && kernelR2;
|
||||
MOZ_ASSERT(areKernelsGood);
|
||||
if (!areKernelsGood) {
|
||||
outputBus->SetNull(outputBus->mDuration);
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned segment = 0; segment < numberOfSegments; ++segment) {
|
||||
// Get the HRTFKernels and interpolated delays.
|
||||
HRTFKernel* kernelL1;
|
||||
HRTFKernel* kernelR1;
|
||||
HRTFKernel* kernelL2;
|
||||
HRTFKernel* kernelR2;
|
||||
double frameDelayL1;
|
||||
double frameDelayR1;
|
||||
double frameDelayL2;
|
||||
double frameDelayR2;
|
||||
database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex1, m_elevation1, kernelL1, kernelR1, frameDelayL1, frameDelayR1);
|
||||
database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex2, m_elevation2, kernelL2, kernelR2, frameDelayL2, frameDelayR2);
|
||||
MOZ_ASSERT(frameDelayL1 / sampleRate() < MaxDelayTimeSeconds && frameDelayR1 / sampleRate() < MaxDelayTimeSeconds);
|
||||
MOZ_ASSERT(frameDelayL2 / sampleRate() < MaxDelayTimeSeconds && frameDelayR2 / sampleRate() < MaxDelayTimeSeconds);
|
||||
|
||||
bool areKernelsGood = kernelL1 && kernelR1 && kernelL2 && kernelR2;
|
||||
MOZ_ASSERT(areKernelsGood);
|
||||
if (!areKernelsGood) {
|
||||
outputBus->SetNull(outputBus->mDuration);
|
||||
return;
|
||||
// Crossfade inter-aural delays based on transitions.
|
||||
double frameDelaysL[WEBAUDIO_BLOCK_SIZE];
|
||||
double frameDelaysR[WEBAUDIO_BLOCK_SIZE];
|
||||
{
|
||||
float x = m_crossfadeX;
|
||||
float incr = m_crossfadeIncr;
|
||||
for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
frameDelaysL[i] = (1 - x) * frameDelayL1 + x * frameDelayL2;
|
||||
frameDelaysR[i] = (1 - x) * frameDelayR1 + x * frameDelayR2;
|
||||
x += incr;
|
||||
}
|
||||
}
|
||||
|
||||
// First run through delay lines for inter-aural time difference.
|
||||
m_delayLine.Write(*inputBus);
|
||||
// "Speakers" means a mono input is read into both outputs (with possibly
|
||||
// different delays).
|
||||
m_delayLine.ReadChannel(frameDelaysL, outputBus, 0,
|
||||
ChannelInterpretation::Speakers);
|
||||
m_delayLine.ReadChannel(frameDelaysR, outputBus, 1,
|
||||
ChannelInterpretation::Speakers);
|
||||
m_delayLine.NextBlock();
|
||||
|
||||
bool needsCrossfading = m_crossfadeIncr;
|
||||
|
||||
// Have the convolvers render directly to the final destination if we're not cross-fading.
|
||||
float* convolutionDestinationL1 = needsCrossfading ? m_tempL1.Elements() : destinationL;
|
||||
float* convolutionDestinationR1 = needsCrossfading ? m_tempR1.Elements() : destinationR;
|
||||
float* convolutionDestinationL2 = needsCrossfading ? m_tempL2.Elements() : destinationL;
|
||||
float* convolutionDestinationR2 = needsCrossfading ? m_tempR2.Elements() : destinationR;
|
||||
|
||||
// Now do the convolutions.
|
||||
// Note that we avoid doing convolutions on both sets of convolvers if we're not currently cross-fading.
|
||||
|
||||
if (m_crossfadeSelection == CrossfadeSelection1 || needsCrossfading) {
|
||||
m_convolverL1.process(kernelL1->fftFrame(), destinationL, convolutionDestinationL1, WEBAUDIO_BLOCK_SIZE);
|
||||
m_convolverR1.process(kernelR1->fftFrame(), destinationR, convolutionDestinationR1, WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
if (m_crossfadeSelection == CrossfadeSelection2 || needsCrossfading) {
|
||||
m_convolverL2.process(kernelL2->fftFrame(), destinationL, convolutionDestinationL2, WEBAUDIO_BLOCK_SIZE);
|
||||
m_convolverR2.process(kernelR2->fftFrame(), destinationR, convolutionDestinationR2, WEBAUDIO_BLOCK_SIZE);
|
||||
}
|
||||
|
||||
if (needsCrossfading) {
|
||||
// Apply linear cross-fade.
|
||||
float x = m_crossfadeX;
|
||||
float incr = m_crossfadeIncr;
|
||||
for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
|
||||
destinationL[i] = (1 - x) * convolutionDestinationL1[i] + x * convolutionDestinationL2[i];
|
||||
destinationR[i] = (1 - x) * convolutionDestinationR1[i] + x * convolutionDestinationR2[i];
|
||||
x += incr;
|
||||
}
|
||||
// Update cross-fade value from local.
|
||||
m_crossfadeX = x;
|
||||
|
||||
MOZ_ASSERT(frameDelayL1 / sampleRate() < MaxDelayTimeSeconds && frameDelayR1 / sampleRate() < MaxDelayTimeSeconds);
|
||||
MOZ_ASSERT(frameDelayL2 / sampleRate() < MaxDelayTimeSeconds && frameDelayR2 / sampleRate() < MaxDelayTimeSeconds);
|
||||
|
||||
// Crossfade inter-aural delays based on transitions.
|
||||
double frameDelayL = (1 - m_crossfadeX) * frameDelayL1 + m_crossfadeX * frameDelayL2;
|
||||
double frameDelayR = (1 - m_crossfadeX) * frameDelayR1 + m_crossfadeX * frameDelayR2;
|
||||
|
||||
// Calculate the source and destination pointers for the current segment.
|
||||
unsigned offset = segment * framesPerSegment;
|
||||
const float* segmentSourceL = sourceL ? sourceL + offset : nullptr;
|
||||
const float* segmentSourceR = sourceR ? sourceR + offset : nullptr;
|
||||
float* segmentDestinationL = destinationL + offset;
|
||||
float* segmentDestinationR = destinationR + offset;
|
||||
|
||||
// First run through delay lines for inter-aural time difference.
|
||||
m_delayLineL.Process(frameDelayL, &segmentSourceL, &segmentDestinationL, 1, framesPerSegment);
|
||||
m_delayLineR.Process(frameDelayR, &segmentSourceR, &segmentDestinationR, 1, framesPerSegment);
|
||||
|
||||
bool needsCrossfading = m_crossfadeIncr;
|
||||
|
||||
// Have the convolvers render directly to the final destination if we're not cross-fading.
|
||||
float* convolutionDestinationL1 = needsCrossfading ? m_tempL1.Elements() : segmentDestinationL;
|
||||
float* convolutionDestinationR1 = needsCrossfading ? m_tempR1.Elements() : segmentDestinationR;
|
||||
float* convolutionDestinationL2 = needsCrossfading ? m_tempL2.Elements() : segmentDestinationL;
|
||||
float* convolutionDestinationR2 = needsCrossfading ? m_tempR2.Elements() : segmentDestinationR;
|
||||
|
||||
// Now do the convolutions.
|
||||
// Note that we avoid doing convolutions on both sets of convolvers if we're not currently cross-fading.
|
||||
|
||||
if (m_crossfadeSelection == CrossfadeSelection1 || needsCrossfading) {
|
||||
m_convolverL1.process(kernelL1->fftFrame(), segmentDestinationL, convolutionDestinationL1, framesPerSegment);
|
||||
m_convolverR1.process(kernelR1->fftFrame(), segmentDestinationR, convolutionDestinationR1, framesPerSegment);
|
||||
}
|
||||
|
||||
if (m_crossfadeSelection == CrossfadeSelection2 || needsCrossfading) {
|
||||
m_convolverL2.process(kernelL2->fftFrame(), segmentDestinationL, convolutionDestinationL2, framesPerSegment);
|
||||
m_convolverR2.process(kernelR2->fftFrame(), segmentDestinationR, convolutionDestinationR2, framesPerSegment);
|
||||
}
|
||||
|
||||
if (needsCrossfading) {
|
||||
// Apply linear cross-fade.
|
||||
float x = m_crossfadeX;
|
||||
float incr = m_crossfadeIncr;
|
||||
for (unsigned i = 0; i < framesPerSegment; ++i) {
|
||||
segmentDestinationL[i] = (1 - x) * convolutionDestinationL1[i] + x * convolutionDestinationL2[i];
|
||||
segmentDestinationR[i] = (1 - x) * convolutionDestinationR1[i] + x * convolutionDestinationR2[i];
|
||||
x += incr;
|
||||
}
|
||||
// Update cross-fade value from local.
|
||||
m_crossfadeX = x;
|
||||
|
||||
if (m_crossfadeIncr > 0 && fabs(m_crossfadeX - 1) < m_crossfadeIncr) {
|
||||
// We've fully made the crossfade transition from 1 -> 2.
|
||||
m_crossfadeSelection = CrossfadeSelection2;
|
||||
m_crossfadeX = 1;
|
||||
m_crossfadeIncr = 0;
|
||||
} else if (m_crossfadeIncr < 0 && fabs(m_crossfadeX) < -m_crossfadeIncr) {
|
||||
// We've fully made the crossfade transition from 2 -> 1.
|
||||
m_crossfadeSelection = CrossfadeSelection1;
|
||||
m_crossfadeX = 0;
|
||||
m_crossfadeIncr = 0;
|
||||
}
|
||||
if (m_crossfadeIncr > 0 && fabs(m_crossfadeX - 1) < m_crossfadeIncr) {
|
||||
// We've fully made the crossfade transition from 1 -> 2.
|
||||
m_crossfadeSelection = CrossfadeSelection2;
|
||||
m_crossfadeX = 1;
|
||||
m_crossfadeIncr = 0;
|
||||
} else if (m_crossfadeIncr < 0 && fabs(m_crossfadeX) < -m_crossfadeIncr) {
|
||||
// We've fully made the crossfade transition from 2 -> 1.
|
||||
m_crossfadeSelection = CrossfadeSelection1;
|
||||
m_crossfadeX = 0;
|
||||
m_crossfadeIncr = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -299,7 +289,7 @@ int HRTFPanner::maxTailFrames() const
|
||||
// tailTime of the DelayKernel and the tailTime of the FFTConvolver.
|
||||
// The FFTConvolver has a tail time of fftSize(), including latency of
|
||||
// fftSize()/2.
|
||||
return m_delayLineL.MaxDelayFrames() + fftSize();
|
||||
return m_delayLine.MaxDelayTicks() + fftSize();
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
|
@ -26,7 +26,7 @@
|
||||
#define HRTFPanner_h
|
||||
|
||||
#include "FFTConvolver.h"
|
||||
#include "DelayProcessor.h"
|
||||
#include "DelayBuffer.h"
|
||||
|
||||
namespace mozilla {
|
||||
struct AudioChunk;
|
||||
@ -43,8 +43,8 @@ public:
|
||||
HRTFPanner(float sampleRate, mozilla::TemporaryRef<HRTFDatabaseLoader> databaseLoader);
|
||||
~HRTFPanner();
|
||||
|
||||
// framesToProcess must be a power of 2 and greater than 128
|
||||
void pan(double azimuth, double elevation, const AudioChunk* inputBus, AudioChunk* outputBus, mozilla::TrackTicks framesToProcess);
|
||||
// chunk durations must be 128
|
||||
void pan(double azimuth, double elevation, const AudioChunk* inputBus, AudioChunk* outputBus);
|
||||
void reset();
|
||||
|
||||
size_t fftSize() const { return m_convolverL1.fftSize(); }
|
||||
@ -99,8 +99,7 @@ private:
|
||||
FFTConvolver m_convolverL2;
|
||||
FFTConvolver m_convolverR2;
|
||||
|
||||
mozilla::DelayProcessor m_delayLineL;
|
||||
mozilla::DelayProcessor m_delayLineR;
|
||||
mozilla::DelayBuffer m_delayLine;
|
||||
|
||||
AudioFloatArray m_tempL1;
|
||||
AudioFloatArray m_tempR1;
|
||||
|
@ -62,8 +62,8 @@ UNIFIED_SOURCES += [
|
||||
'ChannelMergerNode.cpp',
|
||||
'ChannelSplitterNode.cpp',
|
||||
'ConvolverNode.cpp',
|
||||
'DelayBuffer.cpp',
|
||||
'DelayNode.cpp',
|
||||
'DelayProcessor.cpp',
|
||||
'DynamicsCompressorNode.cpp',
|
||||
'FFTBlock.cpp',
|
||||
'GainNode.cpp',
|
||||
|
@ -79,6 +79,7 @@ support-files =
|
||||
[test_decodeMultichannel.html]
|
||||
[test_delayNode.html]
|
||||
[test_delayNodeAtMax.html]
|
||||
[test_delayNodeChannelChanges.html]
|
||||
[test_delayNodeCycles.html]
|
||||
[test_delayNodeSmallMaxDelay.html]
|
||||
[test_delayNodeTailIncrease.html]
|
||||
|
@ -0,0 +1,97 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>test DelayNode channel count changes</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="webaudio.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const bufferSize = 4096;
|
||||
|
||||
var ctx;
|
||||
var testDelay;
|
||||
var stereoDelay;
|
||||
var invertor;
|
||||
|
||||
function compareOutputs(callback) {
|
||||
var processor = ctx.createScriptProcessor(bufferSize, 2, 0);
|
||||
testDelay.connect(processor);
|
||||
invertor.connect(processor);
|
||||
processor.onaudioprocess =
|
||||
function(e) {
|
||||
compareBuffers(e.inputBuffer,
|
||||
ctx.createBuffer(2, bufferSize, ctx.sampleRate));
|
||||
e.target.onaudioprocess = null;
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function startTest() {
|
||||
// And a two-channel signal
|
||||
var merger = ctx.createChannelMerger();
|
||||
merger.connect(testDelay);
|
||||
merger.connect(stereoDelay);
|
||||
var oscL = ctx.createOscillator();
|
||||
oscL.connect(merger, 0, 0);
|
||||
oscL.start(0);
|
||||
var oscR = ctx.createOscillator();
|
||||
oscR.type = "sawtooth";
|
||||
oscR.connect(merger, 0, 1);
|
||||
oscR.start(0);
|
||||
|
||||
compareOutputs(
|
||||
function () {
|
||||
// Disconnect the two-channel signal and test again
|
||||
merger.disconnect();
|
||||
compareOutputs(SimpleTest.finish);
|
||||
});
|
||||
}
|
||||
|
||||
function prepareTest() {
|
||||
ctx = new AudioContext();
|
||||
|
||||
// The output of a test delay node with mono and stereo input will be
|
||||
// compared with that of separate mono and stereo delay nodes.
|
||||
const delayTime = 0.3 * bufferSize / ctx.sampleRate;
|
||||
testDelay = ctx.createDelay(delayTime);
|
||||
testDelay.delayTime.value = delayTime;
|
||||
monoDelay = ctx.createDelay(delayTime);
|
||||
monoDelay.delayTime.value = delayTime;
|
||||
stereoDelay = ctx.createDelay(delayTime);
|
||||
stereoDelay.delayTime.value = delayTime;
|
||||
|
||||
// Create a one-channel signal and connect to the delay nodes
|
||||
var monoOsc = ctx.createOscillator();
|
||||
monoOsc.frequency.value = 110;
|
||||
monoOsc.connect(testDelay);
|
||||
monoOsc.connect(monoDelay);
|
||||
monoOsc.start(0);
|
||||
|
||||
// Invert the expected so that mixing with the test will find the difference.
|
||||
invertor = ctx.createGain();
|
||||
invertor.gain.value = -1.0;
|
||||
monoDelay.connect(invertor);
|
||||
stereoDelay.connect(invertor);
|
||||
|
||||
// Start the test after the delay nodes have begun processing.
|
||||
var processor = ctx.createScriptProcessor(bufferSize, 1, 0);
|
||||
processor.connect(ctx.destination);
|
||||
|
||||
processor.onaudioprocess =
|
||||
function(e) {
|
||||
e.target.onaudioprocess = null;
|
||||
processor.disconnect();
|
||||
startTest();
|
||||
};
|
||||
}
|
||||
prepareTest();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -12,6 +12,7 @@
|
||||
#include "nsError.h"
|
||||
#include "nsSVGAnimatedTransformList.h"
|
||||
#include "nsSVGAttrTearoffTable.h"
|
||||
#include "mozilla/DebugOnly.h"
|
||||
|
||||
namespace {
|
||||
const double kRadPerDegree = 2.0 * M_PI / 360.0;
|
||||
|
@ -84,7 +84,7 @@ nsXMLElement::NodeInfoChanged(nsINodeInfo* aOldNodeInfo)
|
||||
const nsAttrValue* attrVal =
|
||||
mAttrsAndChildren.GetAttr(aOldNodeInfo->GetIDAttributeAtom());
|
||||
if (attrVal) {
|
||||
doc->RemoveFromIdTable(this, attrVal->GetAtomValue());
|
||||
RemoveFromIdTable(attrVal->GetAtomValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +104,7 @@ nsXMLElement::NodeInfoChanged(nsINodeInfo* aOldNodeInfo)
|
||||
NS_ASSERTION(attrVal->Type() == nsAttrValue::eAtom,
|
||||
"Should be atom by now");
|
||||
if (doc) {
|
||||
doc->AddToIdTable(this, attrVal->GetAtomValue());
|
||||
AddToIdTable(attrVal->GetAtomValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,28 +133,3 @@ nsXMLElement::ParseAttribute(int32_t aNamespaceID,
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsXMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||
nsIContent* aBindingParent,
|
||||
bool aCompileEventHandlers)
|
||||
{
|
||||
nsresult rv = Element::BindToTree(aDocument, aParent,
|
||||
aBindingParent,
|
||||
aCompileEventHandlers);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (aDocument && HasID() && !GetBindingParent()) {
|
||||
aDocument->AddToIdTable(this, DoGetID());
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsXMLElement::UnbindFromTree(bool aDeep, bool aNullParent)
|
||||
{
|
||||
RemoveFromIdTable();
|
||||
|
||||
return Element::UnbindFromTree(aDeep, aNullParent);
|
||||
}
|
||||
|
@ -37,10 +37,6 @@ public:
|
||||
// nsIContent interface methods
|
||||
virtual nsIAtom *GetIDAttributeName() const MOZ_OVERRIDE;
|
||||
virtual nsIAtom* DoGetID() const MOZ_OVERRIDE;
|
||||
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||
nsIContent* aBindingParent,
|
||||
bool aCompileEventHandlers) MOZ_OVERRIDE;
|
||||
virtual void UnbindFromTree(bool aDeep, bool aNullParent) MOZ_OVERRIDE;
|
||||
virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
|
||||
bool aNotify) MOZ_OVERRIDE;
|
||||
virtual bool ParseAttribute(int32_t aNamespaceID,
|
||||
|
@ -58,7 +58,7 @@
|
||||
#include "nsIXULTemplateBuilder.h"
|
||||
#include "nsLayoutCID.h"
|
||||
#include "nsContentCID.h"
|
||||
#include "nsDOMEvent.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "nsRDFCID.h"
|
||||
#include "nsStyleConsts.h"
|
||||
#include "nsXPIDLString.h"
|
||||
@ -1185,7 +1185,7 @@ nsXULElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
|
||||
// handling.
|
||||
nsCOMPtr<nsIDOMEvent> domEvent = aVisitor.mDOMEvent;
|
||||
while (domEvent) {
|
||||
nsDOMEvent* event = domEvent->InternalDOMEvent();
|
||||
Event* event = domEvent->InternalDOMEvent();
|
||||
NS_ENSURE_STATE(!SameCOMIdentity(event->GetOriginalTarget(),
|
||||
commandContent));
|
||||
nsCOMPtr<nsIDOMXULCommandEvent> commandEvent =
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "nsHTMLReflowState.h"
|
||||
#include "nsIObjectLoadingContent.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
#include "mozilla/dom/FragmentOrElement.h"
|
||||
|
||||
@ -43,7 +44,6 @@
|
||||
#include "nsViewManager.h"
|
||||
#include "nsError.h"
|
||||
#include "nsMenuFrame.h"
|
||||
#include "nsDOMEvent.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
@ -1720,6 +1720,7 @@ XULDocument::AddElementToDocumentPre(Element* aElement)
|
||||
// elements from prototypes.
|
||||
nsIAtom* id = aElement->GetID();
|
||||
if (id) {
|
||||
// FIXME: Shouldn't BindToTree take care of this?
|
||||
nsAutoScriptBlocker scriptBlocker;
|
||||
AddToIdTable(aElement, id);
|
||||
}
|
||||
@ -1853,6 +1854,7 @@ XULDocument::RemoveSubtreeFromDocument(nsIContent* aContent)
|
||||
RemoveElementFromRefMap(aElement);
|
||||
nsIAtom* id = aElement->GetID();
|
||||
if (id) {
|
||||
// FIXME: Shouldn't UnbindFromTree take care of this?
|
||||
nsAutoScriptBlocker scriptBlocker;
|
||||
RemoveFromIdTable(aElement, id);
|
||||
}
|
||||
|
@ -1090,6 +1090,10 @@ Console::ProcessArguments(JSContext* aCx,
|
||||
}
|
||||
|
||||
++start;
|
||||
if (start == end) {
|
||||
output.Append('%');
|
||||
break;
|
||||
}
|
||||
|
||||
if (*start == '%') {
|
||||
output.Append(*start);
|
||||
@ -1111,13 +1115,23 @@ Console::ProcessArguments(JSContext* aCx,
|
||||
integer = integer * 10 + *start - '0';
|
||||
tmp.Append(*start);
|
||||
++start;
|
||||
} while (*start >= '0' && *start <= '9');
|
||||
} while (*start >= '0' && *start <= '9' && start != end);
|
||||
}
|
||||
|
||||
if (start == end) {
|
||||
output.Append(tmp);
|
||||
break;
|
||||
}
|
||||
|
||||
if (*start == '.') {
|
||||
tmp.Append(*start);
|
||||
++start;
|
||||
|
||||
if (start == end) {
|
||||
output.Append(tmp);
|
||||
break;
|
||||
}
|
||||
|
||||
// '.' must be followed by a number.
|
||||
if (*start < '0' || *start > '9') {
|
||||
output.Append(tmp);
|
||||
@ -1130,7 +1144,12 @@ Console::ProcessArguments(JSContext* aCx,
|
||||
mantissa = mantissa * 10 + *start - '0';
|
||||
tmp.Append(*start);
|
||||
++start;
|
||||
} while (*start >= '0' && *start <= '9');
|
||||
} while (*start >= '0' && *start <= '9' && start != end);
|
||||
|
||||
if (start == end) {
|
||||
output.Append(tmp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char ch = *start;
|
||||
|
@ -4,6 +4,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "MessagePort.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/MessageChannel.h"
|
||||
#include "mozilla/dom/MessagePortBinding.h"
|
||||
#include "mozilla/dom/StructuredCloneTags.h"
|
||||
@ -11,7 +12,6 @@
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsEventDispatcher.h"
|
||||
#include "nsPresContext.h"
|
||||
#include "nsDOMEvent.h"
|
||||
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIDOMFile.h"
|
||||
@ -55,24 +55,16 @@ class PostMessageRunnable : public nsRunnable
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
PostMessageRunnable()
|
||||
: mMessage(nullptr)
|
||||
, mMessageLen(0)
|
||||
{
|
||||
}
|
||||
|
||||
~PostMessageRunnable()
|
||||
{
|
||||
// Ensure that the buffer is freed
|
||||
if (mMessage) {
|
||||
JSAutoStructuredCloneBuffer buffer;
|
||||
buffer.adopt(mMessage, mMessageLen);
|
||||
}
|
||||
}
|
||||
|
||||
void SetJSData(JSAutoStructuredCloneBuffer& aBuffer)
|
||||
JSAutoStructuredCloneBuffer& Buffer()
|
||||
{
|
||||
NS_ASSERTION(!mMessage && mMessageLen == 0, "Don't call twice!");
|
||||
aBuffer.steal(&mMessage, &mMessageLen);
|
||||
return mBuffer;
|
||||
}
|
||||
|
||||
bool StoreISupports(nsISupports* aSupports)
|
||||
@ -89,8 +81,7 @@ class PostMessageRunnable : public nsRunnable
|
||||
|
||||
private:
|
||||
nsRefPtr<MessagePort> mPort;
|
||||
uint64_t* mMessage;
|
||||
size_t mMessageLen;
|
||||
JSAutoStructuredCloneBuffer mBuffer;
|
||||
|
||||
nsTArray<nsCOMPtr<nsISupports> > mSupportsArray;
|
||||
};
|
||||
@ -225,12 +216,6 @@ PostMessageRunnable::Run()
|
||||
{
|
||||
MOZ_ASSERT(mPort);
|
||||
|
||||
// Ensure that the buffer is freed even if we fail to post the message
|
||||
JSAutoStructuredCloneBuffer buffer;
|
||||
buffer.adopt(mMessage, mMessageLen);
|
||||
mMessage = nullptr;
|
||||
mMessageLen = 0;
|
||||
|
||||
// Get the JSContext for the target window
|
||||
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(mPort->GetOwner());
|
||||
NS_ENSURE_STATE(sgo);
|
||||
@ -247,7 +232,7 @@ PostMessageRunnable::Run()
|
||||
scInfo.mEvent = this;
|
||||
scInfo.mPort = mPort;
|
||||
|
||||
if (!buffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) {
|
||||
if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) {
|
||||
return NS_ERROR_DOM_DATA_CLONE_ERR;
|
||||
}
|
||||
}
|
||||
@ -259,7 +244,7 @@ PostMessageRunnable::Run()
|
||||
}
|
||||
|
||||
ErrorResult error;
|
||||
nsRefPtr<nsDOMEvent> event =
|
||||
nsRefPtr<Event> event =
|
||||
doc->CreateEvent(NS_LITERAL_STRING("MessageEvent"), error);
|
||||
if (error.Failed()) {
|
||||
return NS_OK;
|
||||
@ -364,7 +349,6 @@ MessagePort::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
|
||||
|
||||
// We *must* clone the data here, or the JS::Value could be modified
|
||||
// by script
|
||||
JSAutoStructuredCloneBuffer buffer;
|
||||
StructuredCloneInfo scInfo;
|
||||
scInfo.mEvent = event;
|
||||
scInfo.mPort = this;
|
||||
@ -388,14 +372,12 @@ MessagePort::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
|
||||
transferable.setObject(*array);
|
||||
}
|
||||
|
||||
if (!buffer.write(aCx, aMessage, transferable, &kPostMessageCallbacks,
|
||||
&scInfo)) {
|
||||
if (!event->Buffer().write(aCx, aMessage, transferable,
|
||||
&kPostMessageCallbacks, &scInfo)) {
|
||||
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
event->SetJSData(buffer);
|
||||
|
||||
if (!mEntangledPort) {
|
||||
return;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
#include "Connection.h"
|
||||
#include "nsDOMEvent.h"
|
||||
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
|
||||
#include "nsGlobalWindow.h"
|
||||
#ifdef MOZ_B2G_RIL
|
||||
#include "mozilla/dom/IccManager.h"
|
||||
|
@ -54,18 +54,18 @@ DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::Element, mozilla::dom::Element,\
|
||||
/* If this is ever removed, the IID for EventTarget can go away */ \
|
||||
DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::EventTarget, \
|
||||
mozilla::dom::EventTarget, 2, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsDOMEvent, nsIDOMEvent, 3, _extra) \
|
||||
DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::Event, nsIDOMEvent, 3, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsIDocument, nsIDocument, 4, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsDocument, nsIDocument, 5, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsGenericHTMLElement, nsIContent, 6, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsHTMLDocument, nsIDocument, 7, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsStyledElement, nsStyledElement, 8, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsSVGElement, nsIContent, 9, _extra) \
|
||||
/* NOTE: When removing the casts below, remove the nsDOMEventBase class */ \
|
||||
/* NOTE: When removing the casts below, remove the dom::EventBase class */ \
|
||||
DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::MouseEvent, \
|
||||
nsDOMEventBase, 10, _extra) \
|
||||
mozilla::dom::EventBase, 10, _extra) \
|
||||
DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::UIEvent, \
|
||||
nsDOMEventBase, 11, _extra) \
|
||||
mozilla::dom::EventBase, 11, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsGlobalWindow, nsIDOMEventTarget, 12, _extra)
|
||||
|
||||
// Make sure all classes mentioned in DOMCI_CASTABLE_INTERFACES
|
||||
@ -78,6 +78,7 @@ DOMCI_CASTABLE_INTERFACES(unused)
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class Element;
|
||||
class Event;
|
||||
class EventTarget;
|
||||
class MouseEvent;
|
||||
class UIEvent;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user