import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1230352 - Update to Oculus SDK 0.8.0,r=vlad (ec5404763a)
- Bug 1237689 - Enable Oculus hardware latency tester r=daoshengmu (a914fb5c78)
- Bug 1248757 Use string ::Assign() instead of Adopt() when reading blobs as strings. r=asuth (65e361797f)
- Bug 1248757 followup - Release blob after assignment in DoGetBlobAsString on CLOSED TREE. (8f5d86a27a)
- Bug 1240583 - Odin: record and assert whether a function is defined yet (r=bbouvier) (693c0be6fe)
- Bug 1240583 - Odin: store code-range index instead of entry offset in ModuleGenerator (r=bbouvier) (ac14028824)
- Bug 1240583 - add thunkWithPatch/patchThunk (r=jandem) (9b53ac4d5b)
- Bug 1240583 - Odin: fix long jumps/calls on ARM for large modules (r=bbouvier) (3f7bacc9cf)
- Bug 1240583 - Odin: add MacroAssembler::repatchThunk (r=bbouvier) (1336b1492d)
- Bug 1037483 adopt microformats-shiv for microformats v2 support, r=tantek (c08afa618d)
- Bug 1224766 - forward callID to disambiguate multiple gUM requests from same window. r=jesup,smaug (547eafdbaa)
- Bug 1201393. Remove irrelevant ProcessedMediaStream for nsSpeechTask. r=eitan (f17f8e6821)
- Bug 1240583: Fix non-unified build for fuzzers; r=luke (cc7ea34899)
- Bug 1212366 - Part 1. Don't call SetAudioOutputVolume if stream is destroyed. r=roc (af1106491c)
- Bug 1212366 - Part 2. Don't release SourceMediaStream until StreamListener is called. r=roc (08cf9cf62f)
- Bug 1220320 - implement the nsSupportsWeakReference. r=baku (0cfd32c83a)
- Bug 1225347 - Apply audio setting to volume parameter of Speak(). r=eeejay (fc6dbd938c)
- Bug 1228564 - part 1 : revert the changeset of bug 1190040. r=baku. (921a0f7383)
- Bug 1195051 - Part 3: Test changes; r=padenot (438a73a408)
- Bug 1195051 - Part 4: Fix a null pointer crash happening after the destination node gets CCed (90fd50c8ac)
- Bug 1228564 - part 2 : check audio capturing when the agent is registered/unregistered. r=baku. (14f473625b)
- Bug 1228564 - Follow-up to fix static analysis build bustage. r=me (295d61a1b6)
- Bug 1247846 - Odin: switch CallIndirect to wasm binary encoding (r=bbouvier) (5997b4c59b)
- Bug 1247846 - Odin: refactor ModuleGenerator::finish (r=bbouvier) (a71293c284)
- Bug 1247846 - Odin: refactor error stub generation (r=bbouvier) (5f9f98b215)
This commit is contained in:
2023-09-14 09:37:33 +08:00
parent 08625c2b16
commit 9e41bd2207
282 changed files with 32575 additions and 998 deletions
+2
View File
@@ -41,6 +41,8 @@ reAfterArg = "(?=[,)])"
reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
def get_normalized_signatures(signature, fileAnnot = None):
# Remove static
signature = signature.replace('static', '')
# Remove semicolon.
signature = signature.replace(';', ' ')
# Normalize spaces.
+16 -8
View File
@@ -41,7 +41,6 @@ AudioChannelAgent::AudioChannelAgent()
: mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR)
, mInnerWindowID(0)
, mIsRegToService(false)
, mNotifyPlayback(false)
{
}
@@ -208,8 +207,7 @@ AudioChannelAgent::InitInternal(nsIDOMWindow* aWindow, int32_t aChannelType,
return NS_OK;
}
NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(uint32_t aNotifyPlayback,
float *aVolume,
NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(float *aVolume,
bool* aMuted)
{
MOZ_ASSERT(aVolume);
@@ -228,7 +226,7 @@ NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(uint32_t aNotifyPlayback,
return NS_ERROR_FAILURE;
}
service->RegisterAudioChannelAgent(this, aNotifyPlayback,
service->RegisterAudioChannelAgent(this,
static_cast<AudioChannel>(mAudioChannelType));
service->GetState(mWindow, mAudioChannelType, aVolume, aMuted);
@@ -237,7 +235,6 @@ NS_IMETHODIMP AudioChannelAgent::NotifyStartedPlaying(uint32_t aNotifyPlayback,
("AudioChannelAgent, NotifyStartedPlaying, this = %p, mute = %d, "
"volume = %f\n", this, *aMuted, *aVolume));
mNotifyPlayback = aNotifyPlayback;
mIsRegToService = true;
return NS_OK;
}
@@ -254,7 +251,7 @@ NS_IMETHODIMP AudioChannelAgent::NotifyStoppedPlaying()
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
if (service) {
service->UnregisterAudioChannelAgent(this, mNotifyPlayback);
service->UnregisterAudioChannelAgent(this);
}
mIsRegToService = false;
@@ -300,8 +297,15 @@ AudioChannelAgent::WindowID() const
return mWindow ? mWindow->WindowID() : 0;
}
uint64_t
AudioChannelAgent::InnerWindowID() const
{
return mInnerWindowID;
}
void
AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID)
AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID,
bool aCapture)
{
if (aInnerWindowID != mInnerWindowID) {
return;
@@ -312,5 +316,9 @@ AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID)
return;
}
callback->WindowAudioCaptureChanged();
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
("AudioChannelAgent, WindowAudioCaptureChanged, this = %p, "
"capture = %d\n", this, aCapture));
callback->WindowAudioCaptureChanged(aCapture);
}
+2 -2
View File
@@ -34,7 +34,7 @@ public:
AudioChannelAgent();
void WindowVolumeChanged();
void WindowAudioCaptureChanged(uint64_t aInnerWindowID);
void WindowAudioCaptureChanged(uint64_t aInnerWindowID, bool aCapture);
nsPIDOMWindow* Window() const
{
@@ -42,6 +42,7 @@ public:
}
uint64_t WindowID() const;
uint64_t InnerWindowID() const;
private:
virtual ~AudioChannelAgent();
@@ -66,7 +67,6 @@ private:
int32_t mAudioChannelType;
uint64_t mInnerWindowID;
bool mIsRegToService;
bool mNotifyPlayback;
};
} // namespace dom
+31 -14
View File
@@ -268,7 +268,6 @@ AudioChannelService::~AudioChannelService()
void
AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
uint32_t aNotifyPlayback,
AudioChannel aChannel)
{
uint64_t windowID = aAgent->WindowID();
@@ -289,19 +288,24 @@ AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
}
// If this is the first agent for this window, we must notify the observers.
if (aNotifyPlayback == nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY &&
winData->mAgents.Length() == 1) {
if (winData->mAgents.Length() == 1) {
RefPtr<MediaPlaybackRunnable> runnable =
new MediaPlaybackRunnable(aAgent->Window(), true /* active */);
NS_DispatchToCurrentThread(runnable);
}
// If the window has already been captured, the agent of that window should
// also be captured.
if (winData->mIsAudioCaptured) {
aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(),
winData->mIsAudioCaptured);
}
MaybeSendStatusUpdate();
}
void
AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent,
uint32_t aNotifyPlayback)
AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
{
AudioChannelWindow* winData = GetWindowData(aAgent->WindowID());
if (!winData) {
@@ -333,13 +337,17 @@ AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent,
#endif
// If this is the last agent for this window, we must notify the observers.
if (aNotifyPlayback == nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY &&
winData->mAgents.IsEmpty()) {
if (winData->mAgents.IsEmpty()) {
RefPtr<MediaPlaybackRunnable> runnable =
new MediaPlaybackRunnable(aAgent->Window(), false /* active */);
NS_DispatchToCurrentThread(runnable);
}
// No need to capture non-audible object.
if (winData->mIsAudioCaptured) {
aAgent->WindowAudioCaptureChanged(aAgent->InnerWindowID(), false);
}
MaybeSendStatusUpdate();
}
@@ -627,12 +635,18 @@ AudioChannelService::RefreshAgentsVolume(nsPIDOMWindow* aWindow)
}
void
AudioChannelService::RefreshAgentsCapture(nsPIDOMWindow* aWindow,
uint64_t aInnerWindowID)
AudioChannelService::SetWindowAudioCaptured(nsPIDOMWindow* aWindow,
uint64_t aInnerWindowID,
bool aCapture)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aWindow->IsOuterWindow());
MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
("AudioChannelService, SetWindowAudioCaptured, window = %p, "
"aCapture = %d\n", aWindow, aCapture));
nsCOMPtr<nsPIDOMWindow> topWindow = aWindow->GetScriptableTop();
if (!topWindow) {
return;
@@ -649,10 +663,13 @@ AudioChannelService::RefreshAgentsCapture(nsPIDOMWindow* aWindow,
return;
}
nsTObserverArray<AudioChannelAgent*>::ForwardIterator
iter(winData->mAgents);
while (iter.HasMore()) {
iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID);
if (aCapture != winData->mIsAudioCaptured) {
winData->mIsAudioCaptured = aCapture;
nsTObserverArray<AudioChannelAgent*>::ForwardIterator
iter(winData->mAgents);
while (iter.HasMore()) {
iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID, aCapture);
}
}
}
@@ -790,7 +807,7 @@ AudioChannelService::SetAudioChannelVolume(nsPIDOMWindow* aWindow,
MOZ_LOG(GetAudioChannelLog(), LogLevel::Debug,
("AudioChannelService, SetAudioChannelVolume, window = %p, type = %d, "
"volume = %d\n", aWindow, aAudioChannel, aVolume));
"volume = %f\n", aWindow, aAudioChannel, aVolume));
AudioChannelWindow* winData = GetOrCreateWindowData(aWindow);
winData->mChannels[(uint32_t)aAudioChannel].mVolume = aVolume;
+7 -6
View File
@@ -56,15 +56,13 @@ public:
* this service, sharing the AudioChannel.
*/
void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
uint32_t aNotifyPlayback,
AudioChannel aChannel);
/**
* Any audio channel agent that stops playing should unregister itself to
* this service.
*/
void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent,
uint32_t aNotifyPlayback);
void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
/**
* For nested iframes.
@@ -124,8 +122,9 @@ public:
// group agents per top outer window, but we can have multiple innerWindow per
// top outerWindow (subiframes, etc.) and we have to identify all the agents
// just for a particular innerWindow.
void RefreshAgentsCapture(nsPIDOMWindow* aWindow,
uint64_t aInnerWindowID);
void SetWindowAudioCaptured(nsPIDOMWindow* aWindow,
uint64_t aInnerWindowID,
bool aCapture);
#ifdef MOZ_WIDGET_GONK
@@ -189,13 +188,15 @@ private:
struct AudioChannelWindow final
{
explicit AudioChannelWindow(uint64_t aWindowID)
: mWindowID(aWindowID)
: mWindowID(aWindowID),
mIsAudioCaptured(false)
{
// Workaround for bug1183033, system channel type can always playback.
mChannels[(int16_t)AudioChannel::System].mMuted = false;
}
uint64_t mWindowID;
bool mIsAudioCaptured;
AudioChannelConfig mChannels[NUMBER_OF_AUDIO_CHANNELS];
// Raw pointer because the AudioChannelAgent must unregister itself.
+4 -11
View File
@@ -6,7 +6,7 @@
interface nsIDOMWindow;
[uuid(5fe83b24-38b9-4901-a4a1-d1bd57d3fe18)]
[uuid(0a451ee0-972e-11e5-a837-0800200c9a66)]
interface nsIAudioChannelAgentCallback : nsISupports
{
/**
@@ -17,7 +17,7 @@ interface nsIAudioChannelAgentCallback : nsISupports
/**
* Notified when the capture state is changed.
*/
void windowAudioCaptureChanged();
void windowAudioCaptureChanged(in bool aCapture);
};
/**
@@ -34,7 +34,7 @@ interface nsIAudioChannelAgentCallback : nsISupports
* 1. Changes to the playable status of this channel.
*/
[uuid(18222148-1b32-463d-b050-b741f43a07ba)]
[uuid(ab7e21c0-970c-11e5-a837-0800200c9a66)]
interface nsIAudioChannelAgent : nsISupports
{
const long AUDIO_AGENT_CHANNEL_NORMAL = 0;
@@ -52,9 +52,6 @@ interface nsIAudioChannelAgent : nsISupports
const long AUDIO_AGENT_STATE_MUTED = 1;
const long AUDIO_AGENT_STATE_FADED = 2;
const long AUDIO_AGENT_DONT_NOTIFY = 0;
const long AUDIO_AGENT_NOTIFY = 1;
/**
* Before init() is called, this returns AUDIO_AGENT_CHANNEL_ERROR.
*/
@@ -101,10 +98,6 @@ interface nsIAudioChannelAgent : nsISupports
* Note: Gecko component SHOULD call this function first then start to
* play audio stream only when return value is true.
*
* @param notifyPlaying
* Whether to send audio-playback notifications, one of AUDIO_CHANNEL_NOTIFY
* or AUDIO_CHANNEL_DONT_NOTIFY.
*
* @return
* normal state: the agent has registered with audio channel service and
* the component should start playback.
@@ -113,7 +106,7 @@ interface nsIAudioChannelAgent : nsISupports
* faded state: the agent has registered with audio channel service the
* component should start playback as well as reducing the volume.
*/
void notifyStartedPlaying(in unsigned long notifyPlayback, out float volume, out bool muted);
void notifyStartedPlaying(out float volume, out bool muted);
/**
* Notify the agent we no longer want to play.
+2 -1
View File
@@ -1378,6 +1378,7 @@ Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
uint64_t aInnerWindowID,
const nsAString& aCallID,
ErrorResult& aRv)
{
CallbackObjectHolder<MozGetUserMediaDevicesSuccessCallback,
@@ -1397,7 +1398,7 @@ Navigator::MozGetUserMediaDevices(const MediaStreamConstraints& aConstraints,
MediaManager* manager = MediaManager::Get();
aRv = manager->GetUserMediaDevices(mWindow, aConstraints, onsuccess, onerror,
aInnerWindowID);
aInnerWindowID, aCallID);
}
#endif
+1
View File
@@ -294,6 +294,7 @@ public:
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
NavigatorUserMediaErrorCallback& aOnError,
uint64_t aInnerWindowID,
const nsAString& aCallID,
ErrorResult& aRv);
#endif // MOZ_MEDIA_NAVIGATOR
+1 -1
View File
@@ -3797,7 +3797,7 @@ nsPIDOMWindow::SetAudioCapture(bool aCapture)
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
if (service) {
service->RefreshAgentsCapture(GetOuterWindow(), mWindowID);
service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture);
}
return NS_OK;
+2 -7
View File
@@ -9,15 +9,10 @@ fetch("audio.ogg").then(response => {
var src = ac.createBufferSource();
src.buffer = ab;
src.loop = true;
src.loopStart = 0;
src.loopEnd = ab.duration;
src.start();
src.connect(ac.destination);
setTimeout(() => {
if (ac.state == "running") {
parent.runTest();
} else {
setTimeout(arguments.callee, 0);
}
});
});
var suspendPromise;
+1
View File
@@ -322,6 +322,7 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e1
[test_noAudioNotificationOnMutedElement.html]
[test_noAudioNotificationOnMutedOrVolume0Element.html]
[test_noAudioNotificationOnVolume0Element.html]
[test_noWebAudioNotification.html]
[test_openDialogChromeOnly.html]
[test_open_null_features.html]
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Fails on b2g-desktop, tracked in bug 1011874
@@ -0,0 +1,66 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for video controller in windows</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<pre id="test">
</pre>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("Testing an event not happening");
var observer = {
observe: function(subject, topic, data) {
ok(false, "should not receive audio-playback notification!");
}
};
var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
.getService(SpecialPowers.Ci.nsIObserverService);
var ac;
var tests = [
function() {
SpecialPowers.pushPrefEnv({"set": [["media.useAudioChannelService", true]]}, runTest);
},
function() {
observerService.addObserver(observer, "audio-playback", false);
ok(true, "Observer set");
runTest();
},
function() {
ac = new AudioContext();
setTimeout(runTest, 100);
},
function() {
observerService.removeObserver(observer, "audio-playback");
ok(true, "Observer removed");
runTest();
}
];
function runTest() {
if (!tests.length) {
SimpleTest.finish();
return;
}
var test = tests.shift();
test();
}
runTest();
</script>
</body>
</html>
+5 -4
View File
@@ -36,16 +36,17 @@ var tests = [
runTest();
},
function() {
iframe.src = "file_webaudioLoop.html";
},
function() {
observerService.addObserver(observer, "media-playback", false);
ok(true, "Observer set");
runTest();
},
function() {
iframe.src = "file_webaudioLoop.html";
expectedNotification = 'active';
},
function() {
expectedNotification = 'inactive';
iframe.contentWindow.suspendAC();
+1 -2
View File
@@ -1187,8 +1187,7 @@ nsDOMCameraControl::NotifyRecordingStatusChange(const nsString& aMsg)
// Video recording doesn't output any sound, so it's not necessary to check canPlay.
float volume = 0.0;
bool muted = true;
rv = mAudioChannelAgent->NotifyStartedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_DONT_NOTIFY,
&volume, &muted);
rv = mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
+2 -3
View File
@@ -454,8 +454,7 @@ FMRadio::EnableAudioChannelAgent()
float volume = 0.0;
bool muted = true;
mAudioChannelAgent->NotifyStartedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY,
&volume, &muted);
mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
WindowVolumeChanged(volume, muted);
mAudioChannelAgentEnabled = true;
@@ -470,7 +469,7 @@ FMRadio::WindowVolumeChanged(float aVolume, bool aMuted)
}
NS_IMETHODIMP
FMRadio::WindowAudioCaptureChanged()
FMRadio::WindowAudioCaptureChanged(bool aCapture)
{
return NS_OK;
}
+16 -35
View File
@@ -113,31 +113,18 @@ using mozilla::net::nsMediaFragmentURIParser;
class MOZ_STACK_CLASS AutoNotifyAudioChannelAgent
{
RefPtr<mozilla::dom::HTMLMediaElement> mElement;
bool mShouldNotify;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
public:
AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement,
bool aNotify
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
explicit AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mElement(aElement)
, mShouldNotify(aNotify)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (mShouldNotify) {
// The audio channel agent may not exist now.
if (mElement->MaybeCreateAudioChannelAgent()) {
mElement->NotifyAudioChannelAgent(false);
}
}
}
~AutoNotifyAudioChannelAgent()
{
if (mShouldNotify) {
// The audio channel agent is destroyed at this point.
if (mElement->MaybeCreateAudioChannelAgent()) {
mElement->NotifyAudioChannelAgent(true);
}
}
mElement->UpdateAudioChannelPlayingState();
}
};
@@ -3373,10 +3360,7 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
// If the element is gaining or losing an audio track, we need to notify
// the audio channel agent so that the correct audio-playback events will
// get dispatched.
bool audioTrackChanging = mMediaInfo.HasAudio() != aInfo->HasAudio();
AutoNotifyAudioChannelAgent autoNotify(this,
audioTrackChanging &&
mPlayingThroughTheAudioChannel);
AutoNotifyAudioChannelAgent autoNotify(this);
mMediaInfo = *aInfo;
mIsEncrypted = aInfo->IsEncrypted()
@@ -4705,6 +4689,11 @@ HTMLMediaElement::IsPlayingThroughTheAudioChannel() const
return false;
}
// If this element doesn't have any audio tracks.
if (!HasAudio()) {
return false;
}
// The volume should not be ~0
if (std::fabs(Volume()) <= 1e-7) {
return false;
@@ -4760,23 +4749,15 @@ HTMLMediaElement::UpdateAudioChannelPlayingState()
void
HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying)
{
// Immediately check if this should go to the MSG instead of the normal
// media playback route.
WindowAudioCaptureChanged();
// This is needed to pass nsContentUtils::IsCallerChrome().
// AudioChannel API should not called from content but it can happen that
// this method has some content JS in its stack.
AutoNoJSAPI nojsapi;
if (aPlaying) {
// Don't notify playback if this element doesn't have any audio tracks.
uint32_t notify = HasAudio() ? nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY :
nsIAudioChannelAgent::AUDIO_AGENT_DONT_NOTIFY;
float volume = 0.0;
bool muted = true;
mAudioChannelAgent->NotifyStartedPlaying(notify, &volume, &muted);
mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
WindowVolumeChanged(volume, muted);
} else {
mAudioChannelAgent->NotifyStoppedPlaying();
@@ -4938,17 +4919,17 @@ HTMLMediaElement::GetTopLevelPrincipal()
}
#endif // MOZ_EME
NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged()
NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged(bool aCapture)
{
MOZ_ASSERT(mAudioChannelAgent);
MOZ_ASSERT(HasAudio());
if (!OwnerDoc()->GetInnerWindow()) {
return NS_OK;
}
bool captured = OwnerDoc()->GetInnerWindow()->GetAudioCaptured();
if (captured != mAudioCapturedByWindow) {
if (captured) {
if (aCapture != mAudioCapturedByWindow) {
if (aCapture) {
mAudioCapturedByWindow = true;
nsCOMPtr<nsPIDOMWindow> window =
do_QueryInterface(OwnerDoc()->GetParentObject());
@@ -4984,7 +4965,7 @@ NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged()
}
}
return NS_OK;
return NS_OK;
}
AudioTrackList*
+8 -5
View File
@@ -2344,7 +2344,8 @@ MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
const MediaStreamConstraints& aConstraints,
nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
nsIDOMGetUserMediaErrorCallback* aOnFailure,
uint64_t aWindowId)
uint64_t aWindowId,
const nsAString& aCallID)
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIGetUserMediaDevicesSuccessCallback> onSuccess(aOnSuccess);
@@ -2362,10 +2363,12 @@ MediaManager::GetUserMediaDevices(nsPIDOMWindow* aWindow,
for (auto& callID : *callIDs) {
GetUserMediaTask* task;
if (mActiveCallbacks.Get(callID, &task)) {
nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*task->mSourceSet);
onSuccess->OnSuccess(array);
return NS_OK;
if (!aCallID.Length() || aCallID == callID) {
if (mActiveCallbacks.Get(callID, &task)) {
nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*task->mSourceSet);
onSuccess->OnSuccess(array);
return NS_OK;
}
}
}
return NS_ERROR_UNEXPECTED;
+2 -1
View File
@@ -458,7 +458,8 @@ public:
const dom::MediaStreamConstraints& aConstraints,
nsIGetUserMediaDevicesSuccessCallback* onSuccess,
nsIDOMGetUserMediaErrorCallback* onError,
uint64_t aInnerWindowID = 0);
uint64_t aInnerWindowID = 0,
const nsAString& aCallID = nsString());
nsresult EnumerateDevices(nsPIDOMWindow* aWindow,
nsIGetUserMediaDevicesSuccessCallback* aOnSuccess,
+5 -2
View File
@@ -492,9 +492,10 @@ MediaPermissionManager::HandleRequest(RefPtr<dom::GetUserMediaRequest> &req)
{
nsString callID;
req->GetCallID(callID);
uint64_t innerWindowID = req->InnerWindowID();
nsCOMPtr<nsPIDOMWindow> innerWindow = static_cast<nsPIDOMWindow*>
(nsGlobalWindow::GetInnerWindowWithId(req->InnerWindowID()));
(nsGlobalWindow::GetInnerWindowWithId(innerWindowID));
if (!innerWindow) {
MOZ_ASSERT(false, "No inner window");
return NS_ERROR_FAILURE;
@@ -509,7 +510,9 @@ MediaPermissionManager::HandleRequest(RefPtr<dom::GetUserMediaRequest> &req)
req->GetConstraints(constraints);
RefPtr<MediaManager> MediaMgr = MediaManager::GetInstance();
nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints, onSuccess, onError);
nsresult rv = MediaMgr->GetUserMediaDevices(innerWindow, constraints,
onSuccess, onError,
innerWindowID, callID);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
+10 -12
View File
@@ -529,7 +529,7 @@ AudioDestinationNode::WindowVolumeChanged(float aVolume, bool aMuted)
}
NS_IMETHODIMP
AudioDestinationNode::WindowAudioCaptureChanged()
AudioDestinationNode::WindowAudioCaptureChanged(bool aCapture)
{
MOZ_ASSERT(mAudioChannelAgent);
@@ -537,10 +537,13 @@ AudioDestinationNode::WindowAudioCaptureChanged()
return NS_OK;
}
bool captured = GetOwner()->GetAudioCaptured();
nsCOMPtr<nsPIDOMWindow> ownerWindow = GetOwner();
if (!ownerWindow) {
return NS_OK;
}
if (captured != mCaptured) {
if (captured) {
if (aCapture != mCaptured) {
if (aCapture) {
nsCOMPtr<nsPIDOMWindow> window = Context()->GetParentObject();
uint64_t id = window->WindowID();
mCaptureStreamPort =
@@ -548,7 +551,7 @@ AudioDestinationNode::WindowAudioCaptureChanged()
} else {
mCaptureStreamPort->Destroy();
}
mCaptured = captured;
mCaptured = aCapture;
}
return NS_OK;
@@ -640,10 +643,7 @@ AudioDestinationNode::CreateAudioChannelAgent()
return rv;
}
rv = WindowAudioCaptureChanged();
NS_WARN_IF(NS_FAILED(rv));
return rv;
return NS_OK;
}
void
@@ -733,13 +733,11 @@ AudioDestinationNode::InputMuted(bool aMuted)
float volume = 0.0;
bool muted = true;
nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY,
&volume, &muted);
nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
WindowAudioCaptureChanged();
WindowVolumeChanged(volume, muted);
}
+26 -14
View File
@@ -29,8 +29,10 @@ namespace dom {
class SynthStreamListener : public MediaStreamListener
{
public:
explicit SynthStreamListener(nsSpeechTask* aSpeechTask) :
explicit SynthStreamListener(nsSpeechTask* aSpeechTask,
MediaStream* aStream) :
mSpeechTask(aSpeechTask),
mStream(aStream),
mStarted(false)
{
}
@@ -63,6 +65,8 @@ public:
break;
case EVENT_REMOVED:
mSpeechTask = nullptr;
// Dereference MediaStream to destroy safety
mStream = nullptr;
break;
default:
break;
@@ -83,6 +87,8 @@ private:
// Raw pointer; if we exist, the stream exists,
// and 'mSpeechTask' exclusively owns it and therefor exists as well.
nsSpeechTask* mSpeechTask;
// This is KungFuDeathGrip for MediaStream
RefPtr<MediaStream> mStream;
bool mStarted;
};
@@ -94,6 +100,7 @@ NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance, mCallback);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask)
NS_INTERFACE_MAP_ENTRY(nsISpeechTask)
NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTask)
NS_INTERFACE_MAP_END
@@ -132,6 +139,8 @@ nsSpeechTask::~nsSpeechTask()
mStream->Destroy();
}
// This will finally destroyed by SynthStreamListener becasue
// MediaStream::Destroy() is async.
mStream = nullptr;
}
@@ -142,15 +151,19 @@ nsSpeechTask::~nsSpeechTask()
}
void
nsSpeechTask::Init(ProcessedMediaStream* aStream)
nsSpeechTask::InitDirectAudio()
{
if (aStream) {
mStream = aStream->Graph()->CreateSourceStream(nullptr);
mPort = aStream->AllocateInputPort(mStream, 0);
mIndirectAudio = false;
} else {
mIndirectAudio = true;
}
mStream = MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
AudioChannel::Normal)->
CreateSourceStream(nullptr);
mIndirectAudio = false;
mInited = true;
}
void
nsSpeechTask::InitIndirectAudio()
{
mIndirectAudio = true;
mInited = true;
}
@@ -181,7 +194,7 @@ nsSpeechTask::Setup(nsISpeechTaskCallback* aCallback,
// mStream is set up in Init() that should be called before this.
MOZ_ASSERT(mStream);
mStream->AddListener(new SynthStreamListener(this));
mStream->AddListener(new SynthStreamListener(this, mStream));
// XXX: Support more than one channel
if(NS_WARN_IF(!(aChannels == 1))) {
@@ -683,8 +696,7 @@ nsSpeechTask::CreateAudioChannelAgent()
this);
float volume = 0.0f;
bool muted = true;
mAudioChannelAgent->NotifyStartedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY, &volume, &muted);
WindowVolumeChanged(volume, muted);
mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
}
void
@@ -704,7 +716,7 @@ nsSpeechTask::WindowVolumeChanged(float aVolume, bool aMuted)
}
NS_IMETHODIMP
nsSpeechTask::WindowAudioCaptureChanged()
nsSpeechTask::WindowAudioCaptureChanged(bool aCapture)
{
// This is not supported yet.
return NS_OK;
@@ -713,7 +725,7 @@ nsSpeechTask::WindowAudioCaptureChanged()
void
nsSpeechTask::SetAudioOutputVolume(float aVolume)
{
if (mStream) {
if (mStream && !mStream->IsDestroyed()) {
mStream->SetAudioOutputVolume(this, aVolume);
}
if (mIndirectAudio) {
+3 -1
View File
@@ -21,6 +21,7 @@ class SynthStreamListener;
class nsSpeechTask : public nsISpeechTask
, public nsIAudioChannelAgentCallback
, public nsSupportsWeakReference
{
friend class SynthStreamListener;
@@ -48,7 +49,8 @@ public:
void SetSpeechSynthesis(SpeechSynthesis* aSpeechSynthesis);
void Init(ProcessedMediaStream* aStream);
void InitDirectAudio();
void InitIndirectAudio();
void SetChosenVoiceURI(const nsAString& aUri);
@@ -12,6 +12,7 @@
#include "SpeechSynthesisVoice.h"
#include "nsSynthVoiceRegistry.h"
#include "nsSpeechTask.h"
#include "AudioChannelService.h"
#include "nsString.h"
#include "mozilla/StaticPtr.h"
@@ -177,14 +178,6 @@ nsSynthVoiceRegistry::~nsSynthVoiceRegistry()
// mSpeechSynthChild's lifecycle is managed by the Content protocol.
mSpeechSynthChild = nullptr;
if (mStream) {
if (!mStream->IsDestroyed()) {
mStream->Destroy();
}
mStream = nullptr;
}
mUriVoiceMap.Clear();
}
@@ -627,6 +620,22 @@ nsSynthVoiceRegistry::SpeakUtterance(SpeechSynthesisUtterance& aUtterance,
aUtterance.mVoice->GetVoiceURI(uri);
}
// Get current audio volume to apply speech call
float volume = aUtterance.Volume();
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
if (service) {
nsCOMPtr<nsPIDOMWindow> topWindow =
do_QueryInterface(aUtterance.GetOwner());
if (topWindow) {
float audioVolume = 1.0f;
bool muted = false;
service->GetState(topWindow->GetOuterWindow(),
static_cast<uint32_t>(AudioChannelService::GetDefaultAudioChannel()),
&audioVolume, &muted);
volume = muted ? 0.0f : audioVolume * volume;
}
}
RefPtr<nsSpeechTask> task;
if (XRE_IsContentProcess()) {
task = new SpeechTaskChild(&aUtterance);
@@ -636,13 +645,13 @@ nsSynthVoiceRegistry::SpeakUtterance(SpeechSynthesisUtterance& aUtterance,
aUtterance.mText,
lang,
uri,
aUtterance.Volume(),
volume,
aUtterance.Rate(),
aUtterance.Pitch());
} else {
task = new nsSpeechTask(&aUtterance);
Speak(aUtterance.mText, lang, uri,
aUtterance.Volume(), aUtterance.Rate(), aUtterance.Pitch(), task);
volume, aUtterance.Rate(), aUtterance.Pitch(), task);
}
return task.forget();
@@ -775,14 +784,9 @@ nsSynthVoiceRegistry::SpeakImpl(VoiceData* aVoice,
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to get speech service type");
if (serviceType == nsISpeechService::SERVICETYPE_INDIRECT_AUDIO) {
aTask->Init(nullptr);
aTask->InitIndirectAudio();
} else {
if (!mStream) {
mStream =
MediaStreamGraph::GetInstance(MediaStreamGraph::AUDIO_THREAD_DRIVER,
AudioChannel::Normal)->CreateTrackUnionStream(nullptr);
}
aTask->Init(mStream);
aTask->InitDirectAudio();
}
aVoice->mService->Speak(aText, aVoice->mUri, aVolume, aRate, aPitch, aTask);
@@ -95,8 +95,6 @@ private:
SpeechSynthesisChild* mSpeechSynthChild;
RefPtr<ProcessedMediaStream> mStream;
bool mUseGlobalQueue;
nsTArray<RefPtr<GlobalQueueItem>> mGlobalQueue;
+1 -2
View File
@@ -2438,8 +2438,7 @@ _setvalue(NPP npp, NPPVariable variable, void *result)
} else {
float volume = 0.0;
bool muted = true;
rv = agent->NotifyStartedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY,
&volume, &muted);
rv = agent->NotifyStartedPlaying(&volume, &muted);
if (NS_WARN_IF(NS_FAILED(rv))) {
return NPERR_NO_ERROR;
}
+1 -1
View File
@@ -1862,7 +1862,7 @@ nsNPAPIPluginInstance::WindowVolumeChanged(float aVolume, bool aMuted)
}
NS_IMETHODIMP
nsNPAPIPluginInstance::WindowAudioCaptureChanged()
nsNPAPIPluginInstance::WindowAudioCaptureChanged(bool aCapture)
{
return NS_OK;
}
+2 -3
View File
@@ -562,8 +562,7 @@ Telephony::HandleAudioAgentState()
mIsAudioStartPlaying = true;
float volume;
bool muted;
rv = mAudioAgent->NotifyStartedPlaying(nsIAudioChannelAgent::AUDIO_AGENT_NOTIFY,
&volume, &muted);
rv = mAudioAgent->NotifyStartedPlaying(&volume, &muted);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@@ -712,7 +711,7 @@ Telephony::WindowVolumeChanged(float aVolume, bool aMuted)
}
NS_IMETHODIMP
Telephony::WindowAudioCaptureChanged()
Telephony::WindowAudioCaptureChanged(bool aCapture)
{
// Do nothing, it's useless for the telephony object.
return NS_OK;
+7 -1
View File
@@ -406,7 +406,13 @@ partial interface Navigator {
// The originating innerWindowID is needed to
// avoid calling the callbacks if the window has
// navigated away. It is optional only as legacy.
optional unsigned long long innerWindowID = 0);
optional unsigned long long innerWindowID = 0,
// The callID is needed in case of multiple
// concurrent requests to find the right one.
// It is optional only as legacy.
// TODO: Rewrite to not need this method anymore,
// now that devices are enumerated earlier.
optional DOMString callID = "");
};
#endif // MOZ_MEDIA_NAVIGATOR
+5 -3
View File
@@ -1115,7 +1115,8 @@ ContainerLayer::ContainerLayer(LayerManager* aManager, void* aImplData)
mMayHaveReadbackChild(false),
mChildrenChanged(false),
mEventRegionsOverride(EventRegionsOverride::NoOverride),
mVRDeviceID(0)
mVRDeviceID(0),
mInputFrameID(0)
{
MOZ_COUNT_CTOR(ContainerLayer);
mContentFlags = 0; // Clear NO_TEXT, NO_TEXT_OVER_TRANSPARENT
@@ -1277,7 +1278,8 @@ ContainerLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
mInheritedXScale, mInheritedYScale,
mPresShellResolution, mScaleToResolution,
mEventRegionsOverride,
mVRDeviceID);
mVRDeviceID,
mInputFrameID);
}
bool
@@ -2141,7 +2143,7 @@ ContainerLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix)
aStream << " [force-ehr]";
}
if (mVRDeviceID) {
aStream << nsPrintfCString(" [hmd=%lu]", mVRDeviceID).get();
aStream << nsPrintfCString(" [hmd=%lu] [hmdframe=%l]", mVRDeviceID, mInputFrameID).get();
}
}
+9
View File
@@ -2133,10 +2133,18 @@ public:
*/
void SetVRDeviceID(uint32_t aVRDeviceID) {
mVRDeviceID = aVRDeviceID;
Mutated();
}
uint32_t GetVRDeviceID() {
return mVRDeviceID;
}
void SetInputFrameID(int32_t aInputFrameID) {
mInputFrameID = aInputFrameID;
Mutated();
}
int32_t GetInputFrameID() {
return mInputFrameID;
}
/**
* Replace the current effective transform with the given one,
@@ -2213,6 +2221,7 @@ protected:
bool mChildrenChanged;
EventRegionsOverride mEventRegionsOverride;
uint32_t mVRDeviceID;
int32_t mInputFrameID;
};
/**
+7
View File
@@ -25,6 +25,7 @@
#include "nsDebug.h" // for printf_stderr, NS_ASSERTION
#include "nsXULAppAPI.h" // for XRE_GetProcessType, etc
#include "TextureClientSharedSurface.h"
#include "VRManagerChild.h"
using namespace mozilla::gfx;
using namespace mozilla::gl;
@@ -125,6 +126,7 @@ CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer)
t->mTextureClient = mBuffer;
t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mBuffer->GetSize());
t->mFrameID = mFrameID;
t->mInputFrameID = VRManagerChild::Get()->GetInputFrameID();
GetForwarder()->UseTextures(this, textures);
mBuffer->SyncWithObject(GetForwarder()->GetSyncObject());
}
@@ -476,6 +478,11 @@ CanvasClientSharedSurface::Updated()
t->mTextureClient = mFront;
t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mFront->GetSize());
t->mFrameID = mFrameID;
// XXX TODO - This reference to VRManagerChild will be moved with the
// implementation of the WebVR 1.0 API, which will enable
// the inputFrameID to be passed through Javascript with
// the new VRDisplay API.
t->mInputFrameID = VRManagerChild::Get()->GetInputFrameID();
forwarder->UseTextures(this, textures);
}
+3
View File
@@ -194,6 +194,7 @@ public:
gfx::IntRect mPictureRect;
int32_t mFrameID;
int32_t mProducerID;
int32_t mInputFrameID;
};
virtual void UseTextureHost(const nsTArray<TimedTexture>& aTextures);
virtual void UseComponentAlphaTextures(TextureHost* aTextureOnBlack,
@@ -235,6 +236,8 @@ public:
return nullptr;
}
virtual int32_t GetLastInputFrameID() const { return -1; }
protected:
TextureInfo mTextureInfo;
uint64_t mAsyncID;
@@ -139,8 +139,11 @@ template<class ContainerT> void
ContainerRenderVR(ContainerT* aContainer,
LayerManagerComposite* aManager,
const gfx::IntRect& aClipRect,
RefPtr<gfx::VRHMDInfo> aHMD)
RefPtr<gfx::VRHMDInfo> aHMD,
int32_t aInputFrameID)
{
int32_t inputFrameID = -1;
RefPtr<CompositingRenderTarget> surface;
Compositor* compositor = aManager->GetCompositor();
@@ -265,6 +268,14 @@ ContainerRenderVR(ContainerT* aContainer,
surfaceRect.width, surfaceRect.height));
layerToRender->RenderLayer(surfaceRect);
CompositableHost *ch = layerToRender->GetCompositableHost();
if (ch) {
int32_t compositableInputFrameID = ch->GetLastInputFrameID();
if (compositableInputFrameID != -1) {
inputFrameID = compositableInputFrameID;
}
}
if (restoreTransform) {
layer->ReplaceEffectiveTransform(childTransform);
}
@@ -282,7 +293,7 @@ ContainerRenderVR(ContainerT* aContainer,
compositor->SetRenderTarget(previousTarget);
if (vrRendering) {
vrRendering->SubmitFrame(aContainer->mVRRenderTargetSet);
vrRendering->SubmitFrame(aContainer->mVRRenderTargetSet, inputFrameID);
DUMP("<<< ContainerRenderVR [used vrRendering] [%p]\n", aContainer);
if (!gfxPrefs::VRMirrorTextures()) {
return;
@@ -690,7 +701,7 @@ ContainerRender(ContainerT* aContainer,
RefPtr<gfx::VRHMDInfo> hmdInfo = gfx::VRManager::Get()->GetDevice(aContainer->GetVRDeviceID());
if (hmdInfo && hmdInfo->GetConfiguration().IsValid()) {
ContainerRenderVR(aContainer, aManager, aClipRect, hmdInfo);
ContainerRenderVR(aContainer, aManager, aClipRect, hmdInfo, aContainer->GetInputFrameID());
aContainer->mPrepared = nullptr;
return;
}
+3
View File
@@ -32,6 +32,7 @@ ImageHost::ImageHost(const TextureInfo& aTextureInfo)
, mImageContainer(nullptr)
, mLastFrameID(-1)
, mLastProducerID(-1)
, mLastInputFrameID(-1)
, mBias(BIAS_NONE)
, mLocked(false)
{}
@@ -84,6 +85,7 @@ ImageHost::UseTextureHost(const nsTArray<TimedTexture>& aTextures)
img.mPictureRect = t.mPictureRect;
img.mFrameID = t.mFrameID;
img.mProducerID = t.mProducerID;
img.mInputFrameID = t.mInputFrameID;
}
// Recycle any leftover mTextureSources and call PrepareTextureSource on all
// images.
@@ -357,6 +359,7 @@ ImageHost::Composite(LayerComposite* aLayer,
}
mLastFrameID = img->mFrameID;
mLastProducerID = img->mProducerID;
mLastInputFrameID = img->mInputFrameID;
}
aEffectChain.mPrimaryEffect = effect;
gfx::Rect pictureRect(0, 0, img->mPictureRect.width, img->mPictureRect.height);
+3
View File
@@ -100,6 +100,7 @@ public:
int32_t GetLastFrameID() const { return mLastFrameID; }
int32_t GetLastProducerID() const { return mLastProducerID; }
virtual int32_t GetLastInputFrameID() const override { return mLastInputFrameID; }
enum Bias {
// Don't apply bias to frame times
@@ -118,6 +119,7 @@ protected:
gfx::IntRect mPictureRect;
int32_t mFrameID;
int32_t mProducerID;
int32_t mInputFrameID;
};
/**
@@ -135,6 +137,7 @@ protected:
ImageContainerParent* mImageContainer;
int32_t mLastFrameID;
int32_t mLastProducerID;
int32_t mLastInputFrameID;
/**
* Bias to apply to the next frame.
*/
+17 -3
View File
@@ -120,7 +120,12 @@ TextureSourceD3D11::GetShaderResourceView()
if (!mSRV && mTexture) {
RefPtr<ID3D11Device> device;
mTexture->GetDevice(getter_AddRefs(device));
HRESULT hr = device->CreateShaderResourceView(mTexture, nullptr, getter_AddRefs(mSRV));
// see comment in CompositingRenderTargetD3D11 constructor
CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(D3D11_SRV_DIMENSION_TEXTURE2D, mFormatOverride);
D3D11_SHADER_RESOURCE_VIEW_DESC *desc = mFormatOverride == DXGI_FORMAT_UNKNOWN ? nullptr : &srvDesc;
HRESULT hr = device->CreateShaderResourceView(mTexture, desc, getter_AddRefs(mSRV));
if (FAILED(hr)) {
gfxCriticalNote << "[D3D11] TextureSourceD3D11:GetShaderResourceView CreateSRV failure " << gfx::hexa(hr);
return nullptr;
@@ -1164,7 +1169,8 @@ DataTextureSourceD3D11::SetCompositor(Compositor* aCompositor)
}
CompositingRenderTargetD3D11::CompositingRenderTargetD3D11(ID3D11Texture2D* aTexture,
const gfx::IntPoint& aOrigin)
const gfx::IntPoint& aOrigin,
DXGI_FORMAT aFormatOverride)
: CompositingRenderTarget(aOrigin)
{
MOZ_ASSERT(aTexture);
@@ -1174,7 +1180,15 @@ CompositingRenderTargetD3D11::CompositingRenderTargetD3D11(ID3D11Texture2D* aTex
RefPtr<ID3D11Device> device;
mTexture->GetDevice(getter_AddRefs(device));
HRESULT hr = device->CreateRenderTargetView(mTexture, nullptr, getter_AddRefs(mRTView));
mFormatOverride = aFormatOverride;
// If we happen to have a typeless underlying DXGI surface, we need to be explicit
// about the format here. (Such a surface could come from an external source, such
// as the Oculus compositor)
CD3D11_RENDER_TARGET_VIEW_DESC rtvDesc(D3D11_RTV_DIMENSION_TEXTURE2D, mFormatOverride);
D3D11_RENDER_TARGET_VIEW_DESC *desc = aFormatOverride == DXGI_FORMAT_UNKNOWN ? nullptr : &rtvDesc;
HRESULT hr = device->CreateRenderTargetView(mTexture, desc, getter_AddRefs(mRTView));
if (FAILED(hr)) {
LOGD3D11("Failed to create RenderTargetView.");
+4 -2
View File
@@ -221,7 +221,7 @@ protected:
class TextureSourceD3D11
{
public:
TextureSourceD3D11() {}
TextureSourceD3D11() : mFormatOverride(DXGI_FORMAT_UNKNOWN) {}
virtual ~TextureSourceD3D11() {}
virtual ID3D11Texture2D* GetD3D11Texture() const { return mTexture; }
@@ -232,6 +232,7 @@ protected:
gfx::IntSize mSize;
RefPtr<ID3D11Texture2D> mTexture;
RefPtr<ID3D11ShaderResourceView> mSRV;
DXGI_FORMAT mFormatOverride;
};
/**
@@ -404,7 +405,8 @@ class CompositingRenderTargetD3D11 : public CompositingRenderTarget,
{
public:
CompositingRenderTargetD3D11(ID3D11Texture2D* aTexture,
const gfx::IntPoint& aOrigin);
const gfx::IntPoint& aOrigin,
DXGI_FORMAT aFormatOverride = DXGI_FORMAT_UNKNOWN);
virtual TextureSourceD3D11* AsSourceD3D11() override { return this; }
+2 -1
View File
@@ -113,13 +113,14 @@ public:
struct TimedTextureClient {
TimedTextureClient()
: mTextureClient(nullptr), mFrameID(0), mProducerID(0) {}
: mTextureClient(nullptr), mFrameID(0), mProducerID(0), mInputFrameID(0) {}
TextureClient* mTextureClient;
TimeStamp mTimeStamp;
nsIntRect mPictureRect;
int32_t mFrameID;
int32_t mProducerID;
int32_t mInputFrameID;
};
/**
* Tell the CompositableHost on the compositor side what textures to use for
@@ -182,6 +182,7 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation
t->mPictureRect = timedTexture.picture();
t->mFrameID = timedTexture.frameID();
t->mProducerID = timedTexture.producerID();
t->mInputFrameID = timedTexture.inputFrameID();
MOZ_ASSERT(ValidatePictureRect(t->mTexture->GetSize(), t->mPictureRect));
MaybeFence maybeFence = timedTexture.fence();
+1 -1
View File
@@ -157,7 +157,7 @@ ImageBridgeChild::UseTextures(CompositableClient* aCompositable,
textures.AppendElement(TimedTexture(nullptr, t.mTextureClient->GetIPDLActor(),
fence.IsValid() ? MaybeFence(fence) : MaybeFence(null_t()),
t.mTimeStamp, t.mPictureRect,
t.mFrameID, t.mProducerID));
t.mFrameID, t.mProducerID, t.mInputFrameID));
}
mTxn->AddNoSwapEdit(OpUseTexture(nullptr, aCompositable->GetIPDLActor(),
textures));
@@ -404,6 +404,7 @@ LayerTransactionParent::RecvUpdate(InfallibleTArray<Edit>&& cset,
containerLayer->SetScaleToResolution(attrs.scaleToResolution(),
attrs.presShellResolution());
containerLayer->SetEventRegionsOverride(attrs.eventRegionsOverride());
containerLayer->SetInputFrameID(attrs.inputFrameID());
if (attrs.hmdDeviceID()) {
containerLayer->SetVRDeviceID(attrs.hmdDeviceID());
+2
View File
@@ -249,6 +249,7 @@ struct ContainerLayerAttributes {
bool scaleToResolution;
EventRegionsOverride eventRegionsOverride;
uint32_t hmdDeviceID;
int32_t inputFrameID;
};
struct ColorLayerAttributes { LayerColor color; IntRect bounds; };
struct CanvasLayerAttributes { Filter filter; IntRect bounds; };
@@ -393,6 +394,7 @@ struct TimedTexture {
IntRect picture;
uint32_t frameID;
uint32_t producerID;
int32_t inputFrameID;
};
/**
+1 -1
View File
@@ -413,7 +413,7 @@ ShadowLayerForwarder::UseTextures(CompositableClient* aCompositable,
textures.AppendElement(TimedTexture(nullptr, t.mTextureClient->GetIPDLActor(),
fence.IsValid() ? MaybeFence(fence) : MaybeFence(null_t()),
t.mTimeStamp, t.mPictureRect,
t.mFrameID, t.mProducerID));
t.mFrameID, t.mProducerID, t.mInputFrameID));
if ((t.mTextureClient->GetFlags() & TextureFlags::IMMEDIATE_UPLOAD)
&& t.mTextureClient->HasInternalBuffer()) {
+2 -1
View File
@@ -161,6 +161,7 @@ struct VRDeviceInfo
struct VRHMDSensorState {
double timestamp;
int32_t inputFrameID;
VRStateValidFlags flags;
float orientation[4];
float position[3];
@@ -244,7 +245,7 @@ public:
virtual already_AddRefed<RenderTargetSet> CreateRenderTargetSet(layers::Compositor *aCompositor, const IntSize& aSize) = 0;
virtual void DestroyRenderTargetSet(RenderTargetSet *aRTSet) = 0;
virtual void SubmitFrame(RenderTargetSet *aRTSet) = 0;
virtual void SubmitFrame(RenderTargetSet *aRTSet, int32_t aInputFrameID) = 0;
protected:
VRHMDRenderingSupport() { }
};
+72 -170
View File
@@ -34,25 +34,23 @@ namespace {
static pfn_ovr_Initialize ovr_Initialize = nullptr;
static pfn_ovr_Shutdown ovr_Shutdown = nullptr;
static pfn_ovr_GetTimeInSeconds ovr_GetTimeInSeconds = nullptr;
static pfn_ovr_GetHmdDesc ovr_GetHmdDesc = nullptr;
static pfn_ovrHmd_Detect ovrHmd_Detect = nullptr;
static pfn_ovrHmd_Create ovrHmd_Create = nullptr;
static pfn_ovrHmd_CreateDebug ovrHmd_CreateDebug = nullptr;
static pfn_ovrHmd_Destroy ovrHmd_Destroy = nullptr;
static pfn_ovr_Create ovr_Create = nullptr;
static pfn_ovr_Destroy ovr_Destroy = nullptr;
static pfn_ovrHmd_ConfigureTracking ovrHmd_ConfigureTracking = nullptr;
static pfn_ovrHmd_RecenterPose ovrHmd_RecenterPose = nullptr;
static pfn_ovrHmd_GetTrackingState ovrHmd_GetTrackingState = nullptr;
static pfn_ovrHmd_GetFovTextureSize ovrHmd_GetFovTextureSize = nullptr;
static pfn_ovrHmd_GetRenderDesc ovrHmd_GetRenderDesc = nullptr;
static pfn_ovr_RecenterPose ovr_RecenterPose = nullptr;
static pfn_ovr_GetTrackingState ovr_GetTrackingState = nullptr;
static pfn_ovr_GetFovTextureSize ovr_GetFovTextureSize = nullptr;
static pfn_ovr_GetRenderDesc ovr_GetRenderDesc = nullptr;
static pfn_ovrHmd_DestroySwapTextureSet ovrHmd_DestroySwapTextureSet = nullptr;
static pfn_ovrHmd_SubmitFrame ovrHmd_SubmitFrame = nullptr;
static pfn_ovr_DestroySwapTextureSet ovr_DestroySwapTextureSet = nullptr;
static pfn_ovr_SubmitFrame ovr_SubmitFrame = nullptr;
#ifdef XP_WIN
static pfn_ovrHmd_CreateSwapTextureSetD3D11 ovrHmd_CreateSwapTextureSetD3D11 = nullptr;
static pfn_ovr_CreateSwapTextureSetD3D11 ovr_CreateSwapTextureSetD3D11 = nullptr;
#endif
static pfn_ovrHmd_CreateSwapTextureSetGL ovrHmd_CreateSwapTextureSetGL = nullptr;
static pfn_ovr_CreateSwapTextureSetGL ovr_CreateSwapTextureSetGL = nullptr;
#ifdef HAVE_64BIT_BUILD
#define BUILD_BITS 64
@@ -61,7 +59,7 @@ static pfn_ovrHmd_CreateSwapTextureSetGL ovrHmd_CreateSwapTextureSetGL = nullptr
#endif
#define OVR_PRODUCT_VERSION 0
#define OVR_MAJOR_VERSION 6
#define OVR_MAJOR_VERSION 8
#define OVR_MINOR_VERSION 0
static bool
@@ -166,24 +164,22 @@ InitializeOculusCAPI()
REQUIRE_FUNCTION(ovr_Initialize);
REQUIRE_FUNCTION(ovr_Shutdown);
REQUIRE_FUNCTION(ovr_GetTimeInSeconds);
REQUIRE_FUNCTION(ovr_GetHmdDesc);
REQUIRE_FUNCTION(ovrHmd_Detect);
REQUIRE_FUNCTION(ovrHmd_Create);
REQUIRE_FUNCTION(ovrHmd_CreateDebug);
REQUIRE_FUNCTION(ovrHmd_Destroy);
REQUIRE_FUNCTION(ovr_Create);
REQUIRE_FUNCTION(ovr_Destroy);
REQUIRE_FUNCTION(ovrHmd_ConfigureTracking);
REQUIRE_FUNCTION(ovrHmd_RecenterPose);
REQUIRE_FUNCTION(ovrHmd_GetTrackingState);
REQUIRE_FUNCTION(ovrHmd_GetFovTextureSize);
REQUIRE_FUNCTION(ovrHmd_GetRenderDesc);
REQUIRE_FUNCTION(ovr_RecenterPose);
REQUIRE_FUNCTION(ovr_GetTrackingState);
REQUIRE_FUNCTION(ovr_GetFovTextureSize);
REQUIRE_FUNCTION(ovr_GetRenderDesc);
REQUIRE_FUNCTION(ovrHmd_DestroySwapTextureSet);
REQUIRE_FUNCTION(ovrHmd_SubmitFrame);
REQUIRE_FUNCTION(ovr_DestroySwapTextureSet);
REQUIRE_FUNCTION(ovr_SubmitFrame);
#ifdef XP_WIN
REQUIRE_FUNCTION(ovrHmd_CreateSwapTextureSetD3D11);
REQUIRE_FUNCTION(ovr_CreateSwapTextureSetD3D11);
#endif
REQUIRE_FUNCTION(ovrHmd_CreateSwapTextureSetGL);
REQUIRE_FUNCTION(ovr_CreateSwapTextureSetGL);
#undef REQUIRE_FUNCTION
@@ -248,41 +244,36 @@ FromFovPort(const ovrFovPort& aFOV)
} // namespace
HMDInfoOculus::HMDInfoOculus(ovrHmd aHMD, bool aDebug, int aDeviceID)
HMDInfoOculus::HMDInfoOculus(ovrSession aSession)
: VRHMDInfo(VRHMDType::Oculus, false)
, mHMD(aHMD)
, mTracking(false)
, mDebug(aDebug)
, mDeviceID(aDeviceID)
, mSensorTrackingFramesRemaining(0)
, mSession(aSession)
, mInputFrameID(0)
{
MOZ_ASSERT(sizeof(HMDInfoOculus::DistortionVertex) == sizeof(VRDistortionVertex),
"HMDInfoOculus::DistortionVertex must match the size of VRDistortionVertex");
MOZ_COUNT_CTOR_INHERITED(HMDInfoOculus, VRHMDInfo);
if (aDebug) {
mDeviceInfo.mDeviceName.AssignLiteral("Oculus VR HMD Debug)");
} else {
mDeviceInfo.mDeviceName.AssignLiteral("Oculus VR HMD");
}
mDeviceInfo.mDeviceName.AssignLiteral("Oculus VR HMD");
mDesc = ovr_GetHmdDesc(aSession);
mDeviceInfo.mSupportedSensorBits = VRStateValidFlags::State_None;
if (mHMD->TrackingCaps & ovrTrackingCap_Orientation) {
if (mDesc.AvailableTrackingCaps & ovrTrackingCap_Orientation) {
mDeviceInfo.mSupportedSensorBits |= VRStateValidFlags::State_Orientation;
}
if (mHMD->TrackingCaps & ovrTrackingCap_Position) {
if (mDesc.AvailableTrackingCaps & ovrTrackingCap_Position) {
mDeviceInfo.mSupportedSensorBits |= VRStateValidFlags::State_Position;
}
mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Left] = FromFovPort(mHMD->DefaultEyeFov[ovrEye_Left]);
mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Right] = FromFovPort(mHMD->DefaultEyeFov[ovrEye_Right]);
mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Left] = FromFovPort(mDesc.DefaultEyeFov[ovrEye_Left]);
mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Right] = FromFovPort(mDesc.DefaultEyeFov[ovrEye_Right]);
mDeviceInfo.mMaximumEyeFOV[VRDeviceInfo::Eye_Left] = FromFovPort(mHMD->MaxEyeFov[ovrEye_Left]);
mDeviceInfo.mMaximumEyeFOV[VRDeviceInfo::Eye_Right] = FromFovPort(mHMD->MaxEyeFov[ovrEye_Right]);
mDeviceInfo.mMaximumEyeFOV[VRDeviceInfo::Eye_Left] = FromFovPort(mDesc.MaxEyeFov[ovrEye_Left]);
mDeviceInfo.mMaximumEyeFOV[VRDeviceInfo::Eye_Right] = FromFovPort(mDesc.MaxEyeFov[ovrEye_Right]);
uint32_t w = mHMD->Resolution.w;
uint32_t h = mHMD->Resolution.h;
uint32_t w = mDesc.Resolution.w;
uint32_t h = mDesc.Resolution.h;
mDeviceInfo.mScreenRect.x = 0;
mDeviceInfo.mScreenRect.y = 0;
mDeviceInfo.mScreenRect.width = std::max(w, h);
@@ -292,25 +283,12 @@ HMDInfoOculus::HMDInfoOculus(ovrHmd aHMD, bool aDebug, int aDeviceID)
SetFOV(mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Left], mDeviceInfo.mRecommendedEyeFOV[VRDeviceInfo::Eye_Right], 0.01, 10000.0);
}
bool
HMDInfoOculus::GetIsDebug() const
{
return mDebug;
}
int
HMDInfoOculus::GetDeviceID() const
{
return mDeviceID;
}
void
HMDInfoOculus::Destroy()
{
StopSensorTracking();
if (mHMD) {
ovrHmd_Destroy(mHMD);
mHMD = nullptr;
if (mSession) {
ovr_Destroy(mSession);
mSession = nullptr;
}
}
@@ -326,7 +304,7 @@ HMDInfoOculus::SetFOV(const gfx::VRFieldOfView& aFOVLeft, const gfx::VRFieldOfVi
mDeviceInfo.mEyeFOV[eye] = eye == 0 ? aFOVLeft : aFOVRight;
mFOVPort[eye] = ToFovPort(mDeviceInfo.mEyeFOV[eye]);
ovrEyeRenderDesc renderDesc = ovrHmd_GetRenderDesc(mHMD, (ovrEyeType) eye, mFOVPort[eye]);
ovrEyeRenderDesc renderDesc = ovr_GetRenderDesc(mSession, (ovrEyeType)eye, mFOVPort[eye]);
// As of Oculus 0.6.0, the HmdToEyeViewOffset values are correct and don't need to be negated.
mDeviceInfo.mEyeTranslation[eye] = Point3D(renderDesc.HmdToEyeViewOffset.x, renderDesc.HmdToEyeViewOffset.y, renderDesc.HmdToEyeViewOffset.z);
@@ -334,7 +312,7 @@ HMDInfoOculus::SetFOV(const gfx::VRFieldOfView& aFOVLeft, const gfx::VRFieldOfVi
// note that we are using a right-handed coordinate system here, to match CSS
mDeviceInfo.mEyeProjectionMatrix[eye] = mDeviceInfo.mEyeFOV[eye].ConstructProjectionMatrix(zNear, zFar, true);
texSize[eye] = ovrHmd_GetFovTextureSize(mHMD, (ovrEyeType) eye, mFOVPort[eye], pixelsPerDisplayPixel);
texSize[eye] = ovr_GetFovTextureSize(mSession, (ovrEyeType)eye, mFOVPort[eye], pixelsPerDisplayPixel);
}
// take the max of both for eye resolution
@@ -362,56 +340,20 @@ HMDInfoOculus::FillDistortionConstants(uint32_t whichEye,
bool
HMDInfoOculus::KeepSensorTracking()
{
// Keep sensor tracking alive for short time after the last request for
// tracking state by content. Value conservatively high to accomodate
// potentially high frame rates.
const uint32_t kKeepAliveFrames = 200;
bool success = true;
if (mSensorTrackingFramesRemaining == 0) {
success = StartSensorTracking();
}
if (success) {
mSensorTrackingFramesRemaining = kKeepAliveFrames;
}
return success;
// Oculus PC SDK 0.8 and newer enable tracking by default
return true;
}
void
HMDInfoOculus::NotifyVsync(const mozilla::TimeStamp& aVsyncTimestamp)
{
if (mSensorTrackingFramesRemaining == 1) {
StopSensorTracking();
}
if (mSensorTrackingFramesRemaining) {
--mSensorTrackingFramesRemaining;
}
}
bool
HMDInfoOculus::StartSensorTracking()
{
if (!mTracking) {
mTracking = ovrHmd_ConfigureTracking(mHMD, ovrTrackingCap_Orientation | ovrTrackingCap_Position, 0);
}
return mTracking;
}
void
HMDInfoOculus::StopSensorTracking()
{
if (mTracking) {
ovrHmd_ConfigureTracking(mHMD, 0, 0);
mTracking = false;
}
++mInputFrameID;
}
void
HMDInfoOculus::ZeroSensor()
{
ovrHmd_RecenterPose(mHMD);
ovr_RecenterPose(mSession);
}
VRHMDSensorState
@@ -422,7 +364,7 @@ HMDInfoOculus::GetSensorState(double timeOffset)
// XXX this is the wrong time base for timeOffset; we need to figure out how to synchronize
// the Oculus time base and the browser one.
ovrTrackingState state = ovrHmd_GetTrackingState(mHMD, ovr_GetTimeInSeconds() + timeOffset);
ovrTrackingState state = ovr_GetTrackingState(mSession, ovr_GetTimeInSeconds() + timeOffset, true);
ovrPoseStatef& pose(state.HeadPose);
result.timestamp = pose.TimeInSeconds;
@@ -488,10 +430,10 @@ struct RenderTargetSetOculus : public VRHMDRenderingSupport::RenderTargetSet
if (!hmd)
return;
if (hmd->GetOculusHMD()) {
// If the ovrHmd was already destroyed, so were all associated
if (hmd->GetOculusSession()) {
// If the ovrSession was already destroyed, so were all associated
// texture sets
ovrHmd_DestroySwapTextureSet(hmd->GetOculusHMD(), textureSet);
ovr_DestroySwapTextureSet(hmd->GetOculusSession(), textureSet);
}
hmd = nullptr;
textureSet = nullptr;
@@ -534,7 +476,7 @@ struct RenderTargetSetD3D11 : public RenderTargetSetOculus
RefPtr<layers::CompositingRenderTargetD3D11> rt;
tex11 = (ovrD3D11Texture*)&aTS->Textures[i];
rt = new layers::CompositingRenderTargetD3D11(tex11->D3D11.pTexture, IntPoint(0, 0));
rt = new layers::CompositingRenderTargetD3D11(tex11->D3D11.pTexture, IntPoint(0, 0), DXGI_FORMAT_B8G8R8A8_UNORM);
rt->SetSize(size);
renderTargets[i] = rt;
}
@@ -550,11 +492,11 @@ HMDInfoOculus::CreateRenderTargetSet(layers::Compositor *aCompositor, const IntS
{
layers::CompositorD3D11 *comp11 = static_cast<layers::CompositorD3D11*>(aCompositor);
CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM, aSize.width, aSize.height, 1, 1,
CD3D11_TEXTURE2D_DESC desc(DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, aSize.width, aSize.height, 1, 1,
D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET);
ovrSwapTextureSet *ts = nullptr;
ovrResult orv = ovrHmd_CreateSwapTextureSetD3D11(mHMD, comp11->GetDevice(), &desc, &ts);
ovrResult orv = ovr_CreateSwapTextureSetD3D11(mSession, comp11->GetDevice(), &desc, ovrSwapTextureSetD3D11_Typeless, &ts);
if (orv != ovrSuccess) {
return nullptr;
}
@@ -578,7 +520,7 @@ HMDInfoOculus::DestroyRenderTargetSet(RenderTargetSet *aRTSet)
}
void
HMDInfoOculus::SubmitFrame(RenderTargetSet *aRTSet)
HMDInfoOculus::SubmitFrame(RenderTargetSet *aRTSet, int32_t aInputFrameID)
{
RenderTargetSetOculus *rts = static_cast<RenderTargetSetOculus*>(aRTSet);
MOZ_ASSERT(rts->hmd != nullptr);
@@ -607,7 +549,7 @@ HMDInfoOculus::SubmitFrame(RenderTargetSet *aRTSet)
do_CalcEyePoses(rts->hmd->mLastTrackingState.HeadPose.ThePose, hmdToEyeViewOffset, layer.RenderPose);
ovrLayerHeader *layers = &layer.Header;
ovrResult orv = ovrHmd_SubmitFrame(mHMD, 0, nullptr, &layers, 1);
ovrResult orv = ovr_SubmitFrame(mSession, aInputFrameID, nullptr, &layers, 1);
//printf_stderr("Submitted frame %d, result: %d\n", rts->textureSet->CurrentIndex, orv);
if (orv != ovrSuccess) {
// not visible? failed?
@@ -663,11 +605,7 @@ VRHMDManagerOculus::Destroy()
MOZ_ASSERT(NS_GetCurrentThread() == mOculusThread);
mOculusThread = nullptr;
for (size_t i = 0; i < mOculusHMDs.Length(); ++i) {
mOculusHMDs[i]->Destroy();
}
mOculusHMDs.Clear();
mHMDInfo = nullptr;
ovr_Shutdown();
mOculusInitialized = false;
@@ -681,60 +619,24 @@ VRHMDManagerOculus::GetHMDs(nsTArray<RefPtr<VRHMDInfo>>& aHMDResult)
return;
}
nsTArray<RefPtr<impl::HMDInfoOculus> > newHMDs;
ovrResult orv;
int count = ovrHmd_Detect();
for (int j = 0; j < count; ++j) {
bool is_new = true;
for (size_t i = 0; i < mOculusHMDs.Length(); ++i) {
if (mOculusHMDs[i]->GetDeviceID() == j) {
newHMDs.AppendElement(mOculusHMDs[i]);
is_new = false;
break;
}
}
if (is_new) {
ovrHmd hmd;
orv = ovrHmd_Create(j, &hmd);
if (orv == ovrSuccess) {
RefPtr<HMDInfoOculus> oc = new HMDInfoOculus(hmd, false, j);
newHMDs.AppendElement(oc);
}
// ovr_Create can be slow when no HMD is present and we wish
// to keep the same oculus session when possible, so we detect
// presence of an HMD with ovr_GetHmdDesc before calling ovr_Create
ovrHmdDesc desc = ovr_GetHmdDesc(NULL);
if (desc.Type == ovrHmd_None) {
// No HMD connected.
mHMDInfo = nullptr;
} else if (mHMDInfo == nullptr) {
// HMD Detected
ovrSession session;
ovrGraphicsLuid luid;
ovrResult orv = ovr_Create(&session, &luid);
if (orv == ovrSuccess) {
mHMDInfo = new HMDInfoOculus(session);
}
}
// VRAddTestDevices == 1: add test device only if no real devices present
// VRAddTestDevices == 2: add test device always
if ((count == 0 && gfxPrefs::VRAddTestDevices() == 1) ||
(gfxPrefs::VRAddTestDevices() == 2))
{
// Keep existing debug HMD if possible
bool foundDebug = false;
for (size_t i = 0; i < mOculusHMDs.Length(); ++i) {
if (mOculusHMDs[i]->GetIsDebug()) {
newHMDs.AppendElement(mOculusHMDs[i]);
foundDebug = true;
}
}
// If there isn't already a debug HMD, create one
if (!foundDebug) {
ovrHmd hmd;
orv = ovrHmd_CreateDebug(ovrHmd_DK2, &hmd);
if (orv == ovrSuccess) {
RefPtr<HMDInfoOculus> oc = new HMDInfoOculus(hmd, true, -1);
newHMDs.AppendElement(oc);
}
}
}
mOculusHMDs = newHMDs;
for (size_t j = 0; j < mOculusHMDs.Length(); ++j) {
aHMDResult.AppendElement(mOculusHMDs[j]);
if (mHMDInfo) {
aHMDResult.AppendElement(mHMDInfo);
}
}
+7 -16
View File
@@ -23,7 +23,7 @@ namespace impl {
class HMDInfoOculus : public VRHMDInfo, public VRHMDRenderingSupport {
public:
explicit HMDInfoOculus(ovrHmd aHMD, bool aDebug, int aDeviceID);
explicit HMDInfoOculus(ovrSession aSession);
bool SetFOV(const VRFieldOfView& aFOVLeft, const VRFieldOfView& aFOVRight,
double zNear, double zFar) override;
@@ -45,11 +45,9 @@ public:
/* VRHMDRenderingSupport */
already_AddRefed<RenderTargetSet> CreateRenderTargetSet(layers::Compositor *aCompositor, const IntSize& aSize) override;
void DestroyRenderTargetSet(RenderTargetSet *aRTSet) override;
void SubmitFrame(RenderTargetSet *aRTSet) override;
void SubmitFrame(RenderTargetSet *aRTSet, int32_t aInputFrameID) override;
ovrHmd GetOculusHMD() const { return mHMD; }
bool GetIsDebug() const;
int GetDeviceID() const;
ovrSession GetOculusSession() const { return mSession; }
protected:
virtual ~HMDInfoOculus() {
@@ -57,9 +55,6 @@ protected:
MOZ_COUNT_DTOR_INHERITED(HMDInfoOculus, VRHMDInfo);
}
bool StartSensorTracking();
void StopSensorTracking();
// must match the size of VRDistortionVertex
struct DistortionVertex {
float pos[2];
@@ -69,15 +64,11 @@ protected:
float genericAttribs[4];
};
ovrHmd mHMD;
ovrSession mSession;
ovrHmdDesc mDesc;
ovrFovPort mFOVPort[2];
bool mTracking;
ovrTrackingState mLastTrackingState;
bool mDebug; // True if this is a debug HMD
int mDeviceID; // Index of device passed to ovrHmd_Create
uint32_t mSensorTrackingFramesRemaining;
int mInputFrameID;
};
} // namespace impl
@@ -94,7 +85,7 @@ protected:
: mOculusInitialized(false)
{ }
nsTArray<RefPtr<impl::HMDInfoOculus>> mOculusHMDs;
RefPtr<impl::HMDInfoOculus> mHMDInfo;
bool mOculusInitialized;
RefPtr<nsIThread> mOculusThread;
};
+8
View File
@@ -24,6 +24,7 @@ void ReleaseVRManagerParentSingleton() {
}
VRManagerChild::VRManagerChild()
: mInputFrameID(-1)
{
MOZ_COUNT_CTOR(VRManagerChild);
MOZ_ASSERT(NS_IsMainThread());
@@ -157,6 +158,7 @@ VRManagerChild::RecvUpdateDeviceSensors(nsTArray<VRSensorUpdate>&& aDeviceSensor
for (auto& device: mDevices) {
if (device->GetDeviceInfo().GetDeviceID() == sensorUpdate.mDeviceID) {
device->UpdateSensorState(sensorUpdate.mSensorState);
mInputFrameID = sensorUpdate.mSensorState.inputFrameID;
break;
}
}
@@ -182,5 +184,11 @@ VRManagerChild::RefreshVRDevicesWithCallback(dom::Navigator* aNavigator)
return success;
}
int
VRManagerChild::GetInputFrameID()
{
return mInputFrameID;
}
} // namespace gfx
} // namespace mozilla
+2
View File
@@ -25,6 +25,7 @@ class VRManagerChild : public PVRManagerChild
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_MAIN_THREAD_DESTRUCTION(VRManagerChild)
int GetInputFrameID();
bool GetVRDevices(nsTArray<RefPtr<VRDeviceProxy> >& aDevices);
bool RefreshVRDevicesWithCallback(dom::Navigator* aNavigator);
static VRManagerChild* StartUpInChildProcess(Transport* aTransport,
@@ -52,6 +53,7 @@ private:
nsTArray<RefPtr<VRDeviceProxy> > mDevices;
nsTArray<dom::Navigator*> mNavigatorCallbacks;
int32_t mInputFrameID;
};
} // namespace mozilla
+2
View File
@@ -127,6 +127,7 @@ struct ParamTraits<mozilla::gfx::VRHMDSensorState>
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, aParam.timestamp);
WriteParam(aMsg, aParam.inputFrameID);
WriteParam(aMsg, aParam.flags);
WriteParam(aMsg, aParam.orientation[0]);
WriteParam(aMsg, aParam.orientation[1]);
@@ -152,6 +153,7 @@ struct ParamTraits<mozilla::gfx::VRHMDSensorState>
static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
{
if (!ReadParam(aMsg, aIter, &(aResult->timestamp)) ||
!ReadParam(aMsg, aIter, &(aResult->inputFrameID)) ||
!ReadParam(aMsg, aIter, &(aResult->flags)) ||
!ReadParam(aMsg, aIter, &(aResult->orientation[0])) ||
!ReadParam(aMsg, aIter, &(aResult->orientation[1])) ||
+341 -119
View File
@@ -64,16 +64,25 @@ extern "C" {
#endif
typedef int32_t ovrResult;
typedef char ovrBool;
typedef struct { int x, y; } ovrVector2i;
typedef struct { int w, h; } ovrSizei;
typedef struct { ovrVector2i Pos; ovrSizei Size; } ovrRecti;
typedef struct { float x, y, z, w; } ovrQuatf;
typedef struct { float x, y; } ovrVector2f;
typedef struct { float x, y, z; } ovrVector3f;
typedef struct { float M[4][4]; } ovrMatrix4f;
typedef struct {
typedef enum {
ovrSuccess = 0,
ovrSuccess_NotVisible = 1000,
ovrSuccess_HMDFirmwareMismatch = 4100,
ovrSuccess_TrackerFirmwareMismatch = 4101,
ovrSuccess_ControllerFirmwareMismatch = 4104,
} ovrSuccessType;
typedef char ovrBool;
typedef struct OVR_ALIGNAS(4) { int x, y; } ovrVector2i;
typedef struct OVR_ALIGNAS(4) { int w, h; } ovrSizei;
typedef struct OVR_ALIGNAS(4) { ovrVector2i Pos; ovrSizei Size; } ovrRecti;
typedef struct OVR_ALIGNAS(4) { float x, y, z, w; } ovrQuatf;
typedef struct OVR_ALIGNAS(4) { float x, y; } ovrVector2f;
typedef struct OVR_ALIGNAS(4) { float x, y, z; } ovrVector3f;
typedef struct OVR_ALIGNAS(4) { float M[4][4]; } ovrMatrix4f;
typedef struct OVR_ALIGNAS(4) {
ovrQuatf Orientation;
ovrVector3f Position;
} ovrPosef;
@@ -96,23 +105,23 @@ typedef struct {
} ovrFovPort;
typedef enum {
ovrHmd_None = 0,
ovrHmd_DK1 = 3,
ovrHmd_DKHD = 4,
ovrHmd_DK2 = 6,
ovrHmd_BlackStar = 7,
ovrHmd_CB = 8,
ovrHmd_Other = 9,
ovrHmd_EnumSize = 0x7fffffff
ovrHmd_None = 0,
ovrHmd_DK1 = 3,
ovrHmd_DKHD = 4,
ovrHmd_DK2 = 6,
ovrHmd_CB = 8,
ovrHmd_Other = 9,
ovrHmd_E3_2015 = 10,
ovrHmd_ES06 = 11,
ovrHmd_EnumSize = 0x7fffffff
} ovrHmdType;
typedef enum {
ovrHmdCap_Writable_Mask = 0x0000,
ovrHmdCap_Service_Mask = 0x0000,
ovrHmdCap_DebugDevice = 0x0010,
ovrHmdCap_LowPersistence = 0x0080,
ovrHmdCap_DynamicPrediction = 0x0200,
ovrHmdCap_NoVSync = 0x1000,
ovrHmdCap_EnumSize = 0x7fffffff
} ovrHmdCapBits;
} ovrHmdCaps;
typedef enum
{
@@ -123,6 +132,10 @@ typedef enum
ovrTrackingCap_EnumSize = 0x7fffffff
} ovrTrackingCaps;
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
char Reserved[8];
} ovrGraphicsLuid;
typedef enum {
ovrEye_Left = 0,
ovrEye_Right = 1,
@@ -131,11 +144,10 @@ typedef enum {
} ovrEyeType;
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
void* Handle;
ovrHmdType Type;
OVR_ON64(uint32_t pad0;)
const char* ProductName;
const char* Manufacturer;
OVR_ON64(unsigned char pad0[4];)
char ProductName[64];
char Manufacturer[64];
short VendorId;
short ProductId;
char SerialNumber[24];
@@ -146,17 +158,19 @@ typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
float CameraFrustumNearZInMeters;
float CameraFrustumFarZInMeters;
unsigned int HmdCaps;
unsigned int TrackingCaps;
unsigned int AvailableHmdCaps;
unsigned int DefaultHmdCaps;
unsigned int AvailableTrackingCaps;
unsigned int DefaultTrackingCaps;
ovrFovPort DefaultEyeFov[ovrEye_Count];
ovrFovPort MaxEyeFov[ovrEye_Count];
ovrEyeType EyeRenderOrder[ovrEye_Count];
ovrSizei Resolution;
float DisplayRefreshRate;
OVR_ON64(unsigned char pad1[4];)
} ovrHmdDesc;
typedef const ovrHmdDesc* ovrHmd;
typedef struct ovrHmdStruct* ovrSession;
typedef enum {
ovrStatus_OrientationTracked = 0x0001,
@@ -180,47 +194,40 @@ typedef struct OVR_ALIGNAS(8) {
ovrPoseStatef HeadPose;
ovrPosef CameraPose;
ovrPosef LeveledCameraPose;
ovrPoseStatef HandPoses[2];
ovrSensorData RawSensorData;
unsigned int StatusFlags;
unsigned int HandStatusFlags[2];
uint32_t LastCameraFrameCounter;
uint32_t pad0;
unsigned char pad0[4];
} ovrTrackingState;
typedef struct OVR_ALIGNAS(8) {
double DisplayMidpointSeconds;
double FrameIntervalSeconds;
unsigned AppFrameIndex;
unsigned DisplayFrameIndex;
} ovrFrameTiming;
typedef struct OVR_ALIGNAS(4) {
ovrEyeType Eye;
ovrFovPort Fov;
ovrRecti DistortedViewport;
ovrVector2f PixelsPerTanAngleAtCenter;
ovrVector3f HmdToEyeViewOffset;
ovrEyeType Eye;
ovrFovPort Fov;
ovrRecti DistortedViewport;
ovrVector2f PixelsPerTanAngleAtCenter;
ovrVector3f HmdToEyeViewOffset;
} ovrEyeRenderDesc;
typedef struct OVR_ALIGNAS(4) {
float Projection22;
float Projection23;
float Projection32;
float Projection22;
float Projection23;
float Projection32;
} ovrTimewarpProjectionDesc;
typedef struct OVR_ALIGNAS(4) {
ovrVector3f HmdToEyeViewOffset[ovrEye_Count];
float HmdSpaceToWorldScaleInMeters;
ovrVector3f HmdToEyeViewOffset[ovrEye_Count];
float HmdSpaceToWorldScaleInMeters;
} ovrViewScaleDesc;
typedef enum {
ovrRenderAPI_None,
ovrRenderAPI_OpenGL,
ovrRenderAPI_Android_GLES,
ovrRenderAPI_D3D9_Obsolete,
ovrRenderAPI_D3D10_Obsolete,
ovrRenderAPI_D3D11,
ovrRenderAPI_Count,
ovrRenderAPI_EnumSize = 0x7fffffff
ovrRenderAPI_None = 0,
ovrRenderAPI_OpenGL = 1,
ovrRenderAPI_Android_GLES = 2,
ovrRenderAPI_D3D11 = 5,
ovrRenderAPI_Count = 4,
ovrRenderAPI_EnumSize = 0x7fffffff
} ovrRenderAPIType;
typedef struct OVR_ALIGNAS(4) {
@@ -230,7 +237,7 @@ typedef struct OVR_ALIGNAS(4) {
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
ovrTextureHeader Header;
OVR_ON64(uint32_t pad0;)
OVR_ON64(unsigned char pad0[4];)
uintptr_t PlatformData[8];
} ovrTexture;
@@ -240,11 +247,72 @@ typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
int CurrentIndex;
} ovrSwapTextureSet;
typedef enum {
ovrButton_A = 0x00000001,
ovrButton_B = 0x00000002,
ovrButton_RThumb = 0x00000004,
ovrButton_RShoulder = 0x00000008,
ovrButton_X = 0x00000100,
ovrButton_Y = 0x00000200,
ovrButton_LThumb = 0x00000400,
ovrButton_LShoulder = 0x00000800,
ovrButton_Up = 0x00010000,
ovrButton_Down = 0x00020000,
ovrButton_Left = 0x00040000,
ovrButton_Right = 0x00080000,
ovrButton_Enter = 0x00100000,
ovrButton_Back = 0x00200000,
ovrButton_Private = 0x00400000 | 0x00800000 | 0x01000000,
ovrButton_EnumSize = 0x7fffffff
} ovrButton;
typedef enum {
ovrTouch_A = ovrButton_A,
ovrTouch_B = ovrButton_B,
ovrTouch_RThumb = ovrButton_RThumb,
ovrTouch_RIndexTrigger = 0x00000010,
ovrTouch_X = ovrButton_X,
ovrTouch_Y = ovrButton_Y,
ovrTouch_LThumb = ovrButton_LThumb,
ovrTouch_LIndexTrigger = 0x00001000,
ovrTouch_RIndexPointing = 0x00000020,
ovrTouch_RThumbUp = 0x00000040,
ovrTouch_LIndexPointing = 0x00002000,
ovrTouch_LThumbUp = 0x00004000,
ovrTouch_EnumSize = 0x7fffffff
} ovrTouch;
typedef enum {
ovrControllerType_None = 0x00,
ovrControllerType_LTouch = 0x01,
ovrControllerType_RTouch = 0x02,
ovrControllerType_Touch = 0x03,
ovrControllerType_XBox = 0x10,
ovrControllerType_All = 0xff,
ovrControllerType_EnumSize = 0x7fffffff
} ovrControllerType;
typedef enum {
ovrHand_Left = 0,
ovrHand_Right = 1,
ovrHand_EnumSize = 0x7fffffff
} ovrHandType;
typedef struct {
double TimeInSeconds;
unsigned int ConnectedControllerTypes;
unsigned int Buttons;
unsigned int Touches;
float IndexTrigger[2];
float HandTrigger[2];
ovrVector2f Thumbstick[2];
} ovrInputState;
typedef enum {
ovrInit_Debug = 0x00000001,
ovrInit_ServerOptional = 0x00000002,
ovrInit_RequestVersion = 0x00000004,
ovrInit_ForceNoDebug = 0x00000008,
ovrinit_WritableBits = 0x00ffffff,
ovrInit_EnumSize = 0x7fffffff
} ovrInitFlags;
@@ -255,19 +323,65 @@ typedef enum {
ovrLogLevel_EnumSize = 0x7fffffff
} ovrLogLevel;
typedef void (OVR_PFN *ovrLogCallback)(int level, const char* message);
typedef struct OVR_ALIGNAS(8) {
uint32_t Flags;
uint32_t RequestedMinorVersion;
ovrLogCallback LogCallback;
uintptr_t UserData;
uint32_t ConnectionTimeoutMS;
OVR_ON64(unsigned char pad0[4];)
} ovrInitParams;
typedef ovrResult(OVR_PFN *pfn_ovr_Initialize)(ovrInitParams const* params);
typedef void (OVR_PFN *pfn_ovr_Shutdown)();
typedef struct {
ovrResult Result;
char ErrorString[512];
} ovrErrorInfo;
typedef void (OVR_PFN *pfn_ovr_GetLastErrorInfo)(ovrErrorInfo* errorInfo);
typedef const char* (OVR_PFN *pfn_ovr_GetVersionString)();
typedef int (OVR_PFN *pfn_ovr_TraceMessage)(int level, const char* message);
typedef ovrHmdDesc (OVR_PFN *pfn_ovr_GetHmdDesc)(ovrSession session);
typedef ovrResult (OVR_PFN *pfn_ovr_Create)(ovrSession*, ovrGraphicsLuid*);
typedef void (OVR_PFN *pfn_ovr_Destroy)(ovrSession session);
typedef struct {
ovrBool HasVrFocus;
ovrBool HmdPresent;
} ovrSessionStatus;
typedef ovrResult (OVR_PFN *pfn_ovr_GetSessionStatus)(ovrSession session, ovrSessionStatus* sessionStatus);
typedef unsigned int (OVR_PFN *pfn_ovr_GetEnabledCaps)(ovrSession session);
typedef void (OVR_PFN *pfn_ovr_SetEnabledCaps)(ovrSession session, unsigned int hmdCaps);
typedef unsigned int (OVR_PFN *pfn_ovr_GetTrackingCaps)(ovrSession session);
typedef ovrResult(OVR_PFN *pfn_ovr_ConfigureTracking)(ovrSession session, unsigned int requestedTrackingCaps, unsigned int requiredTrackingCaps);
typedef void (OVR_PFN *pfn_ovr_RecenterPose)(ovrSession session);
typedef ovrTrackingState (OVR_PFN *pfn_ovr_GetTrackingState)(ovrSession session, double absTime, ovrBool latencyMarker);
typedef ovrResult (OVR_PFN *pfn_ovr_GetInputState)(ovrSession session, unsigned int controllerTypeMask, ovrInputState* inputState);
typedef ovrResult (OVR_PFN *pfn_ovr_SetControllerVibration)(ovrSession session, unsigned int controllerTypeMask, float frequency, float amplitude);
enum {
ovrMaxLayerCount = 32
};
typedef enum {
ovrLayerType_Disabled = 0,
ovrLayerType_EyeFov = 1,
ovrLayerType_EyeFovDepth = 2,
ovrLayerType_QuadInWorld = 3,
ovrLayerType_QuadHeadLocked = 4,
ovrLayerType_Quad = 3,
ovrLayerType_EyeMatrix = 5,
ovrLayerType_Direct = 6,
ovrLayerType_EnumSize = 0x7fffffff
} ovrLayerType;
typedef enum {
ovrLayerFlag_HighQuality = 0x01,
ovrLayerFlag_TextureOriginAtBottomLeft = 0x02
ovrLayerFlag_TextureOriginAtBottomLeft = 0x02,
ovrLayerFlag_HeadLocked = 0x04
} ovrLayerFlags;
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
@@ -281,66 +395,157 @@ typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
ovrRecti Viewport[ovrEye_Count];
ovrFovPort Fov[ovrEye_Count];
ovrPosef RenderPose[ovrEye_Count];
double SensorSampleTime;
} ovrLayerEyeFov;
typedef void (OVR_PFN *ovrLogCallback)(int level, const char* message);
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
ovrLayerHeader Header;
ovrSwapTextureSet* ColorTexture[ovrEye_Count];
ovrRecti Viewport[ovrEye_Count];
ovrFovPort Fov[ovrEye_Count];
ovrPosef RenderPose[ovrEye_Count];
double SensorSampleTime;
ovrSwapTextureSet* DepthTexture[ovrEye_Count];
ovrTimewarpProjectionDesc ProjectionDesc;
} ovrLayerEyeFovDepth;
typedef struct OVR_ALIGNAS(8) {
uint32_t Flags;
uint32_t RequestedMinorVersion;
ovrLogCallback LogCallback;
uint32_t ConnectionTimeoutMS;
OVR_ON64(uint32_t pad0;)
} ovrInitParams;
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
ovrLayerHeader Header;
ovrSwapTextureSet* ColorTexture[ovrEye_Count];
ovrRecti Viewport[ovrEye_Count];
ovrPosef RenderPose[ovrEye_Count];
ovrMatrix4f Matrix[ovrEye_Count];
double SensorSampleTime;
} ovrLayerEyeMatrix;
enum {
ovrSuccess = 0,
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
ovrLayerHeader Header;
ovrSwapTextureSet* ColorTexture;
ovrRecti Viewport;
ovrPosef QuadPoseCenter;
ovrVector2f QuadSize;
} ovrLayerQuad;
ovrError_MemoryAllocationFailure = -1000,
ovrError_SocketCreationFailure = -1001,
ovrError_InvalidHmd = -1002,
ovrError_Timeout = -1003,
ovrError_NotInitialized = -1004,
ovrError_InvalidParameter = -1005,
ovrError_ServiceError = -1006,
ovrError_NoHmd = -1007,
typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) {
ovrLayerHeader Header;
ovrSwapTextureSet* ColorTexture[ovrEye_Count];
ovrRecti Viewport[ovrEye_Count];
} ovrLayerDirect;
ovrError_AudioReservedBegin = -2000,
ovrError_AudioReservedEnd = -2999,
typedef union {
ovrLayerHeader Header;
ovrLayerEyeFov EyeFov;
ovrLayerEyeFovDepth EyeFovDepth;
ovrLayerQuad Quad;
ovrLayerDirect Direct;
} ovrLayer_Union;
ovrError_Initialize = -3000,
ovrError_LibLoad = -3001,
ovrError_LibVersion = -3002,
ovrError_ServiceConnection = -3003,
ovrError_ServiceVersion = -3004,
ovrError_IncompatibleOS = -3005,
ovrError_DisplayInit = -3006,
ovrError_ServerStart = -3007,
ovrError_Reinitialization = -3008,
ovrError_InvalidBundleAdjustment = -4000,
ovrError_USBBandwidth = -4001
};
typedef ovrResult (OVR_PFN *pfn_ovr_Initialize)(ovrInitParams const* params);
typedef void (OVR_PFN *pfn_ovr_Shutdown)();
typedef void (OVR_PFN *pfn_ovr_DestroySwapTextureSet)(ovrSession session, ovrSwapTextureSet* textureSet);
typedef void (OVR_PFN *pfn_ovr_DestroyMirrorTexture)(ovrSession session, ovrTexture* mirrorTexture);
typedef ovrSizei (OVR_PFN *pfn_ovr_GetFovTextureSize)(ovrSession session, ovrEyeType eye, ovrFovPort fov, float pixelsPerDisplayPixel);
typedef ovrEyeRenderDesc (OVR_PFN *pfn_ovr_GetRenderDesc)(ovrSession session, ovrEyeType eyeType, ovrFovPort fov);
typedef ovrResult (OVR_PFN *pfn_ovr_SubmitFrame)(ovrSession session, unsigned int frameIndex,
const ovrViewScaleDesc* viewScaleDesc,
ovrLayerHeader const * const * layerPtrList, unsigned int layerCount);
typedef double (OVR_PFN *pfn_ovr_GetPredictedDisplayTime)(ovrSession session, long long frameIndex);
typedef double (OVR_PFN *pfn_ovr_GetTimeInSeconds)();
typedef ovrResult (OVR_PFN *pfn_ovrHmd_Detect)();
typedef ovrResult (OVR_PFN *pfn_ovrHmd_Create)(int index, ovrHmd*);
typedef ovrResult (OVR_PFN *pfn_ovrHmd_CreateDebug)(ovrHmdType type, ovrHmd*);
typedef void (OVR_PFN *pfn_ovrHmd_Destroy)(ovrHmd hmd);
typedef ovrResult (OVR_PFN *pfn_ovrHmd_ConfigureTracking)(ovrHmd hmd, unsigned int supportedTrackingCaps, unsigned int requiredTrackingCaps);
typedef void (OVR_PFN *pfn_ovrHmd_RecenterPose)(ovrHmd hmd);
typedef ovrTrackingState (OVR_PFN *pfn_ovrHmd_GetTrackingState)(ovrHmd hmd, double absTime);
typedef ovrSizei (OVR_PFN *pfn_ovrHmd_GetFovTextureSize)(ovrHmd hmd, ovrEyeType eye, ovrFovPort fov, float pixelsPerDisplayPixel);
typedef ovrEyeRenderDesc (OVR_PFN *pfn_ovrHmd_GetRenderDesc)(ovrHmd hmd, ovrEyeType eyeType, ovrFovPort fov);
typedef void (OVR_PFN *pfn_ovrHmd_DestroySwapTextureSet)(ovrHmd hmd, ovrSwapTextureSet* textureSet);
typedef ovrResult (OVR_PFN *pfn_ovrHmd_SubmitFrame)(ovrHmd hmd, unsigned int frameIndex,
const ovrViewScaleDesc* viewScaleDesc,
ovrLayerHeader const * const * layerPtrList, unsigned int layerCount);
typedef enum {
ovrPerfHud_Off = 0,
ovrPerfHud_LatencyTiming = 1,
ovrPerfHud_RenderTiming = 2,
ovrPerfHud_PerfHeadroom = 3,
ovrPerfHud_VersionInfo = 4,
ovrPerfHud_Count,
ovrPerfHud_EnumSize = 0x7fffffff
} ovrPerfHudMode;
typedef enum {
ovrLayerHud_Off = 0,
ovrLayerHud_Info = 1,
ovrLayerHud_EnumSize = 0x7fffffff
} ovrLayerHudMode;
typedef enum {
ovrDebugHudStereo_Off = 0,
ovrDebugHudStereo_Quad = 1,
ovrDebugHudStereo_QuadWithCrosshair = 2,
ovrDebugHudStereo_CrosshairAtInfinity = 3,
ovrDebugHudStereo_Count,
ovrDebugHudStereo_EnumSize = 0x7fffffff
} ovrDebugHudStereoMode;
typedef void (OVR_PFN *pfn_ovr_ResetBackOfHeadTracking)(ovrSession session);
typedef void (OVR_PFN *pfn_ovr_ResetMulticameraTracking)(ovrSession session);
typedef ovrBool (OVR_PFN *pfn_ovr_GetBool)(ovrSession session, const char* propertyName, ovrBool defaultVal);
typedef ovrBool (OVR_PFN *pfn_ovr_SetBool)(ovrSession session, const char* propertyName, ovrBool value);
typedef int (OVR_PFN *pfn_ovr_GetInt)(ovrSession session, const char* propertyName, int defaultVal);
typedef ovrBool (OVR_PFN *pfn_ovr_SetInt)(ovrSession session, const char* propertyName, int value);
typedef float (OVR_PFN *pfn_ovr_GetFloat)(ovrSession session, const char* propertyName, float defaultVal);
typedef ovrBool (OVR_PFN *pfn_ovr_SetFloat)(ovrSession session, const char* propertyName, float value);
typedef unsigned int (OVR_PFN *pfn_ovr_GetFloatArray)(ovrSession session, const char* propertyName,
float values[], unsigned int valuesCapacity);
typedef ovrBool (OVR_PFN *pfn_ovr_SetFloatArray)(ovrSession session, const char* propertyName,
const float values[], unsigned int valuesSize);
typedef const char* (OVR_PFN *pfn_ovr_GetString)(ovrSession session, const char* propertyName,
const char* defaultVal);
typedef ovrBool (OVR_PFN *pfn_ovr_SetString)(ovrSession session, const char* propertyName,
const char* value);
typedef enum {
ovrError_MemoryAllocationFailure = -1000,
ovrError_SocketCreationFailure = -1001,
ovrError_InvalidSession = -1002,
ovrError_Timeout = -1003,
ovrError_NotInitialized = -1004,
ovrError_InvalidParameter = -1005,
ovrError_ServiceError = -1006,
ovrError_NoHmd = -1007,
ovrError_AudioReservedBegin = -2000,
ovrError_AudioDeviceNotFound = -2001,
ovrError_AudioComError = -2002,
ovrError_AudioReservedEnd = -2999,
ovrError_Initialize = -3000,
ovrError_LibLoad = -3001,
ovrError_LibVersion = -3002,
ovrError_ServiceConnection = -3003,
ovrError_ServiceVersion = -3004,
ovrError_IncompatibleOS = -3005,
ovrError_DisplayInit = -3006,
ovrError_ServerStart = -3007,
ovrError_Reinitialization = -3008,
ovrError_MismatchedAdapters = -3009,
ovrError_LeakingResources = -3010,
ovrError_ClientVersion = -3011,
ovrError_OutOfDateOS = -3012,
ovrError_OutOfDateGfxDriver = -3013,
ovrError_IncompatibleGPU = -3014,
ovrError_NoValidVRDisplaySystem = -3015,
ovrError_InvalidBundleAdjustment = -4000,
ovrError_USBBandwidth = -4001,
ovrError_USBEnumeratedSpeed = -4002,
ovrError_ImageSensorCommError = -4003,
ovrError_GeneralTrackerFailure = -4004,
ovrError_ExcessiveFrameTruncation = -4005,
ovrError_ExcessiveFrameSkipping = -4006,
ovrError_SyncDisconnected = -4007,
ovrError_TrackerMemoryReadFailure = -4008,
ovrError_TrackerMemoryWriteFailure = -4009,
ovrError_TrackerFrameTimeout = -4010,
ovrError_TrackerTruncatedFrame = -4011,
ovrError_HMDFirmwareMismatch = -4100,
ovrError_TrackerFirmwareMismatch = -4101,
ovrError_BootloaderDeviceDetected = -4102,
ovrError_TrackerCalibrationError = -4103,
ovrError_ControllerFirmwareMismatch = -4104,
ovrError_Incomplete = -5000,
ovrError_Abandoned = -5001,
ovrError_DisplayLost = -6000,
ovrError_RuntimeException = -7000,
} ovrErrorType;
#ifdef XP_WIN
struct D3D11_TEXTURE2D_DESC;
@@ -360,9 +565,22 @@ typedef union {
ovrD3D11TextureData D3D11;
} ovrD3D11Texture;
typedef ovrResult (OVR_PFN *pfn_ovrHmd_CreateSwapTextureSetD3D11)(ovrHmd hmd, ID3D11Device* device,
const D3D11_TEXTURE2D_DESC* desc,
ovrSwapTextureSet** outTextureSet);
typedef enum {
ovrSwapTextureSetD3D11_Typeless = 0x0001,
ovrSwapTextureSetD3D11_EnumSize = 0x7fffffff
} ovrSwapTextureSetD3D11Flags;
typedef ovrResult (OVR_PFN *pfn_ovr_CreateSwapTextureSetD3D11)(ovrSession session, ID3D11Device* device,
const D3D11_TEXTURE2D_DESC* desc,
unsigned int miscFlags,
ovrSwapTextureSet** outTextureSet);
typedef ovrResult (OVR_PFN *pfn_ovr_CreateMirrorTextureD3D11)(ovrSession session,
ID3D11Device* device,
const D3D11_TEXTURE2D_DESC* desc,
unsigned int miscFlags,
ovrTexture** outMirrorTexture);
#endif
typedef struct {
@@ -375,9 +593,13 @@ typedef union {
ovrGLTextureData OGL;
} ovrGLTexture;
typedef ovrResult (OVR_PFN *pfn_ovrHmd_CreateSwapTextureSetGL)(ovrHmd hmd, uint32_t format,
int width, int height,
ovrSwapTextureSet** outTextureSet);
typedef ovrResult (OVR_PFN *pfn_ovr_CreateSwapTextureSetGL)(ovrSession session, uint32_t format,
int width, int height,
ovrSwapTextureSet** outTextureSet);
typedef ovrResult (OVR_PFN *pfn_ovr_CreateMirrorTextureGL)(ovrSession session, uint32_t format,
int width, int height,
ovrTexture** outMirrorTexture);
#ifdef __cplusplus
}
+24 -28
View File
@@ -1659,6 +1659,14 @@ class MOZ_STACK_CLASS ModuleValidator
return false;
return standardLibrarySimdOpNames_.putNew(atom->asPropertyName(), op);
}
bool newSig(Sig&& sig, uint32_t* sigIndex) {
if (mg_.numSigs() >= MaxSigs)
return failCurrentOffset("too many signatures");
*sigIndex = mg_.numSigs();
mg_.initSig(*sigIndex, Move(sig));
return true;
}
bool declareSig(Sig&& sig, uint32_t* sigIndex) {
SigMap::AddPtr p = sigMap_.lookupForAdd(sig);
if (p) {
@@ -1667,12 +1675,8 @@ class MOZ_STACK_CLASS ModuleValidator
return true;
}
*sigIndex = sigMap_.count();
if (*sigIndex >= MaxSigs)
return failCurrentOffset("too many unique signatures");
mg_.initSig(*sigIndex, Move(sig));
return sigMap_.add(p, &mg_.sig(*sigIndex), *sigIndex);
return newSig(Move(sig), sigIndex) &&
sigMap_.add(p, &mg_.sig(*sigIndex), *sigIndex);
}
public:
@@ -1783,7 +1787,8 @@ class MOZ_STACK_CLASS ModuleValidator
if (!genData ||
!genData->sigs.resize(MaxSigs) ||
!genData->funcSigs.resize(MaxFuncs) ||
!genData->imports.resize(MaxImports))
!genData->imports.resize(MaxImports) ||
!genData->sigToTable.resize(MaxSigs))
{
return false;
}
@@ -2040,27 +2045,28 @@ class MOZ_STACK_CLASS ModuleValidator
bool declareFuncPtrTable(Sig&& sig, PropertyName* name, uint32_t firstUse, uint32_t mask,
uint32_t* index)
{
if (!mg_.declareFuncPtrTable(/* numElems = */ mask + 1, index))
if (mask > MaxTableElems)
return failCurrentOffset("function pointer table too big");
uint32_t sigIndex;
if (!newSig(Move(sig), &sigIndex))
return false;
if (!mg_.initSigTableLength(sigIndex, mask + 1))
return false;
MOZ_ASSERT(*index == numFuncPtrTables());
Global* global = validationLifo_.new_<Global>(Global::FuncPtrTable);
if (!global)
return false;
global->u.funcPtrTableIndex_ = *index;
global->u.funcPtrTableIndex_ = *index = funcPtrTables_.length();
if (!globalMap_.putNew(name, global))
return false;
uint32_t sigIndex;
if (!declareSig(Move(sig), &sigIndex))
return false;
FuncPtrTable* t = validationLifo_.new_<FuncPtrTable>(sigIndex, name, firstUse, mask);
return t && funcPtrTables_.append(t);
}
bool defineFuncPtrTable(uint32_t funcPtrTableIndex, const Vector<uint32_t>& elems) {
bool defineFuncPtrTable(uint32_t funcPtrTableIndex, Uint32Vector&& elems) {
FuncPtrTable& table = *funcPtrTables_[funcPtrTableIndex];
if (table.defined())
return false;
table.define();
mg_.defineFuncPtrTable(funcPtrTableIndex, elems);
mg_.initSigTableElems(table.sigIndex(), Move(elems));
return true;
}
bool declareImport(PropertyName* name, Sig&& sig, unsigned ffiIndex, uint32_t* importIndex) {
@@ -4438,7 +4444,7 @@ CheckFuncPtrTableAgainstExisting(ModuleValidator& m, ParseNode* usepn, PropertyN
return false;
if (!m.declareFuncPtrTable(Move(sig), name, usepn->pn_pos.begin, mask, funcPtrTableIndex))
return m.fail(usepn, "table too big");
return false;
return true;
}
@@ -4473,15 +4479,6 @@ CheckFuncPtrCall(FunctionValidator& f, ParseNode* callNode, ExprType ret, Type*
if (!f.writeCall(callNode, Expr::CallIndirect))
return false;
// Table's mask
if (!f.writeU32(mask))
return false;
// Global data offset
size_t globalDataOffsetAt;
if (!f.temp32(&globalDataOffsetAt))
return false;
// Call signature
size_t sigIndexAt;
if (!f.temp32(&sigIndexAt))
@@ -4504,7 +4501,6 @@ CheckFuncPtrCall(FunctionValidator& f, ParseNode* callNode, ExprType ret, Type*
if (!CheckFuncPtrTableAgainstExisting(f.m(), tableNode, name, Move(sig), mask, &tableIndex))
return false;
f.patch32(globalDataOffsetAt, f.m().mg().funcPtrTableGlobalDataOffset(tableIndex));
f.patch32(sigIndexAt, f.m().funcPtrTable(tableIndex).sigIndex());
*type = Type::ret(ret);
@@ -6750,7 +6746,7 @@ CheckFuncPtrTable(ModuleValidator& m, ParseNode* var)
unsigned mask = length - 1;
Vector<uint32_t> elemFuncIndices(m.cx());
Uint32Vector elemFuncIndices;
const Sig* sig = nullptr;
for (ParseNode* elem = ListHead(arrayLiteral); elem; elem = NextNode(elem)) {
if (!elem->isKind(PNK_NAME))
@@ -6781,7 +6777,7 @@ CheckFuncPtrTable(ModuleValidator& m, ParseNode* var)
if (!CheckFuncPtrTableAgainstExisting(m, var, var->name(), Move(copy), mask, &tableIndex))
return false;
if (!m.defineFuncPtrTable(tableIndex, elemFuncIndices))
if (!m.defineFuncPtrTable(tableIndex, Move(elemFuncIndices)))
return m.fail(var, "duplicate function-pointer definition");
return true;
+4
View File
@@ -30,6 +30,8 @@
using namespace js;
using namespace js::wasm;
using mozilla::IsNaN;
typedef Handle<WasmModuleObject*> HandleWasmModule;
typedef MutableHandle<WasmModuleObject*> MutableHandleWasmModule;
@@ -605,6 +607,8 @@ DecodeSignatureSection(JSContext* cx, Decoder& d, ModuleGeneratorData* init)
if (!init->sigs.resize(numSigs))
return false;
if (!init->sigToTable.resize(numSigs))
return false;
SigSet dupSet(cx);
if (!dupSet.init())
+1
View File
@@ -35,6 +35,7 @@ static const unsigned MaxSigs = 4 * 1024;
static const unsigned MaxFuncs = 512 * 1024;
static const unsigned MaxImports = 4 * 1024;
static const unsigned MaxExports = 4 * 1024;
static const unsigned MaxTableElems = 128 * 1024;
static const unsigned MaxArgsPerFunc = 4 * 1024;
// Module header constants
+17 -2
View File
@@ -96,6 +96,7 @@ FrameIterator::settle()
case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit:
case CodeRange::Inline:
case CodeRange::CallThunk:
MOZ_CRASH("Should not encounter an exit during iteration");
}
}
@@ -491,6 +492,7 @@ ProfilingFrameIterator::initFromFP(const WasmActivation& activation)
case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit:
case CodeRange::Inline:
case CodeRange::CallThunk:
MOZ_CRASH("Unexpected CodeRange kind");
}
@@ -540,6 +542,7 @@ ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
const CodeRange* codeRange = module_->lookupCodeRange(state.pc);
switch (codeRange->kind()) {
case CodeRange::Function:
case CodeRange::CallThunk:
case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit: {
// When the pc is inside the prologue/epilogue, the innermost
@@ -556,7 +559,7 @@ ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
uint32_t offsetInCodeRange = offsetInModule - codeRange->begin();
void** sp = (void**)state.sp;
#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
if (offsetInCodeRange < PushedRetAddr) {
if (offsetInCodeRange < PushedRetAddr || codeRange->kind() == CodeRange::CallThunk) {
// First instruction of the ARM/MIPS function; the return address is
// still in lr and fp still holds the caller's fp.
callerPC_ = state.lr;
@@ -570,7 +573,9 @@ ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
AssertMatchesCallSite(*module_, callerPC_, callerFP_, sp);
} else
#endif
if (offsetInCodeRange < PushedFP || offsetInModule == codeRange->profilingReturn()) {
if (offsetInCodeRange < PushedFP || offsetInModule == codeRange->profilingReturn() ||
codeRange->kind() == CodeRange::CallThunk)
{
// The return address has been pushed on the stack but not fp; fp
// still points to the caller's fp.
callerPC_ = *sp;
@@ -654,6 +659,7 @@ ProfilingFrameIterator::operator++()
case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit:
case CodeRange::Inline:
case CodeRange::CallThunk:
stackAddress_ = callerFP_;
callerPC_ = ReturnAddressFromFP(callerFP_);
AssertMatchesCallSite(*module_, callerPC_, CallerFPFromFP(callerFP_), callerFP_);
@@ -695,6 +701,7 @@ ProfilingFrameIterator::label() const
case CodeRange::ImportJitExit: return importJitDescription;
case CodeRange::ImportInterpExit: return importInterpDescription;
case CodeRange::Inline: return "inline stub (in asm.js)";
case CodeRange::CallThunk: return "call thunk (in asm.js)";
}
MOZ_CRASH("bad code range kind");
@@ -770,6 +777,14 @@ wasm::EnableProfilingPrologue(const Module& module, const CallSite& callSite, bo
#endif
}
void
wasm::EnableProfilingThunk(const Module& module, const CallThunk& callThunk, bool enabled)
{
const CodeRange& cr = module.codeRanges()[callThunk.u.codeRangeIndex];
uint32_t calleeOffset = enabled ? cr.funcProfilingEntry() : cr.funcNonProfilingEntry();
MacroAssembler::repatchThunk(module.code(), callThunk.offset, calleeOffset);
}
// Replace all the nops in all the epilogues of asm.js functions with jumps
// to the profiling epilogues.
void
+4
View File
@@ -33,6 +33,7 @@ namespace wasm {
class CallSite;
class CodeRange;
class Module;
struct CallThunk;
struct FuncOffsets;
struct ProfilingOffsets;
@@ -112,6 +113,9 @@ GenerateFunctionEpilogue(jit::MacroAssembler& masm, unsigned framePushed, FuncOf
void
EnableProfilingPrologue(const Module& module, const CallSite& callSite, bool enabled);
void
EnableProfilingThunk(const Module& module, const CallThunk& callThunk, bool enabled);
void
EnableProfilingEpilogue(const Module& module, const CodeRange& codeRange, bool enabled);
+357 -186
View File
@@ -18,6 +18,8 @@
#include "asmjs/WasmGenerator.h"
#include "mozilla/EnumeratedRange.h"
#include "asmjs/WasmStubs.h"
#include "jit/MacroAssembler-inl.h"
@@ -26,6 +28,8 @@ using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::MakeEnumeratedRange;
// ****************************************************************************
// ModuleGenerator
@@ -41,6 +45,8 @@ ModuleGenerator::ModuleGenerator(ExclusiveContext* cx)
alloc_(&lifo_),
masm_(MacroAssembler::AsmJSToken(), alloc_),
funcIndexToExport_(cx),
lastPatchedCallsite_(0),
startOfUnpatchedBranches_(0),
parallel_(false),
outstanding_(0),
tasks_(cx),
@@ -117,10 +123,6 @@ ModuleGenerator::init(UniqueModuleGeneratorData shared, UniqueChars filename, Mo
module_->heapUsage = HeapUsage::None;
module_->filename = Move(filename);
link_ = MakeUnique<StaticLinkData>();
if (!link_)
return false;
exportMap_ = MakeUnique<ExportMap>();
if (!exportMap_)
return false;
@@ -134,7 +136,7 @@ ModuleGenerator::init(UniqueModuleGeneratorData shared, UniqueChars filename, Mo
numSigs_ = shared_->sigs.length();
module_->numFuncs = shared_->funcSigs.length();
module_->globalBytes = AlignBytes(module_->globalBytes, sizeof(void*));
for (ModuleImportGeneratorData& import : shared_->imports) {
for (ImportModuleGeneratorData& import : shared_->imports) {
MOZ_ASSERT(!import.globalDataOffset);
import.globalDataOffset = module_->globalBytes;
module_->globalBytes += Module::SizeOfImportExit;
@@ -173,25 +175,146 @@ ModuleGenerator::finishOutstandingTask()
return finishTask(task);
}
static const uint32_t BadCodeRange = UINT32_MAX;
bool
ModuleGenerator::funcIsDefined(uint32_t funcIndex) const
{
return funcIndex < funcIndexToCodeRange_.length() &&
funcIndexToCodeRange_[funcIndex] != BadCodeRange;
}
uint32_t
ModuleGenerator::funcEntry(uint32_t funcIndex) const
{
MOZ_ASSERT(funcIsDefined(funcIndex));
return module_->codeRanges[funcIndexToCodeRange_[funcIndex]].funcNonProfilingEntry();
}
static uint32_t
JumpRange()
{
return Min(JitOptions.jumpThreshold, JumpImmediateRange);
}
typedef HashMap<uint32_t, uint32_t> OffsetMap;
bool
ModuleGenerator::convertOutOfRangeBranchesToThunks()
{
masm_.haltingAlign(CodeAlignment);
// Create thunks for callsites that have gone out of range. Use a map to
// create one thunk for each callee since there is often high reuse.
OffsetMap alreadyThunked(cx_);
if (!alreadyThunked.init())
return false;
for (; lastPatchedCallsite_ < masm_.callSites().length(); lastPatchedCallsite_++) {
const CallSiteAndTarget& cs = masm_.callSites()[lastPatchedCallsite_];
if (!cs.isInternal())
continue;
uint32_t callerOffset = cs.returnAddressOffset();
MOZ_RELEASE_ASSERT(callerOffset < INT32_MAX);
if (funcIsDefined(cs.targetIndex())) {
uint32_t calleeOffset = funcEntry(cs.targetIndex());
MOZ_RELEASE_ASSERT(calleeOffset < INT32_MAX);
if (uint32_t(abs(int32_t(calleeOffset) - int32_t(callerOffset))) < JumpRange()) {
masm_.patchCall(callerOffset, calleeOffset);
continue;
}
}
OffsetMap::AddPtr p = alreadyThunked.lookupForAdd(cs.targetIndex());
if (!p) {
Offsets offsets;
offsets.begin = masm_.currentOffset();
uint32_t thunkOffset = masm_.thunkWithPatch().offset();
if (masm_.oom())
return false;
offsets.end = masm_.currentOffset();
if (!module_->codeRanges.emplaceBack(CodeRange::CallThunk, offsets))
return false;
if (!module_->callThunks.emplaceBack(thunkOffset, cs.targetIndex()))
return false;
if (!alreadyThunked.add(p, cs.targetIndex(), offsets.begin))
return false;
}
masm_.patchCall(callerOffset, p->value());
}
// Create thunks for jumps to stubs. Stubs are always generated at the end
// so unconditionally thunk all existing jump sites.
for (JumpTarget target : MakeEnumeratedRange(JumpTarget::Limit)) {
if (masm_.jumpSites()[target].empty())
continue;
for (uint32_t jumpSite : masm_.jumpSites()[target]) {
RepatchLabel label;
label.use(jumpSite);
masm_.bind(&label);
}
Offsets offsets;
offsets.begin = masm_.currentOffset();
uint32_t thunkOffset = masm_.thunkWithPatch().offset();
if (masm_.oom())
return false;
offsets.end = masm_.currentOffset();
if (!module_->codeRanges.emplaceBack(CodeRange::Inline, offsets))
return false;
if (!jumpThunks_[target].append(thunkOffset))
return false;
}
// Unlike callsites, which need to be persisted in the Module, we can simply
// flush jump sites after each patching pass.
masm_.clearJumpSites();
return true;
}
bool
ModuleGenerator::finishTask(IonCompileTask* task)
{
const FuncBytecode& func = task->func();
FuncCompileResults& results = task->results();
// Before merging in the new function's code, if jumps/calls in a previous
// function's body might go out of range, patch these to thunks which have
// full range.
if ((masm_.size() - startOfUnpatchedBranches_) + results.masm().size() > JumpRange()) {
startOfUnpatchedBranches_ = masm_.size();
if (!convertOutOfRangeBranchesToThunks())
return false;
}
// Offset the recorded FuncOffsets by the offset of the function in the
// whole module's code segment.
uint32_t offsetInWhole = masm_.size();
results.offsets().offsetBy(offsetInWhole);
// Record the non-profiling entry for whole-module linking later.
// Cannot simply append because funcIndex order is nonlinear.
if (func.index() >= funcEntryOffsets_.length()) {
if (!funcEntryOffsets_.resize(func.index() + 1))
// Add the CodeRange for this function.
uint32_t funcCodeRangeIndex = module_->codeRanges.length();
if (!module_->codeRanges.emplaceBack(func.index(), func.lineOrBytecode(), results.offsets()))
return false;
// Maintain a mapping from function index to CodeRange index.
if (func.index() >= funcIndexToCodeRange_.length()) {
uint32_t n = func.index() - funcIndexToCodeRange_.length() + 1;
if (!funcIndexToCodeRange_.appendN(BadCodeRange, n))
return false;
}
MOZ_ASSERT(funcEntryOffsets_[func.index()] == 0);
funcEntryOffsets_[func.index()] = results.offsets().nonProfilingEntry;
MOZ_ASSERT(!funcIsDefined(func.index()));
funcIndexToCodeRange_[func.index()] = funcCodeRangeIndex;
// Merge the compiled results into the whole-module masm.
DebugOnly<size_t> sizeBefore = masm_.size();
@@ -199,10 +322,6 @@ ModuleGenerator::finishTask(IonCompileTask* task)
return false;
MOZ_ASSERT(masm_.size() == offsetInWhole + results.masm().size());
// Add the CodeRange for this function.
if (!module_->codeRanges.emplaceBack(func.index(), func.lineOrBytecode(), results.offsets()))
return false;
// Keep a record of slow functions for printing in the final console message.
unsigned totalTime = func.generateTime() + results.compileTime();
if (totalTime >= SlowFunction::msThreshold) {
@@ -214,6 +333,197 @@ ModuleGenerator::finishTask(IonCompileTask* task)
return true;
}
bool
ModuleGenerator::finishCodegen(StaticLinkData* link)
{
uint32_t offsetInWhole = masm_.size();
// Generate stubs in a separate MacroAssembler since, otherwise, for modules
// larger than the JumpImmediateRange, even local uses of Label will fail
// due to the large absolute offsets temporarily stored by Label::bind().
Vector<Offsets> entries(cx_);
Vector<ProfilingOffsets> interpExits(cx_);
Vector<ProfilingOffsets> jitExits(cx_);
EnumeratedArray<JumpTarget, JumpTarget::Limit, Offsets> jumpTargets;
Offsets interruptExit;
{
TempAllocator alloc(&lifo_);
MacroAssembler masm(MacroAssembler::AsmJSToken(), alloc);
if (!entries.resize(numExports()))
return false;
for (uint32_t i = 0; i < numExports(); i++) {
uint32_t target = exportMap_->exportFuncIndices[i];
const Sig& sig = module_->exports[i].sig();
entries[i] = GenerateEntry(masm, target, sig, usesHeap());
}
if (!interpExits.resize(numImports()))
return false;
if (!jitExits.resize(numImports()))
return false;
for (uint32_t i = 0; i < numImports(); i++) {
interpExits[i] = GenerateInterpExit(masm, module_->imports[i], i);
jitExits[i] = GenerateJitExit(masm, module_->imports[i], usesHeap());
}
for (JumpTarget target : MakeEnumeratedRange(JumpTarget::Limit))
jumpTargets[target] = GenerateJumpTarget(masm, target);
interruptExit = GenerateInterruptStub(masm);
if (masm.oom() || !masm_.asmMergeWith(masm))
return false;
}
// Adjust each of the resulting Offsets (to account for being merged into
// masm_) and then create code ranges for all the stubs.
for (uint32_t i = 0; i < numExports(); i++) {
entries[i].offsetBy(offsetInWhole);
module_->exports[i].initStubOffset(entries[i].begin);
if (!module_->codeRanges.emplaceBack(CodeRange::Entry, entries[i]))
return false;
}
for (uint32_t i = 0; i < numImports(); i++) {
interpExits[i].offsetBy(offsetInWhole);
module_->imports[i].initInterpExitOffset(interpExits[i].begin);
if (!module_->codeRanges.emplaceBack(CodeRange::ImportInterpExit, interpExits[i]))
return false;
jitExits[i].offsetBy(offsetInWhole);
module_->imports[i].initJitExitOffset(jitExits[i].begin);
if (!module_->codeRanges.emplaceBack(CodeRange::ImportJitExit, jitExits[i]))
return false;
}
for (JumpTarget target : MakeEnumeratedRange(JumpTarget::Limit)) {
jumpTargets[target].offsetBy(offsetInWhole);
if (!module_->codeRanges.emplaceBack(CodeRange::Inline, jumpTargets[target]))
return false;
}
interruptExit.offsetBy(offsetInWhole);
if (!module_->codeRanges.emplaceBack(CodeRange::Inline, interruptExit))
return false;
// The signal handler redirects PC to the out-of-bounds and interrupt stubs.
link->pod.outOfBoundsOffset = jumpTargets[JumpTarget::OutOfBounds].begin;
link->pod.interruptOffset = interruptExit.begin;
// Only call convertOutOfRangeBranchesToThunks after all other codegen that may
// emit new jumps to JumpTargets has finished.
if (!convertOutOfRangeBranchesToThunks())
return false;
// Now that all thunks have been generated, patch all the thunks.
for (CallThunk& callThunk : module_->callThunks) {
uint32_t funcIndex = callThunk.u.funcIndex;
callThunk.u.codeRangeIndex = funcIndexToCodeRange_[funcIndex];
masm_.patchThunk(callThunk.offset, funcEntry(funcIndex));
}
for (JumpTarget target : MakeEnumeratedRange(JumpTarget::Limit)) {
for (uint32_t thunkOffset : jumpThunks_[target])
masm_.patchThunk(thunkOffset, jumpTargets[target].begin);
}
// Code-generation is complete!
masm_.finish();
return !masm_.oom();
}
bool
ModuleGenerator::finishStaticLinkData(uint8_t* code, uint32_t codeBytes, StaticLinkData* link)
{
// Add links to absolute addresses identified symbolically.
StaticLinkData::SymbolicLinkArray& symbolicLinks = link->symbolicLinks;
for (size_t i = 0; i < masm_.numAsmJSAbsoluteAddresses(); i++) {
AsmJSAbsoluteAddress src = masm_.asmJSAbsoluteAddress(i);
if (!symbolicLinks[src.target].append(src.patchAt.offset()))
return false;
}
// Relative link metadata: absolute addresses that refer to another point within
// the asm.js module.
// CodeLabels are used for switch cases and loads from floating-point /
// SIMD values in the constant pool.
for (size_t i = 0; i < masm_.numCodeLabels(); i++) {
CodeLabel cl = masm_.codeLabel(i);
StaticLinkData::InternalLink inLink(StaticLinkData::InternalLink::CodeLabel);
inLink.patchAtOffset = masm_.labelToPatchOffset(*cl.patchAt());
inLink.targetOffset = cl.target()->offset();
if (!link->internalLinks.append(inLink))
return false;
}
#if defined(JS_CODEGEN_X86)
// Global data accesses in x86 need to be patched with the absolute
// address of the global. Globals are allocated sequentially after the
// code section so we can just use an InternalLink.
for (size_t i = 0; i < masm_.numAsmJSGlobalAccesses(); i++) {
AsmJSGlobalAccess a = masm_.asmJSGlobalAccess(i);
StaticLinkData::InternalLink inLink(StaticLinkData::InternalLink::RawPointer);
inLink.patchAtOffset = masm_.labelToPatchOffset(a.patchAt);
inLink.targetOffset = codeBytes + a.globalDataOffset;
if (!link->internalLinks.append(inLink))
return false;
}
#endif
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
// On MIPS we need to update all the long jumps because they contain an
// absolute adress. The values are correctly patched for the current address
// space, but not after serialization or profiling-mode toggling.
for (size_t i = 0; i < masm_.numLongJumps(); i++) {
size_t off = masm_.longJump(i);
StaticLinkData::InternalLink inLink(StaticLinkData::InternalLink::InstructionImmediate);
inLink.patchAtOffset = off;
inLink.targetOffset = Assembler::ExtractInstructionImmediate(code + off) - uintptr_t(code);
if (!link->internalLinks.append(inLink))
return false;
}
#endif
#if defined(JS_CODEGEN_X64)
// Global data accesses on x64 use rip-relative addressing and thus do
// not need patching after deserialization.
uint8_t* globalData = code + codeBytes;
for (size_t i = 0; i < masm_.numAsmJSGlobalAccesses(); i++) {
AsmJSGlobalAccess a = masm_.asmJSGlobalAccess(i);
masm_.patchAsmJSGlobalAccess(a.patchAt, code, globalData, a.globalDataOffset);
}
#endif
// Convert the function pointer table elements from function-indices to code
// offsets that static linking will convert to absolute addresses.
for (uint32_t sigIndex = 0; sigIndex < numSigs_; sigIndex++) {
const TableModuleGeneratorData& table = shared_->sigToTable[sigIndex];
if (table.elemFuncIndices.empty())
continue;
Uint32Vector elemOffsets;
if (!elemOffsets.resize(table.elemFuncIndices.length()))
return false;
for (size_t i = 0; i < table.elemFuncIndices.length(); i++)
elemOffsets[i] = funcEntry(table.elemFuncIndices[i]);
if (!link->funcPtrTables.emplaceBack(table.globalDataOffset, Move(elemOffsets)))
return false;
}
return true;
}
bool
ModuleGenerator::addImport(const Sig& sig, uint32_t globalDataOffset)
{
@@ -326,16 +636,17 @@ ModuleGenerator::funcSig(uint32_t funcIndex) const
bool
ModuleGenerator::initImport(uint32_t importIndex, uint32_t sigIndex)
{
MOZ_ASSERT(isAsmJS());
uint32_t globalDataOffset;
if (!allocateGlobalBytes(Module::SizeOfImportExit, sizeof(void*), &globalDataOffset))
return false;
MOZ_ASSERT(isAsmJS());
MOZ_ASSERT(importIndex == module_->imports.length());
if (!addImport(sig(sigIndex), globalDataOffset))
return false;
ModuleImportGeneratorData& import = shared_->imports[importIndex];
ImportModuleGeneratorData& import = shared_->imports[importIndex];
MOZ_ASSERT(!import.sig);
import.sig = &shared_->sigs[sigIndex];
import.globalDataOffset = globalDataOffset;
@@ -348,23 +659,13 @@ ModuleGenerator::numImports() const
return module_->imports.length();
}
const ModuleImportGeneratorData&
const ImportModuleGeneratorData&
ModuleGenerator::import(uint32_t index) const
{
MOZ_ASSERT(shared_->imports[index].sig);
return shared_->imports[index];
}
bool
ModuleGenerator::defineImport(uint32_t index, ProfilingOffsets interpExit, ProfilingOffsets jitExit)
{
Import& import = module_->imports[index];
import.initInterpExitOffset(interpExit.begin);
import.initJitExitOffset(jitExit.begin);
return module_->codeRanges.emplaceBack(CodeRange::ImportInterpExit, interpExit) &&
module_->codeRanges.emplaceBack(CodeRange::ImportJitExit, jitExit);
}
bool
ModuleGenerator::declareExport(UniqueChars fieldName, uint32_t funcIndex, uint32_t* exportIndex)
{
@@ -394,37 +695,12 @@ ModuleGenerator::declareExport(UniqueChars fieldName, uint32_t funcIndex, uint32
exportMap_->exportFuncIndices.append(funcIndex);
}
uint32_t
ModuleGenerator::exportFuncIndex(uint32_t index) const
{
return exportMap_->exportFuncIndices[index];
}
uint32_t
ModuleGenerator::exportEntryOffset(uint32_t index) const
{
return funcEntryOffsets_[exportMap_->exportFuncIndices[index]];
}
const Sig&
ModuleGenerator::exportSig(uint32_t index) const
{
return module_->exports[index].sig();
}
uint32_t
ModuleGenerator::numExports() const
{
return module_->exports.length();
}
bool
ModuleGenerator::defineExport(uint32_t index, Offsets offsets)
{
module_->exports[index].initStubOffset(offsets.begin);
return module_->codeRanges.emplaceBack(CodeRange::Entry, offsets);
}
bool
ModuleGenerator::addMemoryExport(UniqueChars fieldName)
{
@@ -553,20 +829,8 @@ ModuleGenerator::finishFuncDefs()
return false;
}
// During codegen, all wasm->wasm (internal) calls use AsmJSInternalCallee
// as the call target, which contains the function-index of the target.
// These get recorded in a CallSiteAndTargetVector in the MacroAssembler
// so that we can patch them now that all the function entry offsets are
// known.
for (CallSiteAndTarget& cs : masm_.callSites()) {
if (!cs.isInternal())
continue;
MOZ_ASSERT(cs.kind() == CallSiteDesc::Relative);
uint32_t callerOffset = cs.returnAddressOffset();
uint32_t calleeOffset = funcEntryOffsets_[cs.targetIndex()];
masm_.patchCall(callerOffset, calleeOffset);
}
for (uint32_t funcIndex = 0; funcIndex < funcIndexToCodeRange_.length(); funcIndex++)
MOZ_ASSERT(funcIsDefined(funcIndex));
module_->functionBytes = masm_.size();
finishedFuncs_ = true;
@@ -574,69 +838,34 @@ ModuleGenerator::finishFuncDefs()
}
bool
ModuleGenerator::declareFuncPtrTable(uint32_t numElems, uint32_t* index)
ModuleGenerator::initSigTableLength(uint32_t sigIndex, uint32_t numElems)
{
// Here just add an uninitialized FuncPtrTable and claim space in the global
// data section. Later, 'defineFuncPtrTable' will be called with function
// indices for all the elements of the table.
// Avoid easy way to OOM the process.
if (numElems > 1024 * 1024)
return false;
MOZ_ASSERT(isAsmJS());
MOZ_ASSERT(numElems != 0);
MOZ_ASSERT(numElems <= MaxTableElems);
uint32_t globalDataOffset;
if (!allocateGlobalBytes(numElems * sizeof(void*), sizeof(void*), &globalDataOffset))
return false;
StaticLinkData::FuncPtrTableVector& tables = link_->funcPtrTables;
*index = tables.length();
if (!tables.emplaceBack(globalDataOffset))
return false;
if (!tables.back().elemOffsets.resize(numElems))
return false;
TableModuleGeneratorData& table = shared_->sigToTable[sigIndex];
MOZ_ASSERT(table.numElems == 0);
table.numElems = numElems;
table.globalDataOffset = globalDataOffset;
return true;
}
uint32_t
ModuleGenerator::funcPtrTableGlobalDataOffset(uint32_t index) const
{
return link_->funcPtrTables[index].globalDataOffset;
}
void
ModuleGenerator::defineFuncPtrTable(uint32_t index, const Vector<uint32_t>& elemFuncIndices)
ModuleGenerator::initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices)
{
MOZ_ASSERT(finishedFuncs_);
MOZ_ASSERT(isAsmJS());
MOZ_ASSERT(!elemFuncIndices.empty());
StaticLinkData::FuncPtrTable& table = link_->funcPtrTables[index];
MOZ_ASSERT(table.elemOffsets.length() == elemFuncIndices.length());
TableModuleGeneratorData& table = shared_->sigToTable[sigIndex];
MOZ_ASSERT(table.numElems == elemFuncIndices.length());
for (size_t i = 0; i < elemFuncIndices.length(); i++)
table.elemOffsets[i] = funcEntryOffsets_[elemFuncIndices[i]];
}
bool
ModuleGenerator::defineInlineStub(Offsets offsets)
{
MOZ_ASSERT(finishedFuncs_);
return module_->codeRanges.emplaceBack(CodeRange::Inline, offsets);
}
void
ModuleGenerator::defineInterruptExit(uint32_t offset)
{
MOZ_ASSERT(finishedFuncs_);
link_->pod.interruptOffset = offset;
}
void
ModuleGenerator::defineOutOfBoundsExit(uint32_t offset)
{
MOZ_ASSERT(finishedFuncs_);
link_->pod.outOfBoundsOffset = offset;
MOZ_ASSERT(table.elemFuncIndices.empty());
table.elemFuncIndices = Move(elemFuncIndices);
}
bool
@@ -649,15 +878,15 @@ ModuleGenerator::finish(CacheableCharsVector&& prettyFuncNames,
MOZ_ASSERT(!activeFunc_);
MOZ_ASSERT(finishedFuncs_);
UniqueStaticLinkData link = MakeUnique<StaticLinkData>();
if (!link)
return false;
if (!finishCodegen(link.get()))
return false;
module_->prettyFuncNames = Move(prettyFuncNames);
if (!GenerateStubs(*this))
return false;
masm_.finish();
if (masm_.oom())
return false;
// Start global data on a new page so JIT code may be given independent
// protection flags. Note assumption that global data starts right after
// code below.
@@ -675,8 +904,7 @@ ModuleGenerator::finish(CacheableCharsVector&& prettyFuncNames,
// Delay flushing until Module::dynamicallyLink. The flush-inhibited range
// is set by executableCopy.
AutoFlushICache afc("ModuleGenerator::finish", /* inhibit = */ true);
uint8_t* code = module_->code.get();
masm_.executableCopy(code);
masm_.executableCopy(module_->code.get());
// c.f. JitCode::copyFrom
MOZ_ASSERT(masm_.jumpRelocationTableBytes() == 0);
@@ -692,68 +920,11 @@ ModuleGenerator::finish(CacheableCharsVector&& prettyFuncNames,
// The MacroAssembler has accumulated all the heap accesses during codegen.
module_->heapAccesses = masm_.extractHeapAccesses();
// Add links to absolute addresses identified symbolically.
StaticLinkData::SymbolicLinkArray& symbolicLinks = link_->symbolicLinks;
for (size_t i = 0; i < masm_.numAsmJSAbsoluteAddresses(); i++) {
AsmJSAbsoluteAddress src = masm_.asmJSAbsoluteAddress(i);
if (!symbolicLinks[src.target].append(src.patchAt.offset()))
return false;
}
// Relative link metadata: absolute addresses that refer to another point within
// the asm.js module.
// CodeLabels are used for switch cases and loads from floating-point /
// SIMD values in the constant pool.
for (size_t i = 0; i < masm_.numCodeLabels(); i++) {
CodeLabel cl = masm_.codeLabel(i);
StaticLinkData::InternalLink link(StaticLinkData::InternalLink::CodeLabel);
link.patchAtOffset = masm_.labelToPatchOffset(*cl.patchAt());
link.targetOffset = cl.target()->offset();
if (!link_->internalLinks.append(link))
return false;
}
#if defined(JS_CODEGEN_X86)
// Global data accesses in x86 need to be patched with the absolute
// address of the global. Globals are allocated sequentially after the
// code section so we can just use an InternalLink.
for (size_t i = 0; i < masm_.numAsmJSGlobalAccesses(); i++) {
AsmJSGlobalAccess a = masm_.asmJSGlobalAccess(i);
StaticLinkData::InternalLink link(StaticLinkData::InternalLink::RawPointer);
link.patchAtOffset = masm_.labelToPatchOffset(a.patchAt);
link.targetOffset = module_->codeBytes + a.globalDataOffset;
if (!link_->internalLinks.append(link))
return false;
}
#endif
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
// On MIPS we need to update all the long jumps because they contain an
// absolute adress. The values are correctly patched for the current address
// space, but not after serialization or profiling-mode toggling.
for (size_t i = 0; i < masm_.numLongJumps(); i++) {
size_t off = masm_.longJump(i);
StaticLinkData::InternalLink link(StaticLinkData::InternalLink::InstructionImmediate);
link.patchAtOffset = off;
link.targetOffset = Assembler::ExtractInstructionImmediate(code + off) - uintptr_t(code);
if (!link_->internalLinks.append(link))
return false;
}
#endif
#if defined(JS_CODEGEN_X64)
// Global data accesses on x64 use rip-relative addressing and thus do
// not need patching after deserialization.
uint8_t* globalData = code + module_->codeBytes;
for (size_t i = 0; i < masm_.numAsmJSGlobalAccesses(); i++) {
AsmJSGlobalAccess a = masm_.asmJSGlobalAccess(i);
masm_.patchAsmJSGlobalAccess(a.patchAt, code, globalData, a.globalDataOffset);
}
#endif
if (!finishStaticLinkData(module_->code.get(), module_->codeBytes, link.get()))
return false;
*module = Move(module_);
*linkData = Move(link_);
*linkData = Move(link);
*exportMap = Move(exportMap_);
*slowFuncs = Move(slowFuncs_);
return true;
+40 -22
View File
@@ -54,16 +54,33 @@ typedef Vector<SlowFunction> SlowFunctionVector;
// element is written to Bytecode sent to a ModuleGeneratorThreadView thread.
// Once created, the Vectors are never resized.
struct ModuleImportGeneratorData
struct TableModuleGeneratorData
{
uint32_t globalDataOffset;
uint32_t numElems;
Uint32Vector elemFuncIndices;
TableModuleGeneratorData()
: globalDataOffset(0), numElems(0)
{}
TableModuleGeneratorData(TableModuleGeneratorData&& rhs)
: globalDataOffset(rhs.globalDataOffset), numElems(rhs.numElems),
elemFuncIndices(Move(rhs.elemFuncIndices))
{}
};
typedef Vector<TableModuleGeneratorData, 0, SystemAllocPolicy> TableModuleGeneratorDataVector;
struct ImportModuleGeneratorData
{
const DeclaredSig* sig;
uint32_t globalDataOffset;
ModuleImportGeneratorData() : sig(nullptr), globalDataOffset(0) {}
explicit ModuleImportGeneratorData(const DeclaredSig* sig) : sig(sig), globalDataOffset(0) {}
ImportModuleGeneratorData() : sig(nullptr), globalDataOffset(0) {}
explicit ImportModuleGeneratorData(const DeclaredSig* sig) : sig(sig), globalDataOffset(0) {}
};
typedef Vector<ModuleImportGeneratorData, 0, SystemAllocPolicy> ModuleImportGeneratorDataVector;
typedef Vector<ImportModuleGeneratorData, 0, SystemAllocPolicy> ImportModuleGeneratorDataVector;
struct AsmJSGlobalVariable
{
@@ -80,8 +97,9 @@ typedef Vector<AsmJSGlobalVariable, 0, SystemAllocPolicy> AsmJSGlobalVariableVec
struct ModuleGeneratorData
{
DeclaredSigVector sigs;
TableModuleGeneratorDataVector sigToTable;
DeclaredSigPtrVector funcSigs;
ModuleImportGeneratorDataVector imports;
ImportModuleGeneratorDataVector imports;
AsmJSGlobalVariableVector globals;
};
@@ -103,11 +121,15 @@ class ModuleGeneratorThreadView
const DeclaredSig& sig(uint32_t sigIndex) const {
return shared_.sigs[sigIndex];
}
const TableModuleGeneratorData& sigToTable(uint32_t sigIndex) const {
MOZ_ASSERT(shared_.sigToTable[sigIndex].numElems != 0);
return shared_.sigToTable[sigIndex];
}
const DeclaredSig& funcSig(uint32_t funcIndex) const {
MOZ_ASSERT(shared_.funcSigs[funcIndex]);
return *shared_.funcSigs[funcIndex];
}
const ModuleImportGeneratorData& import(uint32_t importIndex) const {
const ImportModuleGeneratorData& import(uint32_t importIndex) const {
MOZ_ASSERT(shared_.imports[importIndex].sig);
return shared_.imports[importIndex];
}
@@ -132,7 +154,6 @@ class MOZ_STACK_CLASS ModuleGenerator
// Data handed back to the caller in finish()
UniqueModuleData module_;
UniqueStaticLinkData link_;
UniqueExportMap exportMap_;
SlowFunctionVector slowFuncs_;
@@ -142,8 +163,11 @@ class MOZ_STACK_CLASS ModuleGenerator
LifoAlloc lifo_;
jit::TempAllocator alloc_;
jit::MacroAssembler masm_;
Uint32Vector funcEntryOffsets_;
Uint32Vector funcIndexToCodeRange_;
FuncIndexMap funcIndexToExport_;
uint32_t lastPatchedCallsite_;
uint32_t startOfUnpatchedBranches_;
JumpSiteArray jumpThunks_;
// Parallel compilation
bool parallel_;
@@ -157,7 +181,12 @@ class MOZ_STACK_CLASS ModuleGenerator
DebugOnly<bool> finishedFuncs_;
bool finishOutstandingTask();
bool funcIsDefined(uint32_t funcIndex) const;
uint32_t funcEntry(uint32_t funcIndex) const;
bool convertOutOfRangeBranchesToThunks();
bool finishTask(IonCompileTask* task);
bool finishCodegen(StaticLinkData* link);
bool finishStaticLinkData(uint8_t* code, uint32_t codeBytes, StaticLinkData* link);
bool addImport(const Sig& sig, uint32_t globalDataOffset);
bool startedFuncDefs() const { return !!threadView_; }
bool allocateGlobalBytes(uint32_t bytes, uint32_t align, uint32_t* globalDataOffset);
@@ -193,16 +222,11 @@ class MOZ_STACK_CLASS ModuleGenerator
// Imports:
bool initImport(uint32_t importIndex, uint32_t sigIndex);
uint32_t numImports() const;
const ModuleImportGeneratorData& import(uint32_t index) const;
bool defineImport(uint32_t index, ProfilingOffsets interpExit, ProfilingOffsets jitExit);
const ImportModuleGeneratorData& import(uint32_t index) const;
// Exports:
bool declareExport(UniqueChars fieldName, uint32_t funcIndex, uint32_t* exportIndex = nullptr);
uint32_t numExports() const;
uint32_t exportFuncIndex(uint32_t index) const;
uint32_t exportEntryOffset(uint32_t index) const;
const Sig& exportSig(uint32_t index) const;
bool defineExport(uint32_t index, Offsets offsets);
bool addMemoryExport(UniqueChars fieldName);
// Function definitions:
@@ -212,14 +236,8 @@ class MOZ_STACK_CLASS ModuleGenerator
bool finishFuncDefs();
// Function-pointer tables:
bool declareFuncPtrTable(uint32_t numElems, uint32_t* index);
uint32_t funcPtrTableGlobalDataOffset(uint32_t index) const;
void defineFuncPtrTable(uint32_t index, const Vector<uint32_t>& elemFuncIndices);
// Stubs:
bool defineInlineStub(Offsets offsets);
void defineInterruptExit(uint32_t offset);
void defineOutOfBoundsExit(uint32_t offset);
bool initSigTableLength(uint32_t sigIndex, uint32_t numElems);
void initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncIndices);
// Return a ModuleData object which may be used to construct a Module, the
// StaticLinkData required to call Module::staticallyLink, and the list of
+10 -7
View File
@@ -1597,17 +1597,15 @@ EmitCall(FunctionCompiler& f, ExprType ret, MDefinition** def)
}
static bool
EmitFuncPtrCall(FunctionCompiler& f, ExprType ret, MDefinition** def)
EmitCallIndirect(FunctionCompiler& f, ExprType ret, MDefinition** def)
{
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
uint32_t mask = f.readU32();
uint32_t globalDataOffset = f.readU32();
uint32_t sigIndex = f.readU32();
const Sig& sig = f.mg().sig(sigIndex);
MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret != ExprType::Void, sig.ret() == ret);
MDefinition *index;
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
@@ -1615,7 +1613,12 @@ EmitFuncPtrCall(FunctionCompiler& f, ExprType ret, MDefinition** def)
if (!EmitCallArgs(f, sig, &call))
return false;
return f.funcPtrCall(sig, mask, globalDataOffset, index, call, def);
const TableModuleGeneratorData& table = f.mg().sigToTable(sigIndex);
uint32_t length = table.numElems;
MOZ_ASSERT(IsPowerOfTwo(length));
uint32_t mask = length - 1;
return f.funcPtrCall(sig, mask, table.globalDataOffset, index, call, def);
}
static bool
@@ -1624,7 +1627,7 @@ EmitCallImport(FunctionCompiler& f, ExprType ret, MDefinition** def)
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
uint32_t importIndex = f.readU32();
const ModuleImportGeneratorData& import = f.mg().import(importIndex);
const ImportModuleGeneratorData& import = f.mg().import(importIndex);
const Sig& sig = *import.sig;
MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret != ExprType::Void, sig.ret() == ret);
@@ -2673,7 +2676,7 @@ EmitExpr(FunctionCompiler& f, ExprType type, MDefinition** def, LabelVector* may
case Expr::Call:
return EmitCall(f, type, def);
case Expr::CallIndirect:
return EmitFuncPtrCall(f, type, def);
return EmitCallIndirect(f, type, def);
case Expr::CallImport:
return EmitCallImport(f, type, def);
case Expr::AtomicsFence:
+16 -8
View File
@@ -111,7 +111,7 @@ size_t
StaticLinkData::SymbolicLinkArray::serializedSize() const
{
size_t size = 0;
for (const OffsetVector& offsets : *this)
for (const Uint32Vector& offsets : *this)
size += SerializedPodVectorSize(offsets);
return size;
}
@@ -119,7 +119,7 @@ StaticLinkData::SymbolicLinkArray::serializedSize() const
uint8_t*
StaticLinkData::SymbolicLinkArray::serialize(uint8_t* cursor) const
{
for (const OffsetVector& offsets : *this)
for (const Uint32Vector& offsets : *this)
cursor = SerializePodVector(cursor, offsets);
return cursor;
}
@@ -127,7 +127,7 @@ StaticLinkData::SymbolicLinkArray::serialize(uint8_t* cursor) const
const uint8_t*
StaticLinkData::SymbolicLinkArray::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
for (OffsetVector& offsets : *this) {
for (Uint32Vector& offsets : *this) {
cursor = DeserializePodVector(cx, cursor, &offsets);
if (!cursor)
return nullptr;
@@ -149,7 +149,7 @@ size_t
StaticLinkData::SymbolicLinkArray::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
size_t size = 0;
for (const OffsetVector& offsets : *this)
for (const Uint32Vector& offsets : *this)
size += offsets.sizeOfExcludingThis(mallocSizeOf);
return size;
}
@@ -235,7 +235,7 @@ StaticLinkData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
symbolicLinks.sizeOfExcludingThis(mallocSizeOf) +
SizeOfVectorExcludingThis(funcPtrTables, mallocSizeOf);
for (const OffsetVector& offsets : symbolicLinks)
for (const Uint32Vector& offsets : symbolicLinks)
size += offsets.sizeOfExcludingThis(mallocSizeOf);
return size;
@@ -360,7 +360,7 @@ CodeRange::CodeRange(Kind kind, Offsets offsets)
u.kind_ = kind;
MOZ_ASSERT(begin_ <= end_);
MOZ_ASSERT(u.kind_ == Entry || u.kind_ == Inline);
MOZ_ASSERT(u.kind_ == Entry || u.kind_ == Inline || u.kind_ == CallThunk);
}
CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets)
@@ -502,6 +502,7 @@ ModuleData::serializedSize() const
SerializedPodVectorSize(heapAccesses) +
SerializedPodVectorSize(codeRanges) +
SerializedPodVectorSize(callSites) +
SerializedPodVectorSize(callThunks) +
SerializedVectorSize(prettyFuncNames) +
filename.serializedSize();
}
@@ -516,6 +517,7 @@ ModuleData::serialize(uint8_t* cursor) const
cursor = SerializePodVector(cursor, heapAccesses);
cursor = SerializePodVector(cursor, codeRanges);
cursor = SerializePodVector(cursor, callSites);
cursor = SerializePodVector(cursor, callThunks);
cursor = SerializeVector(cursor, prettyFuncNames);
cursor = filename.serialize(cursor);
return cursor;
@@ -536,6 +538,7 @@ ModuleData::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
(cursor = DeserializePodVector(cx, cursor, &heapAccesses)) &&
(cursor = DeserializePodVector(cx, cursor, &codeRanges)) &&
(cursor = DeserializePodVector(cx, cursor, &callSites)) &&
(cursor = DeserializePodVector(cx, cursor, &callThunks)) &&
(cursor = DeserializeVector(cx, cursor, &prettyFuncNames)) &&
(cursor = filename.deserialize(cx, cursor));
return cursor;
@@ -556,6 +559,7 @@ ModuleData::clone(JSContext* cx, ModuleData* out) const
ClonePodVector(cx, heapAccesses, &out->heapAccesses) &&
ClonePodVector(cx, codeRanges, &out->codeRanges) &&
ClonePodVector(cx, callSites, &out->callSites) &&
ClonePodVector(cx, callThunks, &out->callThunks) &&
CloneVector(cx, prettyFuncNames, &out->prettyFuncNames) &&
filename.clone(cx, &out->filename);
}
@@ -569,6 +573,7 @@ ModuleData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
heapAccesses.sizeOfExcludingThis(mallocSizeOf) +
codeRanges.sizeOfExcludingThis(mallocSizeOf) +
callSites.sizeOfExcludingThis(mallocSizeOf) +
callThunks.sizeOfExcludingThis(mallocSizeOf) +
prettyFuncNames.sizeOfExcludingThis(mallocSizeOf) +
filename.sizeOfExcludingThis(mallocSizeOf);
}
@@ -786,6 +791,9 @@ Module::setProfilingEnabled(JSContext* cx, bool enabled)
for (const CallSite& callSite : module_->callSites)
EnableProfilingPrologue(*this, callSite, enabled);
for (const CallThunk& callThunk : module_->callThunks)
EnableProfilingThunk(*this, callThunk, enabled);
for (const CodeRange& codeRange : module_->codeRanges)
EnableProfilingEpilogue(*this, codeRange, enabled);
}
@@ -833,7 +841,7 @@ Module::clone(JSContext* cx, const StaticLinkData& link, Module* out) const
// in Module::staticallyLink are valid.
for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
void* callee = AddressOf(imm, cx);
const StaticLinkData::OffsetVector& offsets = link.symbolicLinks[imm];
const Uint32Vector& offsets = link.symbolicLinks[imm];
for (uint32_t offset : offsets) {
jit::Assembler::PatchDataWithValueCheck(jit::CodeLocationLabel(out->code() + offset),
jit::PatchedImmPtr((void*)-1),
@@ -1026,7 +1034,7 @@ Module::staticallyLink(ExclusiveContext* cx, const StaticLinkData& linkData)
}
for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
const StaticLinkData::OffsetVector& offsets = linkData.symbolicLinks[imm];
const Uint32Vector& offsets = linkData.symbolicLinks[imm];
for (size_t i = 0; i < offsets.length(); i++) {
uint8_t* patchAt = code() + offsets[i];
void* target = AddressOf(imm, cx);
+32 -9
View File
@@ -66,21 +66,20 @@ struct StaticLinkData
};
typedef Vector<InternalLink, 0, SystemAllocPolicy> InternalLinkVector;
typedef Vector<uint32_t, 0, SystemAllocPolicy> OffsetVector;
struct SymbolicLinkArray : mozilla::EnumeratedArray<SymbolicAddress,
SymbolicAddress::Limit,
OffsetVector> {
struct SymbolicLinkArray : EnumeratedArray<SymbolicAddress, SymbolicAddress::Limit, Uint32Vector> {
WASM_DECLARE_SERIALIZABLE(SymbolicLinkArray)
};
struct FuncPtrTable {
uint32_t globalDataOffset;
OffsetVector elemOffsets;
explicit FuncPtrTable(uint32_t globalDataOffset) : globalDataOffset(globalDataOffset) {}
FuncPtrTable() = default;
Uint32Vector elemOffsets;
FuncPtrTable(uint32_t globalDataOffset, Uint32Vector&& elemOffsets)
: globalDataOffset(globalDataOffset), elemOffsets(Move(elemOffsets))
{}
FuncPtrTable(FuncPtrTable&& rhs)
: globalDataOffset(rhs.globalDataOffset), elemOffsets(Move(rhs.elemOffsets))
{}
FuncPtrTable() = default;
WASM_DECLARE_SERIALIZABLE(FuncPtrTable)
};
typedef Vector<FuncPtrTable, 0, SystemAllocPolicy> FuncPtrTableVector;
@@ -213,7 +212,7 @@ class CodeRange
void assertValid();
public:
enum Kind { Function, Entry, ImportJitExit, ImportInterpExit, Inline };
enum Kind { Function, Entry, ImportJitExit, ImportInterpExit, Inline, CallThunk };
CodeRange() = default;
CodeRange(Kind kind, Offsets offsets);
@@ -237,7 +236,7 @@ class CodeRange
// which is used for asynchronous profiling to determine the frame pointer.
uint32_t profilingReturn() const {
MOZ_ASSERT(kind() != Entry && kind() != Inline);
MOZ_ASSERT(isFunction() || isImportExit());
return profilingReturn_;
}
@@ -247,6 +246,9 @@ class CodeRange
bool isFunction() const {
return kind() == Function;
}
bool isImportExit() const {
return kind() == ImportJitExit || kind() == ImportInterpExit;
}
uint32_t funcProfilingEntry() const {
MOZ_ASSERT(isFunction());
return begin();
@@ -288,6 +290,25 @@ class CodeRange
typedef Vector<CodeRange, 0, SystemAllocPolicy> CodeRangeVector;
// A CallThunk describes the offset and target of thunks so that they may be
// patched at runtime when profiling is toggled. Thunks are emitted to connect
// callsites that are too far away from callees to fit in a single call
// instruction's relative offset.
struct CallThunk
{
uint32_t offset;
union {
uint32_t funcIndex;
uint32_t codeRangeIndex;
} u;
CallThunk(uint32_t offset, uint32_t funcIndex) : offset(offset) { u.funcIndex = funcIndex; }
CallThunk() = default;
};
typedef Vector<CallThunk, 0, SystemAllocPolicy> CallThunkVector;
// CacheableChars is used to cacheably store UniqueChars.
struct CacheableChars : UniqueChars
@@ -389,6 +410,7 @@ struct ModuleData : ModuleCacheablePod
HeapAccessVector heapAccesses;
CodeRangeVector codeRanges;
CallSiteVector callSites;
CallThunkVector callThunks;
CacheableCharsVector prettyFuncNames;
CacheableChars filename;
bool loadedFromCache;
@@ -496,6 +518,7 @@ class Module
CompileArgs compileArgs() const { return module_->compileArgs; }
const ImportVector& imports() const { return module_->imports; }
const ExportVector& exports() const { return module_->exports; }
const CodeRangeVector& codeRanges() const { return module_->codeRanges; }
const char* filename() const { return module_->filename.get(); }
bool loadedFromCache() const { return module_->loadedFromCache; }
bool staticallyLinked() const { return staticallyLinked_; }
+73 -194
View File
@@ -19,7 +19,6 @@
#include "asmjs/WasmStubs.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/EnumeratedRange.h"
#include "jit/MacroAssembler-inl.h"
@@ -28,7 +27,6 @@ using namespace js::jit;
using namespace js::wasm;
using mozilla::ArrayLength;
using mozilla::MakeEnumeratedRange;
static void
AssertStackAlignment(MacroAssembler& masm, uint32_t alignment, uint32_t addBeforeAssert = 0)
@@ -94,12 +92,9 @@ static const unsigned FramePushedForEntrySP = FramePushedAfterSave + sizeof(void
// The signature of the entry point is Module::CodePtr. The exported wasm
// function has an ABI derived from its specific signature, so this function
// must map from the ABI of CodePtr to the export's signature's ABI.
static bool
GenerateEntry(ModuleGenerator& mg, unsigned exportIndex)
Offsets
wasm::GenerateEntry(MacroAssembler& masm, unsigned target, const Sig& sig, bool usesHeap)
{
MacroAssembler& masm = mg.masm();
const Sig& sig = mg.exportSig(exportIndex);
masm.haltingAlign(CodeAlignment);
Offsets offsets;
@@ -131,7 +126,7 @@ GenerateEntry(ModuleGenerator& mg, unsigned exportIndex)
// ARM, MIPS/MIPS64 and x64 have a globally-pinned HeapReg (x86 uses immediates in
// effective addresses). Loading the heap register depends on the global
// register already having been loaded.
if (mg.usesHeap())
if (usesHeap)
masm.loadAsmJSHeapRegisterFromGlobalData();
// Put the 'argv' argument into a non-argument/return register so that we
@@ -235,9 +230,7 @@ GenerateEntry(ModuleGenerator& mg, unsigned exportIndex)
// Call into the real function.
masm.assertStackAlignment(AsmJSStackAlignment);
Label target;
target.bind(mg.exportEntryOffset(exportIndex));
masm.call(CallSiteDesc(CallSiteDesc::Relative), &target);
masm.call(CallSiteDesc(CallSiteDesc::Relative), AsmJSInternalCallee(target));
// Recover the stack pointer value before dynamic alignment.
masm.loadWasmActivation(scratch);
@@ -283,11 +276,8 @@ GenerateEntry(ModuleGenerator& mg, unsigned exportIndex)
masm.move32(Imm32(true), ReturnReg);
masm.ret();
if (masm.oom())
return false;
offsets.end = masm.currentOffset();
return mg.defineExport(exportIndex, offsets);
return offsets;
}
static void
@@ -333,11 +323,10 @@ FillArgumentArray(MacroAssembler& masm, const ValTypeVector& args, unsigned argO
// Generate a stub that is called via the internal ABI derived from the
// signature of the import and calls into an appropriate InvokeImport C++
// function, having boxed all the ABI arguments into a homogeneous Value array.
static bool
GenerateInterpExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets* offsets)
ProfilingOffsets
wasm::GenerateInterpExit(MacroAssembler& masm, const Import& import, uint32_t importIndex)
{
MacroAssembler& masm = mg.masm();
const Sig& sig = *mg.import(importIndex).sig;
const Sig& sig = import.sig();
masm.setFramePushed(0);
@@ -356,7 +345,8 @@ GenerateInterpExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffse
unsigned argBytes = Max<size_t>(1, sig.args().length()) * sizeof(Value);
unsigned framePushed = StackDecrementForCall(masm, ABIStackAlignment, argOffset + argBytes);
GenerateExitPrologue(masm, framePushed, ExitReason::ImportInterp, offsets);
ProfilingOffsets offsets;
GenerateExitPrologue(masm, framePushed, ExitReason::ImportInterp, &offsets);
// Fill the argument array.
unsigned offsetToCallerStackArgs = sizeof(AsmJSFrame) + masm.framePushed();
@@ -407,7 +397,6 @@ GenerateInterpExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffse
case ExprType::I64:
MOZ_CRASH("no int64 in asm.js");
case ExprType::F32:
MOZ_ASSERT(!mg.isAsmJS(), "import can't return float32 in asm.js");
masm.call(SymbolicAddress::InvokeImport_F64);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, JumpTarget::Throw);
masm.loadDouble(argv, ReturnDoubleReg);
@@ -426,13 +415,10 @@ GenerateInterpExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffse
MOZ_CRASH("Limit");
}
GenerateExitEpilogue(masm, framePushed, ExitReason::ImportInterp, offsets);
GenerateExitEpilogue(masm, framePushed, ExitReason::ImportInterp, &offsets);
if (masm.oom())
return false;
offsets->end = masm.currentOffset();
return true;
offsets.end = masm.currentOffset();
return offsets;
}
#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
@@ -444,11 +430,10 @@ static const unsigned MaybeSavedGlobalReg = 0;
// Generate a stub that is called via the internal ABI derived from the
// signature of the import and calls into a compatible JIT function,
// having boxed all the ABI arguments into the JIT stack frame layout.
static bool
GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets* offsets)
ProfilingOffsets
wasm::GenerateJitExit(MacroAssembler& masm, const Import& import, bool usesHeap)
{
MacroAssembler& masm = mg.masm();
const Sig& sig = *mg.import(importIndex).sig;
const Sig& sig = import.sig();
masm.setFramePushed(0);
@@ -465,7 +450,8 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets*
unsigned jitFramePushed = StackDecrementForCall(masm, JitStackAlignment, totalJitFrameBytes) -
sizeOfRetAddr;
GenerateExitPrologue(masm, jitFramePushed, ExitReason::ImportJit, offsets);
ProfilingOffsets offsets;
GenerateExitPrologue(masm, jitFramePushed, ExitReason::ImportJit, &offsets);
// 1. Descriptor
size_t argOffset = 0;
@@ -479,7 +465,7 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets*
Register scratch = ABIArgGenerator::NonArgReturnReg1; // repeatedly clobbered
// 2.1. Get ExitDatum
unsigned globalDataOffset = mg.import(importIndex).globalDataOffset;
uint32_t globalDataOffset = import.exitGlobalDataOffset();
#if defined(JS_CODEGEN_X64)
masm.append(AsmJSGlobalAccess(masm.leaRipRelative(callee), globalDataOffset));
#elif defined(JS_CODEGEN_X86)
@@ -663,7 +649,6 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets*
case ExprType::I64:
MOZ_CRASH("no int64 in asm.js");
case ExprType::F32:
MOZ_ASSERT(!mg.isAsmJS(), "import can't return float32 in asm.js");
masm.convertValueToFloat(JSReturnOperand, ReturnFloat32Reg, &oolConvert);
break;
case ExprType::F64:
@@ -682,10 +667,10 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets*
// Ion code does not respect system callee-saved register conventions so
// reload the heap register.
if (mg.usesHeap())
if (usesHeap)
masm.loadAsmJSHeapRegisterFromGlobalData();
GenerateExitEpilogue(masm, masm.framePushed(), ExitReason::ImportJit, offsets);
GenerateExitEpilogue(masm, masm.framePushed(), ExitReason::ImportJit, &offsets);
if (oolConvert.used()) {
masm.bind(&oolConvert);
@@ -728,7 +713,6 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets*
masm.loadDouble(Address(masm.getStackPointer(), offsetToCoerceArgv), ReturnDoubleReg);
break;
case ExprType::F32:
MOZ_ASSERT(!mg.isAsmJS(), "import can't return float32 in asm.js");
masm.call(SymbolicAddress::CoerceInPlace_ToNumber);
masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, JumpTarget::Throw);
masm.loadDouble(Address(masm.getStackPointer(), offsetToCoerceArgv), ReturnDoubleReg);
@@ -744,37 +728,18 @@ GenerateJitExitStub(ModuleGenerator& mg, unsigned importIndex, ProfilingOffsets*
MOZ_ASSERT(masm.framePushed() == 0);
if (masm.oom())
return false;
offsets->end = masm.currentOffset();
return true;
}
static void
BindJumps(MacroAssembler& masm, JumpTarget target)
{
for (uint32_t offset : masm.jumpSites()[target]) {
RepatchLabel label;
label.use(offset);
masm.bind(&label);
}
offsets.end = masm.currentOffset();
return offsets;
}
// Generate a stub that is called immediately after the prologue when there is a
// stack overflow. This stub calls a C++ function to report the error and then
// jumps to the throw stub to pop the activation.
static bool
GenerateStackOverflowStub(ModuleGenerator& mg)
static Offsets
GenerateStackOverflow(MacroAssembler& masm)
{
MacroAssembler& masm = mg.masm();
masm.haltingAlign(CodeAlignment);
if (masm.jumpSites()[JumpTarget::StackOverflow].empty())
return true;
BindJumps(masm, JumpTarget::StackOverflow);
Offsets offsets;
offsets.begin = masm.currentOffset();
@@ -797,27 +762,16 @@ GenerateStackOverflowStub(ModuleGenerator& mg)
masm.call(SymbolicAddress::ReportOverRecursed);
masm.jump(JumpTarget::Throw);
if (masm.oom())
return false;
offsets.end = masm.currentOffset();
return mg.defineInlineStub(offsets);
return offsets;
}
// Generate a stub that is jumped to from an out-of-bounds heap access when
// there are throwing semantics. This stub calls a C++ function to report an
// error and then jumps to the throw stub to pop the activation.
static bool
GenerateConversionErrorStub(ModuleGenerator& mg)
// Generate a stub that is jumped to from function bodies to throw an exception.
static Offsets
GenerateErrorStub(MacroAssembler& masm, SymbolicAddress address)
{
MacroAssembler& masm = mg.masm();
masm.haltingAlign(CodeAlignment);
if (masm.jumpSites()[JumpTarget::ConversionError].empty())
return true;
BindJumps(masm, JumpTarget::ConversionError);
Offsets offsets;
offsets.begin = masm.currentOffset();
@@ -825,50 +779,63 @@ GenerateConversionErrorStub(ModuleGenerator& mg)
// into C++. We unconditionally jump to throw so don't worry about restoring sp.
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
// OnImpreciseConversion always throws.
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::OnImpreciseConversion);
masm.call(address);
masm.jump(JumpTarget::Throw);
if (masm.oom())
return false;
offsets.end = masm.currentOffset();
return mg.defineInlineStub(offsets);
return offsets;
}
// Generate a stub that is jumped to from an out-of-bounds heap access when
// there are throwing semantics. This stub calls a C++ function to report an
// error and then jumps to the throw stub to pop the activation.
static bool
GenerateOutOfBoundsStub(ModuleGenerator& mg)
// If an exception is thrown, simply pop all frames (since asm.js does not
// contain try/catch). To do this:
// 1. Restore 'sp' to it's value right after the PushRegsInMask in GenerateEntry.
// 2. PopRegsInMask to restore the caller's non-volatile registers.
// 3. Return (to CallAsmJS).
static Offsets
GenerateThrow(MacroAssembler& masm)
{
MacroAssembler& masm = mg.masm();
masm.haltingAlign(CodeAlignment);
// Generate the out-of-bounds stub unconditionally since it may always be
// used by the signal handler.
mg.defineOutOfBoundsExit(masm.currentOffset());
BindJumps(masm, JumpTarget::OutOfBounds);
Offsets offsets;
offsets.begin = masm.currentOffset();
// sp can be anything at this point, so ensure it is aligned when calling
// into C++. We unconditionally jump to throw so don't worry about restoring sp.
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
// We are about to pop all frames in this WasmActivation. Set fp to null to
// maintain the invariant that fp is either null or pointing to a valid
// frame.
Register scratch = ABIArgGenerator::NonArgReturnReg0;
masm.loadWasmActivation(scratch);
masm.storePtr(ImmWord(0), Address(scratch, WasmActivation::offsetOfFP()));
// OnOutOfBounds always throws.
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::OnOutOfBounds);
masm.jump(JumpTarget::Throw);
masm.setFramePushed(FramePushedForEntrySP);
masm.loadStackPtr(Address(scratch, WasmActivation::offsetOfEntrySP()));
masm.Pop(scratch);
masm.PopRegsInMask(NonVolatileRegs);
MOZ_ASSERT(masm.framePushed() == 0);
if (masm.oom())
return false;
masm.mov(ImmWord(0), ReturnReg);
masm.ret();
offsets.end = masm.currentOffset();
return mg.defineInlineStub(offsets);
return offsets;
}
Offsets
wasm::GenerateJumpTarget(MacroAssembler& masm, JumpTarget target)
{
switch (target) {
case JumpTarget::StackOverflow:
return GenerateStackOverflow(masm);
case JumpTarget::ConversionError:
return GenerateErrorStub(masm, SymbolicAddress::OnImpreciseConversion);
case JumpTarget::OutOfBounds:
return GenerateErrorStub(masm, SymbolicAddress::OnOutOfBounds);
case JumpTarget::Throw:
return GenerateThrow(masm);
case JumpTarget::Limit:
break;
}
MOZ_CRASH("bad JumpTarget");
}
static const LiveRegisterSet AllRegsExceptSP(
@@ -884,16 +851,11 @@ static const LiveRegisterSet AllRegsExceptSP(
// Unfortunately, loading this requires a scratch register which we don't have
// after restoring all registers. To hack around this, push the resumePC on the
// stack so that it can be popped directly into PC.
static bool
GenerateInterruptStub(ModuleGenerator& mg)
Offsets
wasm::GenerateInterruptStub(MacroAssembler& masm)
{
MacroAssembler& masm = mg.masm();
masm.haltingAlign(CodeAlignment);
// Generate the interrupt stub unconditionally since it may always be used
// by the signal handler.
mg.defineInterruptExit(masm.currentOffset());
Offsets offsets;
offsets.begin = masm.currentOffset();
@@ -1040,89 +1002,6 @@ GenerateInterruptStub(ModuleGenerator& mg)
# error "Unknown architecture!"
#endif
if (masm.oom())
return false;
offsets.end = masm.currentOffset();
return mg.defineInlineStub(offsets);
return offsets;
}
// If an exception is thrown, simply pop all frames (since asm.js does not
// contain try/catch). To do this:
// 1. Restore 'sp' to it's value right after the PushRegsInMask in GenerateEntry.
// 2. PopRegsInMask to restore the caller's non-volatile registers.
// 3. Return (to CallAsmJS).
static bool
GenerateThrowStub(ModuleGenerator& mg)
{
MacroAssembler& masm = mg.masm();
masm.haltingAlign(CodeAlignment);
if (masm.jumpSites()[JumpTarget::Throw].empty())
return true;
BindJumps(masm, JumpTarget::Throw);
Offsets offsets;
offsets.begin = masm.currentOffset();
// We are about to pop all frames in this WasmActivation. Set fp to null to
// maintain the invariant that fp is either null or pointing to a valid
// frame.
Register scratch = ABIArgGenerator::NonArgReturnReg0;
masm.loadWasmActivation(scratch);
masm.storePtr(ImmWord(0), Address(scratch, WasmActivation::offsetOfFP()));
masm.setFramePushed(FramePushedForEntrySP);
masm.loadStackPtr(Address(scratch, WasmActivation::offsetOfEntrySP()));
masm.Pop(scratch);
masm.PopRegsInMask(NonVolatileRegs);
MOZ_ASSERT(masm.framePushed() == 0);
masm.mov(ImmWord(0), ReturnReg);
masm.ret();
if (masm.oom())
return false;
offsets.end = masm.currentOffset();
return mg.defineInlineStub(offsets);
}
bool
wasm::GenerateStubs(ModuleGenerator& mg)
{
for (unsigned i = 0; i < mg.numExports(); i++) {
if (!GenerateEntry(mg, i))
return false;
}
for (size_t i = 0; i < mg.numImports(); i++) {
ProfilingOffsets interp;
if (!GenerateInterpExitStub(mg, i, &interp))
return false;
ProfilingOffsets jit;
if (!GenerateJitExitStub(mg, i, &jit))
return false;
if (!mg.defineImport(i, interp, jit))
return false;
}
if (!GenerateStackOverflowStub(mg))
return false;
if (!GenerateConversionErrorStub(mg))
return false;
if (!GenerateOutOfBoundsStub(mg))
return false;
if (!GenerateInterruptStub(mg))
return false;
// The throw stub must go last since the other stubs use it.
return GenerateThrowStub(mg);
}
+14 -2
View File
@@ -24,8 +24,20 @@
namespace js {
namespace wasm {
bool
GenerateStubs(ModuleGenerator& mg);
extern Offsets
GenerateEntry(jit::MacroAssembler& masm, uint32_t target, const Sig& sig, bool usesHeap);
extern ProfilingOffsets
GenerateInterpExit(jit::MacroAssembler& masm, const Import& import, uint32_t importIndex);
extern ProfilingOffsets
GenerateJitExit(jit::MacroAssembler& masm, const Import& import, bool usesHeap);
extern Offsets
GenerateJumpTarget(jit::MacroAssembler& masm, JumpTarget target);
extern Offsets
GenerateInterruptStub(jit::MacroAssembler& masm);
} // namespace wasm
} // namespace js
+3 -2
View File
@@ -38,6 +38,7 @@ class PropertyName;
namespace wasm {
using mozilla::EnumeratedArray;
using mozilla::Move;
using mozilla::DebugOnly;
using mozilla::MallocSizeOf;
@@ -271,7 +272,7 @@ typedef Vector<const DeclaredSig*, 0, SystemAllocPolicy> DeclaredSigPtrVector;
struct Offsets
{
MOZ_IMPLICIT Offsets(uint32_t begin = 0, uint32_t end = 0)
explicit Offsets(uint32_t begin = 0, uint32_t end = 0)
: begin(begin), end(end)
{}
@@ -576,7 +577,7 @@ enum class JumpTarget
Limit
};
typedef mozilla::EnumeratedArray<JumpTarget, JumpTarget::Limit, Uint32Vector> JumpSiteArray;
typedef EnumeratedArray<JumpTarget, JumpTarget::Limit, Uint32Vector> JumpSiteArray;
// The CompileArgs struct captures global parameters that affect all wasm code
// generation. It also currently is the single source of truth for whether or
@@ -0,0 +1,51 @@
load(libdir + "asm.js");
load(libdir + "asserts.js");
var fatFunc = USE_ASM + '\n';
for (var i = 0; i < 100; i++)
fatFunc += "function f" + i + "() { return ((f" + (i+1) + "()|0)+1)|0 }\n";
fatFunc += "function f100() { return 42 }\n";
fatFunc += "return f0";
for (var signals = 0; signals <= 1; signals++) {
setJitCompilerOption("signals.enable", signals);
for (let threshold of [0, 50, 100, 5000, -1]) {
setJitCompilerOption("jump-threshold", threshold);
assertEq(asmCompile(
USE_ASM + `
function h() { return ((g()|0)+2)|0 }
function g() { return ((f()|0)+1)|0 }
function f() { return 42 }
return h
`)()(), 45);
if (isSimdAvailable() && this.SIMD) {
var buf = new ArrayBuffer(BUF_MIN);
new Int32Array(buf)[0] = 10;
new Float32Array(buf)[1] = 42;
assertEq(asmCompile('stdlib', 'ffis', 'buf',
USE_ASM + `
var H = new stdlib.Uint8Array(buf);
var i4 = stdlib.SIMD.Int32x4;
var f4 = stdlib.SIMD.Float32x4;
var i4load = i4.load;
var f4load = f4.load;
var toi4 = i4.fromFloat32x4;
var i4ext = i4.extractLane;
function f(i) { i=i|0; return i4ext(i4load(H, i), 0)|0 }
function g(i) { i=i|0; return (i4ext(toi4(f4load(H, i)),1) + (f(i)|0))|0 }
function h(i) { i=i|0; return g(i)|0 }
return h
`)(this, null, buf)(0), 52);
}
enableSPSProfiling();
asmLink(asmCompile(USE_ASM + 'function f() {} function g() { f() } function h() { g() } return h'))();
disableSPSProfiling();
assertEq(asmCompile(fatFunc)()(), 142);
}
}
@@ -215,6 +215,15 @@ if (isSimdAvailable() && typeof SIMD !== 'undefined') {
}
// Thunks
setJitCompilerOption("jump-threshold", 0);
var h = asmLink(asmCompile(USE_ASM + 'function f() {} function g() { f() } function h() { g() } return h'));
enableSingleStepProfiling();
h();
var stacks = disableSingleStepProfiling();
assertStackContainsSeq(stacks, ">,h,>,g,h,>,f,g,h,>,g,h,>,h,>,>");
setJitCompilerOption("jump-threshold", -1);
// This takes forever to run.
// Stack-overflow exit test
//var limit = -1;
+4
View File
@@ -161,6 +161,10 @@ DefaultJitOptions::DefaultJitOptions()
// The bytecode length limit for small function.
SET_DEFAULT(smallFunctionMaxBytecodeLength_, 120);
// An artificial testing limit for the maximum supported offset of
// pc-relative jump and call instructions.
SET_DEFAULT(jumpThreshold, UINT32_MAX);
// Force how many invocation or loop iterations are needed before compiling
// a function with the highest ionmonkey optimization level.
// (i.e. OptimizationLevel_Normal)
+1
View File
@@ -72,6 +72,7 @@ struct DefaultJitOptions
uint32_t maxStackArgs;
uint32_t osrPcMismatchesBeforeRecompile;
uint32_t smallFunctionMaxBytecodeLength_;
uint32_t jumpThreshold;
mozilla::Maybe<uint32_t> forcedDefaultIonWarmUpThreshold;
mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator;
-7
View File
@@ -85,13 +85,6 @@ MacroAssembler::call(const wasm::CallSiteDesc& desc, const Register reg)
append(desc, l, framePushed());
}
void
MacroAssembler::call(const wasm::CallSiteDesc& desc, Label* label)
{
CodeOffset l = call(label);
append(desc, l, framePushed());
}
void
MacroAssembler::call(const wasm::CallSiteDesc& desc, AsmJSInternalCallee callee)
{
+7 -1
View File
@@ -498,12 +498,18 @@ class MacroAssembler : public MacroAssemblerSpecific
void call(JitCode* c) PER_SHARED_ARCH;
inline void call(const wasm::CallSiteDesc& desc, const Register reg);
inline void call(const wasm::CallSiteDesc& desc, Label* label);
inline void call(const wasm::CallSiteDesc& desc, AsmJSInternalCallee callee);
CodeOffset callWithPatch() PER_SHARED_ARCH;
void patchCall(uint32_t callerOffset, uint32_t calleeOffset) PER_SHARED_ARCH;
// Thunks provide the ability to jump to any uint32_t offset from any other
// uint32_t offset without using a constant pool (thus returning a simple
// CodeOffset instead of a CodeOffsetJump).
CodeOffset thunkWithPatch() PER_SHARED_ARCH;
void patchThunk(uint32_t thunkOffset, uint32_t targetOffset) PER_SHARED_ARCH;
static void repatchThunk(uint8_t* code, uint32_t thunkOffset, uint32_t targetOffset) PER_SHARED_ARCH;
// Push the return address and make a call. On platforms where this function
// is not defined, push the link register (pushReturnAddress) at the entry
// point of the callee.
+4
View File
@@ -36,6 +36,10 @@ static const int32_t NUNBOX32_TYPE_OFFSET = 4;
static const int32_t NUNBOX32_PAYLOAD_OFFSET = 0;
static const uint32_t ShadowStackSpace = 0;
// How far forward/back can a jump go? Provide a generous buffer for thunks.
static const uint32_t JumpImmediateRange = 25 * 1024 * 1024;
////
// These offsets are related to bailouts.
////
+60 -1
View File
@@ -10,6 +10,7 @@
#include "mozilla/DebugOnly.h"
#include "mozilla/MathAlgorithms.h"
#include "asmjs/WasmBinary.h"
#include "jit/arm/Simulator-arm.h"
#include "jit/Bailouts.h"
#include "jit/BaselineFrame.h"
@@ -4987,10 +4988,12 @@ MacroAssembler::call(JitCode* c)
CodeOffset
MacroAssembler::callWithPatch()
{
// For now, assume that it'll be nearby.
// The caller ensures that the call is always in range using thunks (below)
// as necessary.
as_bl(BOffImm(), Always, /* documentation */ nullptr);
return CodeOffset(currentOffset());
}
void
MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset)
{
@@ -4998,6 +5001,62 @@ MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset)
as_bl(BufferOffset(calleeOffset).diffB<BOffImm>(inst), Always, inst);
}
CodeOffset
MacroAssembler::thunkWithPatch()
{
static_assert(32 * 1024 * 1024 - JumpImmediateRange > wasm::MaxFuncs * 3 * sizeof(Instruction),
"always enough space for thunks");
// The goal of the thunk is to be able to jump to any address without the
// usual 32MiB branch range limitation. Additionally, to make the thunk
// simple to use, the thunk does not use the constant pool or require
// patching an absolute address. Instead, a relative offset is used which
// can be patched during compilation.
// Inhibit pools since these three words must be contiguous so that the offset
// calculations below are valid.
AutoForbidPools afp(this, 3);
// When pc is used, the read value is the address of the instruction + 8.
// This is exactly the address of the uint32 word we want to load.
ScratchRegisterScope scratch(*this);
ma_ldr(Address(pc, 0), scratch);
// Branch by making pc the destination register.
ma_add(pc, scratch, pc, LeaveCC, Always);
// Allocate space which will be patched by patchThunk().
CodeOffset u32Offset(currentOffset());
writeInst(UINT32_MAX);
return u32Offset;
}
void
MacroAssembler::patchThunk(uint32_t u32Offset, uint32_t targetOffset)
{
uint32_t* u32 = reinterpret_cast<uint32_t*>(editSrc(BufferOffset(u32Offset)));
MOZ_ASSERT(*u32 == UINT32_MAX);
uint32_t addOffset = u32Offset - 4;
MOZ_ASSERT(editSrc(BufferOffset(addOffset))->is<InstALU>());
// When pc is read as the operand of the add, its value is the address of
// the add instruction + 8.
*u32 = (targetOffset - addOffset) - 8;
}
void
MacroAssembler::repatchThunk(uint8_t* code, uint32_t u32Offset, uint32_t targetOffset)
{
uint32_t* u32 = reinterpret_cast<uint32_t*>(code + u32Offset);
uint32_t addOffset = u32Offset - 4;
MOZ_ASSERT(reinterpret_cast<Instruction*>(code + addOffset)->is<InstALU>());
*u32 = (targetOffset - addOffset) - 8;
}
void
MacroAssembler::pushReturnAddress()
{
+5
View File
@@ -298,6 +298,11 @@ static const uint32_t ION_FRAME_SLACK_SIZE = 24;
static const uint32_t ShadowStackSpace = 0;
// TODO:
// This constant needs to be updated to account for whatever near/far branching
// strategy is used by ARM64.
static const uint32_t JumpImmediateRange = UINT32_MAX;
static const uint32_t ABIStackAlignment = 16;
static const uint32_t CodeAlignment = 16;
static const bool StackKeptAligned = false;
+18
View File
@@ -566,6 +566,24 @@ MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset)
MOZ_CRASH("NYI");
}
CodeOffset
MacroAssembler::thunkWithPatch()
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::patchThunk(uint32_t thunkOffset, uint32_t targetOffset)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::repatchThunk(uint8_t* code, uint32_t thunkOffset, uint32_t targetOffset)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::pushReturnAddress()
{
-8
View File
@@ -2474,14 +2474,6 @@ class MacroAssemblerCompat : public vixl::MacroAssembler
void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label* label);
void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label* label);
void appendCallSite(const wasm::CallSiteDesc& desc) {
MOZ_CRASH("appendCallSite");
}
void callExit(wasm::SymbolicAddress imm, uint32_t stackArgBytes) {
MOZ_CRASH("callExit");
}
void profilerEnterFrame(Register framePtr, Register scratch) {
AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation());
loadPtr(activation, scratch);
@@ -1156,6 +1156,24 @@ MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset)
PatchedImmPtr((const void*)calleeOffset));
}
CodeOffset
MacroAssembler::thunkWithPatch()
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::patchThunk(uint32_t callerOffset, uint32_t calleeOffset)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::repatchThunk(uint8_t* code, uint32_t callerOffset, uint32_t calleeOffset)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::call(wasm::SymbolicAddress target)
{
+1
View File
@@ -141,6 +141,7 @@ inline bool hasUnaliasedDouble() { MOZ_CRASH(); }
inline bool hasMultiAlias() { MOZ_CRASH(); }
static const uint32_t ShadowStackSpace = 0;
static const uint32_t JumpImmediateRange = INT32_MAX;
#ifdef JS_NUNBOX32
static const int32_t NUNBOX32_TYPE_OFFSET = 4;
+2 -1
View File
@@ -754,7 +754,8 @@ class AssemblerShared
void append(wasm::JumpTarget target, uint32_t offset) {
enoughMemory_ &= jumpsites_[target].append(offset);
}
const wasm::JumpSiteArray& jumpSites() const { return jumpsites_; }
const wasm::JumpSiteArray& jumpSites() { return jumpsites_; }
void clearJumpSites() { for (auto& v : jumpsites_) v.clear(); }
void append(wasm::HeapAccess access) { enoughMemory_ &= heapAccesses_.append(access); }
wasm::HeapAccessVector&& extractHeapAccesses() { return Move(heapAccesses_); }
@@ -57,6 +57,8 @@ static const uint32_t ShadowStackSpace = 32;
static const uint32_t ShadowStackSpace = 0;
#endif
static const uint32_t JumpImmediateRange = INT32_MAX;
class Registers {
public:
typedef uint8_t Code;
@@ -1025,6 +1025,16 @@ class AssemblerX86Shared : public AssemblerShared
unsigned char* code = masm.data();
X86Encoding::SetRel32(code + callerOffset, code + calleeOffset);
}
CodeOffset thunkWithPatch() {
return CodeOffset(masm.jmp().offset());
}
void patchThunk(uint32_t thunkOffset, uint32_t targetOffset) {
unsigned char* code = masm.data();
X86Encoding::SetRel32(code + thunkOffset, code + targetOffset);
}
static void repatchThunk(uint8_t* code, uint32_t thunkOffset, uint32_t targetOffset) {
X86Encoding::SetRel32(code + thunkOffset, code + targetOffset);
}
void breakpoint() {
masm.int3();
@@ -544,6 +544,24 @@ MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset)
Assembler::patchCall(callerOffset, calleeOffset);
}
CodeOffset
MacroAssembler::thunkWithPatch()
{
return Assembler::thunkWithPatch();
}
void
MacroAssembler::patchThunk(uint32_t thunkOffset, uint32_t targetOffset)
{
Assembler::patchThunk(thunkOffset, targetOffset);
}
void
MacroAssembler::repatchThunk(uint8_t* code, uint32_t thunkOffset, uint32_t targetOffset)
{
Assembler::repatchThunk(code, thunkOffset, targetOffset);
}
void
MacroAssembler::callAndPushReturnAddress(Register reg)
{
+7
View File
@@ -5873,6 +5873,13 @@ JS_SetGlobalJitCompilerOption(JSRuntime* rt, JSJitCompilerOption opt, uint32_t v
JitSpew(js::jit::JitSpew_IonScripts, "Disable signals");
}
break;
case JSJITCOMPILER_JUMP_THRESHOLD:
if (value == uint32_t(-1)) {
jit::DefaultJitOptions defaultValues;
value = defaultValues.jumpThreshold;
}
jit::JitOptions.jumpThreshold = value;
break;
default:
break;
}
+2 -1
View File
@@ -5292,7 +5292,8 @@ JS_SetOffthreadIonCompilationEnabled(JSRuntime* rt, bool enabled);
Register(ION_ENABLE, "ion.enable") \
Register(BASELINE_ENABLE, "baseline.enable") \
Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable") \
Register(SIGNALS_ENABLE, "signals.enable")
Register(SIGNALS_ENABLE, "signals.enable") \
Register(JUMP_THRESHOLD, "jump-threshold")
typedef enum JSJitCompilerOption {
#define JIT_COMPILER_DECLARE(key, str) \
+1
View File
@@ -6403,6 +6403,7 @@ nsDisplayVR::BuildLayer(nsDisplayListBuilder* aBuilder,
newContainerParameters, nullptr, flags);
container->SetVRDeviceID(mHMD->GetDeviceInfo().GetDeviceID());
container->SetInputFrameID(mHMD->GetSensorState().inputFrameID);
container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(),
/*the value is irrelevant*/nullptr);
+2 -1
View File
@@ -104,7 +104,8 @@ DoGetBlobAsString(T* aThis, uint32_t aIndex, V& aValue)
aThis->GetBlob(aIndex, &size, reinterpret_cast<uint8_t**>(&blob));
NS_ENSURE_SUCCESS(rv, rv);
aValue.Adopt(blob, size / sizeof(char_type));
aValue.Assign(blob, size / sizeof(char_type));
delete[] blob;
return NS_OK;
}
@@ -20,3 +20,6 @@ test_container = true
; loop tests
[include:../../../../../browser/components/loop/manifest.ini]
; microformats tests
[include:../../../../../toolkit/components/microformats/manifest.ini]
@@ -0,0 +1,9 @@
{
"extends": [
"../../.eslintrc"
],
"rules": {
"eol-last": 0,
}
}
@@ -0,0 +1,9 @@
[DEFAULT]
b2g = false
browser = true
qemu = false
[test/marionette/test_standards.py]
[test/marionette/test_modules.py]
[test/marionette/test_interface.py]
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -4,9 +4,8 @@
# 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/.
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
EXTRA_JS_MODULES += [
'microformat-shiv.js',
'Microformats.js',
]
@@ -0,0 +1,107 @@
/*
Unit test for count
*/
assert = chai.assert;
describe('Microformat.count', function() {
it('count', function(){
var doc,
node,
result;
var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a><a class="h-card" href="http://janedoe.net"><span class="p-name">Jane</span></a><a class="h-event" href="http://janedoe.net"><span class="p-name">Event</span><span class="dt-start">2015-07-01</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = html;
doc.body.appendChild(node);
options ={
'node': node,
};
result = Microformats.count(options);
assert.deepEqual( result, {'h-event': 1,'h-card': 2} );
});
it('count rels', function(){
var doc,
node,
result;
var html = '<link href="http://glennjones.net/notes/atom" rel="notes alternate" title="Notes" type="application/atom+xml" /><a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a><a class="h-card" href="http://janedoe.net"><span class="p-name">Jane</span></a><a class="h-event" href="http://janedoe.net"><span class="p-name">Event</span><span class="dt-start">2015-07-01</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = html;
doc.body.appendChild(node);
options ={
'node': node,
};
result = Microformats.count(options);
assert.deepEqual( result, {'h-event': 1,'h-card': 2, 'rels': 1} );
});
it('count - no results', function(){
var doc,
node,
result;
var html = '<span class="p-name">Jane</span>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = html;
doc.body.appendChild(node);
options ={
'node': node,
};
result = Microformats.count(options);
assert.deepEqual( result, {} );
});
it('count - no options', function(){
var result;
result = Microformats.count({});
assert.deepEqual( result, {} );
});
it('count - options.html', function(){
var options = {},
result;
options.html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a><a class="h-card" href="http://janedoe.net"><span class="p-name">Jane</span></a><a class="h-event" href="http://janedoe.net"><span class="p-name">Event</span><span class="dt-start">2015-07-01</span></a>';
result = Microformats.count(options);
assert.deepEqual( result, {'h-event': 1,'h-card': 2} );
});
});
@@ -0,0 +1,37 @@
/*
Unit test for get
*/
assert = chai.assert;
describe('experimental', function() {
it('h-geo - geo data writen as lat;lon', function(){
var expected = {
'items': [{
'type': ['h-geo'],
'properties': {
'name': ['30.267991;-97.739568'],
'latitude': [30.267991],
'longitude': [-97.739568]
}
}],
'rels': {},
'rel-urls': {}
},
options = {
'html': '<div class="h-geo">30.267991;-97.739568</div>',
'baseUrl': 'http://example.com',
'dateFormat': 'html5',
'parseLatLonGeo': true
};
var result = Microformats.get(options);
assert.deepEqual( result, expected );
});
});
@@ -0,0 +1,595 @@
/*
Unit test for get
*/
assert = chai.assert;
describe('Microformat.get', function() {
var expected = {
'items': [{
'type': ['h-card'],
'properties': {
'name': ['Glenn Jones'],
'url': ['http://glennjones.net']
}
}],
'rels': {
'bookmark': ['http://glennjones.net'],
'alternate': ['http://example.com/fr'],
'home': ['http://example.com/fr']
},
'rel-urls': {
'http://glennjones.net': {
'text': 'Glenn Jones',
'rels': ['bookmark']
},
'http://example.com/fr': {
'media': 'handheld',
'hreflang': 'fr',
'text': 'French mobile homepage',
'rels': ['alternate', 'home']
}
}
},
html = '<div class="h-card"><a class="p-name u-url" rel="bookmark" href="http://glennjones.net">Glenn Jones</a></div><a rel="alternate home" href="http://example.com/fr" media="handheld" hreflang="fr">French mobile homepage</a>';
it('get - no options.node parse this document', function(){
var result;
result = Microformats.get({});
assert.deepEqual( result.items, [] );
});
it('get - standard', function(){
var doc,
node,
options,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = html;
doc.body.appendChild(node);
options ={
'node': node,
'baseUrl': 'http://example.com',
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, expected );
});
it('get - doc pass to node', function(){
var doc,
node,
options,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = html;
doc.body.appendChild(node);
options ={
'node': doc,
'baseUrl': 'http://example.com',
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, expected );
});
it('get - pass base tag', function(){
var doc,
node,
options,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = html + '<base href="http://example.com">';
doc.body.appendChild(node);
options ={
'node': node,
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, expected );
});
it('get - pass no document', function(){
var doc,
node,
options,
parser,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = html + '<base href="http://example.com">';
doc.body.appendChild(node);
options ={
'node': doc,
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, expected );
});
it('get - textFormat: normalised', function(){
var doc,
node,
options,
result;
var altHTML = '<a class="h-card" href="http://glennjones.net">\n';
altHTML += ' <span class="p-given-name">Glenn</span>\n';
altHTML += ' <span class="p-family-name">Jones</span>\n';
altHTML += '</a>\n';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'textFormat': 'normalised',
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.equal( result.items[0].properties.name[0], 'Glenn Jones' );
});
it('get - textFormat: whitespace', function(){
var doc,
node,
options,
parser,
result;
var altHTML = '<a class="h-card" href="http://glennjones.net">\n';
altHTML += ' <span class="p-given-name">Glenn</span>\n';
altHTML += ' <span class="p-family-name">Jones</span>\n';
altHTML += '</a>\n';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'textFormat': 'whitespace',
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.equal( result.items[0].properties.name[0], '\n Glenn\n Jones\n' );
});
it('get - textFormat: whitespacetrimmed', function(){
var doc,
node,
options,
result;
var altHTML = '<a class="h-card" href="http://glennjones.net">\n';
altHTML += ' <span class="p-given-name">Glenn</span>\n';
altHTML += ' <span class="p-family-name">Jones</span>\n';
altHTML += '</a>\n';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'textFormat': 'whitespacetrimmed',
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.equal( result.items[0].properties.name[0], 'Glenn\n Jones' );
});
it('get - dateFormat: auto', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'dateFormat': 'auto'
};
result = Microformats.get(options);
assert.equal( result.items[0].properties.start[0], '2015-07-01t17:30z' );
});
it('get - dateFormat: w3c', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'dateFormat': 'w3c'
};
result = Microformats.get(options);
assert.equal( result.items[0].properties.start[0], '2015-07-01T17:30Z' );
});
it('get - dateFormat: html5', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.equal( result.items[0].properties.start[0], '2015-07-01 17:30Z' );
});
it('get - dateFormat: rfc3339', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'dateFormat': 'rfc3339'
};
result = Microformats.get(options);
assert.equal( result.items[0].properties.start[0], '20150701T1730Z' );
});
it('get - filters h-card', function(){
var doc,
node,
options,
parser,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div><a class="h-card" href="http://glennjones.net">Glenn Jones</a>';
var altExpected = {
'items': [{
'type': ['h-card'],
'properties': {
'name': ['Glenn Jones'],
'url': ['http://glennjones.net']
}
}],
'rels': {},
'rel-urls': {}
}
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'filters': ['h-card'],
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, altExpected );
});
it('get - filters h-event', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div><a class="h-card" href="http://glennjones.net">Glenn Jones</a>';
var altExpected = {
'items': [{
'type': ['h-event'],
'properties': {
'name': ['Pub'],
'start': ['2015-07-01 17:30Z']
}
}],
'rels': {},
'rel-urls': {}
}
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'filters': ['h-event'],
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, altExpected );
});
it('get - filters h-card and h-event', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div><a class="h-card" href="http://glennjones.net">Glenn Jones</a>';
var altExpected = {
'items': [{
'type': ['h-event'],
'properties': {
'name': ['Pub'],
'start': ['2015-07-01 17:30Z']
}
},
{
'type': ['h-card'],
'properties': {
'name': ['Glenn Jones'],
'url': ['http://glennjones.net']
}
}],
'rels': {},
'rel-urls': {}
}
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'filter': ['h-event'],
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, altExpected );
});
it('get - filters h-card no result', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
var altExpected = {
'items': [],
'rels': {},
'rel-urls': {}
}
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'filters': ['h-card'],
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, altExpected );
});
it('get - filters h-card match v1 format', function(){
var doc,
node,
options,
parser,
result;
var altHTML = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn Jones</span></a>';
var altExpected = {
'items': [{
'type': ['h-card'],
'properties': {
'name': ['Glenn Jones']
}
}],
'rels': {},
'rel-urls': {}
}
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'filter': ['h-card'],
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, altExpected );
});
it('get - add new v1 format through options', function(){
var doc,
node,
options,
result;
var altHTML = '<div class="hpayment">£<span class="amount">36.78</span></div>';
var altExpected = {
'items': [{
'type': ['h-payment'],
'properties': {
'amount': ['36.78']
}
}],
'rels': {},
'rel-urls': {}
};
var v1Definition = {
root: 'hpayment',
name: 'h-payment',
properties: {
'amount': {}
}
};
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
options ={
'node': node,
'maps': v1Definition,
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, altExpected );
});
it('get - options.html', function(){
var options,
result;
options ={
'html': html,
'baseUrl': 'http://example.com',
'dateFormat': 'html5'
};
result = Microformats.get(options);
assert.deepEqual( result, expected );
});
});
@@ -0,0 +1,220 @@
/*
Unit test for getParent
*/
assert = chai.assert;
describe('Microformat.getParent', function() {
var HTML = '<div class="h-event"><span class="p-name">Pub</span><span class="dt-start">2015-07-01t17:30z</span></div>';
var emptyExpected = {
"items": [],
"rels": {},
"rel-urls": {}
};
var expected = {
"items": [
{
"type": [
"h-event"
],
"properties": {
"name": [
"Pub"
],
"start": [
"2015-07-01 17:30Z"
]
}
}
],
"rels": {},
"rel-urls": {}
};
var options = {'dateFormat': 'html5'};
it('getParent with parent', function(){
var doc,
node,
span,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = HTML;
doc.body.appendChild(node);
span = doc.querySelector('.dt-start');
result = Microformats.getParent(span,options);
assert.deepEqual( result, expected );
});
it('getParent without parent', function(){
var doc,
node,
parser,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = HTML;
doc.body.appendChild(node);
result = Microformats.getParent(node,options);
assert.deepEqual( result, emptyExpected );
});
it('getParent found with option.filters', function(){
var doc,
node,
span,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = HTML;
doc.body.appendChild(node);
span = doc.querySelector('.dt-start');
result = Microformats.getParent( span, {'filters': ['h-event'], 'dateFormat': 'html5'} );
assert.deepEqual( result, expected );
});
it('getParent not found with option.filters', function(){
var doc,
node,
span,
result;
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = HTML;
doc.body.appendChild(node);
span = doc.querySelector('.dt-start');
result = Microformats.getParent( span, {'filters': ['h-card'], 'dateFormat': 'html5'} );
assert.deepEqual( result, emptyExpected );
});
it('getParent use option.filters to up through h-*', function(){
var doc,
node,
span,
result;
var altHTML = '<div class="h-entry"><h1 class="p-name">test</h1><div class="e-content">this</div><a class="p-author h-card" href="http://glennjones.net"><span class="p-name">Glenn Jones</span></a><span class="dt-publish">2015-07-01t17:30z</span></div>';
var altExpected = {
"items": [
{
"type": [
"h-entry"
],
"properties": {
"name": [
"test"
],
"content": [
{
"value": "this",
"html": "this"
}
],
"author": [
{
"value": "Glenn Jones",
"type": [
"h-card"
],
"properties": {
"name": [
"Glenn Jones"
],
"url": [
"http://glennjones.net"
]
}
}
],
"publish": [
"2015-07-01 17:30Z"
]
}
}
],
"rels": {},
"rel-urls": {}
};
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
span = doc.querySelector('.h-card .p-name');
result = Microformats.getParent( span, {'filters': ['h-entry'], 'dateFormat': 'html5'} );
assert.deepEqual( result, altExpected );
});
it('getParent stop at first h-* parent', function(){
var doc,
node,
span,
result;
var altHTML = '<div class="h-entry"><h1 class="p-name">test</h1><div class="e-content">this</div><a class="p-author h-card" href="http://glennjones.net"><span class="p-name">Glenn Jones</span></a><span class="dt-publish">2015-07-01t17:30z</span></div>';
var altExpected = {
"items": [
{
"type": [
"h-card"
],
"properties": {
"name": [
"Glenn Jones"
],
"url": [
"http://glennjones.net"
]
}
}
],
"rels": {},
"rel-urls": {}
};
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
node.innerHTML = altHTML;
doc.body.appendChild(node);
span = doc.querySelector('.h-card .p-name');
result = Microformats.getParent( span, options );
assert.deepEqual( result, altExpected );
});
});
@@ -0,0 +1,185 @@
/*
Unit test for hasMicroformat
*/
assert = chai.assert;
describe('Microformat.hasMicroformats', function() {
it('true - v2 on node', function(){
var doc,
node;
var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.hasMicroformats( node ) );
});
it('true - v1 on node', function(){
var doc,
node;
var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.hasMicroformats( node ) );
});
it('true - v2 filter on node', function(){
var doc,
node;
var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.hasMicroformats( node, {'filters': ['h-card']} ) );
});
it('true - v1 filter on node', function(){
var doc,
node;
var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.hasMicroformats( node, {'filters': ['h-card']} ) );
});
it('false - v2 filter on node', function(){
var doc,
node;
var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isFalse( Microformats.hasMicroformats( node, {'filters': ['h-entry']} ) );
});
it('false - property', function(){
var doc,
node,
parser;
var html = '<span class="p-name">Glenn</span>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'span' );
assert.isFalse( Microformats.hasMicroformats( node ) );
});
it('false - no class', function(){
var doc,
node,
parser;
var html = '<span>Glenn</span>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'span' );
assert.isFalse( Microformats.hasMicroformats( node ) );
});
it('false - no node', function(){
assert.isFalse( Microformats.hasMicroformats( ) );
});
it('false - undefined node', function(){
assert.isFalse( Microformats.hasMicroformats( undefined ) );
});
it('true - child', function(){
var doc,
node;
var html = '<section><div><a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a></div></section>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
assert.isTrue( Microformats.hasMicroformats( node ) );
});
it('true - document', function(){
var doc,
node;
var html = '<html><head></head><body><section><div><a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a></div></section></body></html>';
var dom = new DOMParser();
doc = dom.parseFromString( html, 'text/html' );
assert.isTrue( Microformats.hasMicroformats( doc ) );
});
});
@@ -0,0 +1,68 @@
<html><head><title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="../static/css/mocha.css" />
<script src="../static/javascript/chai.js"></script>
<script src="../static/javascript/mocha.js"></script>
<link rel="stylesheet" href="../static/css/mocha-custom.css" />
<script src="../static/javascript/DOMParser.js"></script>
<script data-cover src="../../microformat-shiv.js"></script>
<script>
var uncaughtError;
window.addEventListener("error", function(error) {
uncaughtError = error;
});
var consoleWarn = console.warn;
var caughtWarnings = [];
console.warn = function() {
var args = Array.slice(arguments);
caughtWarnings.push(args);
consoleWarn.apply(console, args);
};
</script>
<script>
chai.config.includeStack = true;
mocha.setup({ui: 'bdd', timeout: 10000});
</script>
<script src="../interface-tests/get-test.js"></script>
<script src="../interface-tests/getParent-test.js"></script>
<script src="../interface-tests/count-test.js"></script>
<script src="../interface-tests/isMicroformat-test.js"></script>
<script src="../interface-tests/hasMicroformats-test.js"></script>
<script src="../interface-tests/experimental-test.js"></script>
</head><body>
<h3 class="capitalize">Microformats-shiv: interface tests</h3>
<div id="mocha"></div>
</body>
<script>
describe("Uncaught Error Check", function() {
it("should load the tests without errors", function() {
chai.expect(uncaughtError && uncaughtError.message).to.be.undefined;
});
});
describe("Unexpected Warnings Check", function() {
it("should long only the warnings we expect", function() {
chai.expect(caughtWarnings.length).to.eql(0);
});
});
mocha.run(function () {
var completeNode = document.createElement("p");
completeNode.setAttribute("id", "complete");
completeNode.appendChild(document.createTextNode("Complete"));
document.getElementById("mocha").appendChild(completeNode);
});
</script>
</body></html>
@@ -0,0 +1,146 @@
/*
Unit test for isMicroformat
*/
assert = chai.assert;
describe('Microformat.isMicroformat', function() {
it('true - v2', function(){
var doc,
node;
var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.isMicroformat( node ) );
});
it('true - v1', function(){
var doc,
node;
var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.isMicroformat( node ) );
});
it('true - v2 filter', function(){
var doc,
node;
var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.isMicroformat( node, {'filters': ['h-card']} ) );
});
it('true - v1 filter', function(){
var doc,
node;
var html = '<a class="vcard" href="http://glennjones.net"><span class="fn">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isTrue( Microformats.isMicroformat( node, {'filters': ['h-card']} ) );
});
it('false - v2 filter', function(){
var doc,
node;
var html = '<a class="h-card" href="http://glennjones.net"><span class="p-name">Glenn</span></a>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'a' );
assert.isFalse( Microformats.isMicroformat( node, {'filters': ['h-entry']} ) );
});
it('false - property', function(){
var doc,
node;
var html = '<span class="p-name">Glenn</span>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'span' );
assert.isFalse( Microformats.isMicroformat( node ) );
});
it('false - no class', function(){
var doc,
node;
var html = '<span>Glenn</span>';
doc = document.implementation.createHTMLDocument('New Document');
node = document.createElement('div');
doc.body.appendChild( node );
node.innerHTML = html;
node = doc.querySelector( 'span' );
assert.isFalse( Microformats.isMicroformat( node ) );
});
it('false - no node', function(){
assert.isFalse( Microformats.isMicroformat( ) );
});
it('false - undefined node', function(){
assert.isFalse( Microformats.isMicroformat( undefined ) );
});
});
@@ -0,0 +1,268 @@
/*!
dates
These functions are based on microformats implied rules for parsing date fragments from text.
They are not generalist date utilities and should only be used with the isodate.js module of this library.
Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
Dependencies utilities.js, isodate.js
*/
var Modules = (function (modules) {
modules.dates = {
/**
* does text contain am
*
* @param {String} text
* @return {Boolean}
*/
hasAM: function( text ) {
text = text.toLowerCase();
return(text.indexOf('am') > -1 || text.indexOf('a.m.') > -1);
},
/**
* does text contain pm
*
* @param {String} text
* @return {Boolean}
*/
hasPM: function( text ) {
text = text.toLowerCase();
return(text.indexOf('pm') > -1 || text.indexOf('p.m.') > -1);
},
/**
* remove am and pm from text and return it
*
* @param {String} text
* @return {String}
*/
removeAMPM: function( text ) {
return text.replace('pm', '').replace('p.m.', '').replace('am', '').replace('a.m.', '');
},
/**
* simple test of whether ISO date string is a duration i.e. PY17M or PW12
*
* @param {String} text
* @return {Boolean}
*/
isDuration: function( text ) {
if(modules.utils.isString( text )){
text = text.toLowerCase();
if(modules.utils.startWith(text, 'p') ){
return true;
}
}
return false;
},
/**
* is text a time or timezone
* i.e. HH-MM-SS or z+-HH-MM-SS 08:43 | 15:23:00:0567 | 10:34pm | 10:34 p.m. | +01:00:00 | -02:00 | z15:00 | 0843
*
* @param {String} text
* @return {Boolean}
*/
isTime: function( text ) {
if(modules.utils.isString(text)){
text = text.toLowerCase();
text = modules.utils.trim( text );
// start with timezone char
if( text.match(':') && ( modules.utils.startWith(text, 'z') || modules.utils.startWith(text, '-') || modules.utils.startWith(text, '+') )) {
return true;
}
// has ante meridiem or post meridiem
if( text.match(/^[0-9]/) &&
( this.hasAM(text) || this.hasPM(text) )) {
return true;
}
// contains time delimiter but not datetime delimiter
if( text.match(':') && !text.match(/t|\s/) ) {
return true;
}
// if it's a number of 2, 4 or 6 chars
if(modules.utils.isNumber(text)){
if(text.length === 2 || text.length === 4 || text.length === 6){
return true;
}
}
}
return false;
},
/**
* parses a time from text and returns 24hr time string
* i.e. 5:34am = 05:34:00 and 1:52:04p.m. = 13:52:04
*
* @param {String} text
* @return {String}
*/
parseAmPmTime: function( text ) {
var out = text,
times = [];
// if the string has a text : or am or pm
if(modules.utils.isString(out)) {
//text = text.toLowerCase();
text = text.replace(/[ ]+/g, '');
if(text.match(':') || this.hasAM(text) || this.hasPM(text)) {
if(text.match(':')) {
times = text.split(':');
} else {
// single number text i.e. 5pm
times[0] = text;
times[0] = this.removeAMPM(times[0]);
}
// change pm hours to 24hr number
if(this.hasPM(text)) {
if(times[0] < 12) {
times[0] = parseInt(times[0], 10) + 12;
}
}
// add leading zero's where needed
if(times[0] && times[0].length === 1) {
times[0] = '0' + times[0];
}
// rejoin text elements together
if(times[0]) {
text = times.join(':');
}
}
}
// remove am/pm strings
return this.removeAMPM(text);
},
/**
* overlays a time on a date to return the union of the two
*
* @param {String} date
* @param {String} time
* @param {String} format ( Modules.ISODate profile format )
* @return {Object} Modules.ISODate
*/
dateTimeUnion: function(date, time, format) {
var isodate = new modules.ISODate(date, format),
isotime = new modules.ISODate();
isotime.parseTime(this.parseAmPmTime(time), format);
if(isodate.hasFullDate() && isotime.hasTime()) {
isodate.tH = isotime.tH;
isodate.tM = isotime.tM;
isodate.tS = isotime.tS;
isodate.tD = isotime.tD;
return isodate;
} else {
if(isodate.hasFullDate()){
return isodate;
}
return new modules.ISODate();
}
},
/**
* concatenate an array of date and time text fragments to create an ISODate object
* used for microformat value and value-title rules
*
* @param {Array} arr ( Array of Strings )
* @param {String} format ( Modules.ISODate profile format )
* @return {Object} Modules.ISODate
*/
concatFragments: function (arr, format) {
var out = new modules.ISODate(),
i = 0,
value = '';
// if the fragment already contains a full date just return it once
if(arr[0].toUpperCase().match('T')) {
return new modules.ISODate(arr[0], format);
}else{
for(i = 0; i < arr.length; i++) {
value = arr[i];
// date pattern
if( value.charAt(4) === '-' && out.hasFullDate() === false ){
out.parseDate(value);
}
// time pattern
if( (value.indexOf(':') > -1 || modules.utils.isNumber( this.parseAmPmTime(value) )) && out.hasTime() === false ) {
// split time and timezone
var items = this.splitTimeAndZone(value);
value = items[0];
// parse any use of am/pm
value = this.parseAmPmTime(value);
out.parseTime(value);
// parse any timezone
if(items.length > 1){
out.parseTimeZone(items[1], format);
}
}
// timezone pattern
if(value.charAt(0) === '-' || value.charAt(0) === '+' || value.toUpperCase() === 'Z') {
if( out.hasTimeZone() === false ){
out.parseTimeZone(value);
}
}
}
return out;
}
},
/**
* parses text by splitting it into an array of time and timezone strings
*
* @param {String} text
* @return {Array} Modules.ISODate
*/
splitTimeAndZone: function ( text ){
var out = [text],
chars = ['-','+','z','Z'],
i = chars.length;
while (i--) {
if(text.indexOf(chars[i]) > -1){
out[0] = text.slice( 0, text.indexOf(chars[i]) );
out.push( text.slice( text.indexOf(chars[i]) ) );
break;
}
}
return out;
}
};
return modules;
} (Modules || {}));
@@ -0,0 +1,103 @@
// Based on https://gist.github.com/1129031 By Eli Grey, http://eligrey.com - Public domain.
// DO NOT use https://developer.mozilla.org/en-US/docs/Web/API/DOMParser example polyfill
// as it does not work with earlier versions of Chrome
(function(DOMParser) {
'use strict';
var DOMParser_proto;
var real_parseFromString;
var textHTML; // Flag for text/html support
var textXML; // Flag for text/xml support
var htmlElInnerHTML; // Flag for support for setting html element's innerHTML
// Stop here if DOMParser not defined
if (!DOMParser) {
return;
}
// Firefox, Opera and IE throw errors on unsupported types
try {
// WebKit returns null on unsupported types
textHTML = !!(new DOMParser()).parseFromString('', 'text/html');
} catch (er) {
textHTML = false;
}
// If text/html supported, don't need to do anything.
if (textHTML) {
return;
}
// Next try setting innerHTML of a created document
// IE 9 and lower will throw an error (can't set innerHTML of its HTML element)
try {
var doc = document.implementation.createHTMLDocument('');
doc.documentElement.innerHTML = '<title></title><div></div>';
htmlElInnerHTML = true;
} catch (er) {
htmlElInnerHTML = false;
}
// If if that failed, try text/xml
if (!htmlElInnerHTML) {
try {
textXML = !!(new DOMParser()).parseFromString('', 'text/xml');
} catch (er) {
textHTML = false;
}
}
// Mess with DOMParser.prototype (less than optimal...) if one of the above worked
// Assume can write to the prototype, if not, make this a stand alone function
if (DOMParser.prototype && (htmlElInnerHTML || textXML)) {
DOMParser_proto = DOMParser.prototype;
real_parseFromString = DOMParser_proto.parseFromString;
DOMParser_proto.parseFromString = function (markup, type) {
// Only do this if type is text/html
if (/^\s*text\/html\s*(?:;|$)/i.test(type)) {
var doc, doc_el, first_el;
// Use innerHTML if supported
if (htmlElInnerHTML) {
doc = document.implementation.createHTMLDocument('');
doc_el = doc.documentElement;
doc_el.innerHTML = markup;
first_el = doc_el.firstElementChild;
// Otherwise use XML method
} else if (textXML) {
// Make sure markup is wrapped in HTML tags
// Should probably allow for a DOCTYPE
if (!(/^<html.*html>$/i.test(markup))) {
markup = '<html>' + markup + '<\/html>';
}
doc = (new DOMParser()).parseFromString(markup, 'text/xml');
doc_el = doc.documentElement;
first_el = doc_el.firstElementChild;
}
// Is this an entire document or a fragment?
if (doc_el.childElementCount === 1 && first_el.localName.toLowerCase() === 'html') {
doc.replaceChild(first_el, doc_el);
}
return doc;
// If not text/html, send as-is to host method
} else {
return real_parseFromString.apply(this, arguments);
}
};
}
}(DOMParser));
@@ -0,0 +1,611 @@
/*
dom utilities
The purpose of this module is to abstract DOM functions away from the main parsing modules of the library.
It was created so the file can be replaced in node.js environment to make use of different types of light weight node.js DOM's
such as 'cherrio.js'. It also contains a number of DOM utilities which are used throughout the parser such as: 'getDescendant'
Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
MIT License: https://raw.github.com/glennjones/microformat-shiv/master/license.txt
Dependencies utilities.js
*/
var Modules = (function (modules) {
modules.domUtils = {
// blank objects for DOM
document: null,
rootNode: null,
/**
* gets DOMParser object
*
* @return {Object || undefined}
*/
getDOMParser: function () {
if (typeof DOMParser === undefined) {
try {
return Components.classes["@mozilla.org/xmlextras/domparser;1"]
.createInstance(Components.interfaces.nsIDOMParser);
} catch (e) {
return;
}
} else {
return new DOMParser();
}
},
/**
* configures what are the base DOM objects for parsing
*
* @param {Object} options
* @return {DOM Node} node
*/
getDOMContext: function( options ){
// if a node is passed
if(options.node){
this.rootNode = options.node;
}
// if a html string is passed
if(options.html){
//var domParser = new DOMParser();
var domParser = this.getDOMParser();
this.rootNode = domParser.parseFromString( options.html, 'text/html' );
}
// find top level document from rootnode
if(this.rootNode !== null){
if(this.rootNode.nodeType === 9){
this.document = this.rootNode;
this.rootNode = modules.domUtils.querySelector(this.rootNode, 'html');
}else{
// if it's DOM node get parent DOM Document
this.document = modules.domUtils.ownerDocument(this.rootNode);
}
}
// use global document object
if(!this.rootNode && document){
this.rootNode = modules.domUtils.querySelector(document, 'html');
this.document = document;
}
if(this.rootNode && this.document){
return {document: this.document, rootNode: this.rootNode};
}
return {document: null, rootNode: null};
},
/**
* gets the first DOM node
*
* @param {Dom Document}
* @return {DOM Node} node
*/
getTopMostNode: function( node ){
//var doc = this.ownerDocument(node);
//if(doc && doc.nodeType && doc.nodeType === 9 && doc.documentElement){
// return doc.documentElement;
//}
return node;
},
/**
* abstracts DOM ownerDocument
*
* @param {DOM Node} node
* @return {Dom Document}
*/
ownerDocument: function(node){
return node.ownerDocument;
},
/**
* abstracts DOM textContent
*
* @param {DOM Node} node
* @return {String}
*/
textContent: function(node){
if(node.textContent){
return node.textContent;
}else if(node.innerText){
return node.innerText;
}
return '';
},
/**
* abstracts DOM innerHTML
*
* @param {DOM Node} node
* @return {String}
*/
innerHTML: function(node){
return node.innerHTML;
},
/**
* abstracts DOM hasAttribute
*
* @param {DOM Node} node
* @param {String} attributeName
* @return {Boolean}
*/
hasAttribute: function(node, attributeName) {
return node.hasAttribute(attributeName);
},
/**
* does an attribute contain a value
*
* @param {DOM Node} node
* @param {String} attributeName
* @param {String} value
* @return {Boolean}
*/
hasAttributeValue: function(node, attributeName, value) {
return (this.getAttributeList(node, attributeName).indexOf(value) > -1);
},
/**
* abstracts DOM getAttribute
*
* @param {DOM Node} node
* @param {String} attributeName
* @return {String || null}
*/
getAttribute: function(node, attributeName) {
return node.getAttribute(attributeName);
},
/**
* abstracts DOM setAttribute
*
* @param {DOM Node} node
* @param {String} attributeName
* @param {String} attributeValue
*/
setAttribute: function(node, attributeName, attributeValue){
node.setAttribute(attributeName, attributeValue);
},
/**
* abstracts DOM removeAttribute
*
* @param {DOM Node} node
* @param {String} attributeName
*/
removeAttribute: function(node, attributeName) {
node.removeAttribute(attributeName);
},
/**
* abstracts DOM getElementById
*
* @param {DOM Node || DOM Document} node
* @param {String} id
* @return {DOM Node}
*/
getElementById: function(docNode, id) {
return docNode.querySelector( '#' + id );
},
/**
* abstracts DOM querySelector
*
* @param {DOM Node || DOM Document} node
* @param {String} selector
* @return {DOM Node}
*/
querySelector: function(docNode, selector) {
return docNode.querySelector( selector );
},
/**
* get value of a Node attribute as an array
*
* @param {DOM Node} node
* @param {String} attributeName
* @return {Array}
*/
getAttributeList: function(node, attributeName) {
var out = [],
attList;
attList = node.getAttribute(attributeName);
if(attList && attList !== '') {
if(attList.indexOf(' ') > -1) {
out = attList.split(' ');
} else {
out.push(attList);
}
}
return out;
},
/**
* gets all child nodes with a given attribute
*
* @param {DOM Node} node
* @param {String} attributeName
* @return {NodeList}
*/
getNodesByAttribute: function(node, attributeName) {
var selector = '[' + attributeName + ']';
return node.querySelectorAll(selector);
},
/**
* gets all child nodes with a given attribute containing a given value
*
* @param {DOM Node} node
* @param {String} attributeName
* @return {DOM NodeList}
*/
getNodesByAttributeValue: function(rootNode, name, value) {
var arr = [],
x = 0,
i,
out = [];
arr = this.getNodesByAttribute(rootNode, name);
if(arr) {
i = arr.length;
while(x < i) {
if(this.hasAttributeValue(arr[x], name, value)) {
out.push(arr[x]);
}
x++;
}
}
return out;
},
/**
* gets attribute value from controlled list of tags
*
* @param {Array} tagNames
* @param {String} attributeName
* @return {String || null}
*/
getAttrValFromTagList: function(node, tagNames, attributeName) {
var i = tagNames.length;
while(i--) {
if(node.tagName.toLowerCase() === tagNames[i]) {
var attrValue = this.getAttribute(node, attributeName);
if(attrValue && attrValue !== '') {
return attrValue;
}
}
}
return null;
},
/**
* get node if it has no siblings. CSS equivalent is :only-child
*
* @param {DOM Node} rootNode
* @param {Array} tagNames
* @return {DOM Node || null}
*/
getSingleDescendant: function(node){
return this.getDescendant( node, null, false );
},
/**
* get node if it has no siblings of the same type. CSS equivalent is :only-of-type
*
* @param {DOM Node} rootNode
* @param {Array} tagNames
* @return {DOM Node || null}
*/
getSingleDescendantOfType: function(node, tagNames){
return this.getDescendant( node, tagNames, true );
},
/**
* get child node limited by presence of siblings - either CSS :only-of-type or :only-child
*
* @param {DOM Node} rootNode
* @param {Array} tagNames
* @return {DOM Node || null}
*/
getDescendant: function( node, tagNames, onlyOfType ){
var i = node.children.length,
countAll = 0,
countOfType = 0,
child,
out = null;
while(i--) {
child = node.children[i];
if(child.nodeType === 1) {
if(tagNames){
// count just only-of-type
if(this.hasTagName(child, tagNames)){
out = child;
countOfType++;
}
}else{
// count all elements
out = child;
countAll++;
}
}
}
if(onlyOfType === true){
return (countOfType === 1)? out : null;
}else{
return (countAll === 1)? out : null;
}
},
/**
* is a node one of a list of tags
*
* @param {DOM Node} rootNode
* @param {Array} tagNames
* @return {Boolean}
*/
hasTagName: function(node, tagNames){
var i = tagNames.length;
while(i--) {
if(node.tagName.toLowerCase() === tagNames[i]) {
return true;
}
}
return false;
},
/**
* abstracts DOM appendChild
*
* @param {DOM Node} node
* @param {DOM Node} childNode
* @return {DOM Node}
*/
appendChild: function(node, childNode){
return node.appendChild(childNode);
},
/**
* abstracts DOM removeChild
*
* @param {DOM Node} childNode
* @return {DOM Node || null}
*/
removeChild: function(childNode){
if (childNode.parentNode) {
return childNode.parentNode.removeChild(childNode);
}else{
return null;
}
},
/**
* abstracts DOM cloneNode
*
* @param {DOM Node} node
* @return {DOM Node}
*/
clone: function(node) {
var newNode = node.cloneNode(true);
newNode.removeAttribute('id');
return newNode;
},
/**
* gets the text of a node
*
* @param {DOM Node} node
* @return {String}
*/
getElementText: function( node ){
if(node && node.data){
return node.data;
}else{
return '';
}
},
/**
* gets the attributes of a node - ordered by sequence in html
*
* @param {DOM Node} node
* @return {Array}
*/
getOrderedAttributes: function( node ){
var nodeStr = node.outerHTML,
attrs = [];
for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
attr.indexNum = nodeStr.indexOf(attr.name);
attrs.push( attr );
}
return attrs.sort( modules.utils.sortObjects( 'indexNum' ) );
},
/**
* decodes html entities in given text
*
* @param {DOM Document} doc
* @param String} text
* @return {String}
*/
decodeEntities: function( doc, text ){
//return text;
return doc.createTextNode( text ).nodeValue;
},
/**
* clones a DOM document
*
* @param {DOM Document} document
* @return {DOM Document}
*/
cloneDocument: function( document ){
var newNode,
newDocument = null;
if( this.canCloneDocument( document )){
newDocument = document.implementation.createHTMLDocument('');
newNode = newDocument.importNode( document.documentElement, true );
newDocument.replaceChild(newNode, newDocument.querySelector('html'));
}
return (newNode && newNode.nodeType && newNode.nodeType === 1)? newDocument : document;
},
/**
* can environment clone a DOM document
*
* @param {DOM Document} document
* @return {Boolean}
*/
canCloneDocument: function( document ){
return (document && document.importNode && document.implementation && document.implementation.createHTMLDocument);
},
/**
* get the child index of a node. Used to create a node path
*
* @param {DOM Node} node
* @return {Int}
*/
getChildIndex: function (node) {
var parent = node.parentNode,
i = -1,
child;
while (parent && (child = parent.childNodes[++i])){
if (child === node){
return i;
}
}
return -1;
},
/**
* get a node's path
*
* @param {DOM Node} node
* @return {Array}
*/
getNodePath: function (node) {
var parent = node.parentNode,
path = [],
index = this.getChildIndex(node);
if(parent && (path = this.getNodePath(parent))){
if(index > -1){
path.push(index);
}
}
return path;
},
/**
* get a node from a path.
*
* @param {DOM document} document
* @param {Array} path
* @return {DOM Node}
*/
getNodeByPath: function (document, path) {
var node = document.documentElement,
i = 0,
index;
while ((index = path[++i]) > -1){
node = node.childNodes[index];
}
return node;
},
/**
* get an array/nodeList of child nodes
*
* @param {DOM node} node
* @return {Array}
*/
getChildren: function( node ){
return node.children;
},
/**
* create a node
*
* @param {String} tagName
* @return {DOM node}
*/
createNode: function( tagName ){
return this.document.createElement(tagName);
},
/**
* create a node with text content
*
* @param {String} tagName
* @param {String} text
* @return {DOM node}
*/
createNodeWithText: function( tagName, text ){
var node = this.document.createElement(tagName);
node.innerHTML = text;
return node;
}
};
return modules;
} (Modules || {}));
@@ -0,0 +1,107 @@
/*
html
Extracts a HTML string from DOM nodes. Was created to get around the issue of not being able to exclude the content
of nodes with the 'data-include' attribute. DO NOT replace with functions such as innerHTML as it will break a
number of microformat include patterns.
Copyright (C) 2010 - 2015 Glenn Jones. All Rights Reserved.
MIT License: https://raw.github.com/glennjones/microformat-node/master/license.txt
Dependencies utilities.js, domutils.js
*/
var Modules = (function (modules) {
modules.html = {
// elements which are self-closing
selfClosingElt: ['area', 'base', 'br', 'col', 'hr', 'img', 'input', 'link', 'meta', 'param', 'command', 'keygen', 'source'],
/**
* parse the html string from DOM Node
*
* @param {DOM Node} node
* @return {String}
*/
parse: function( node ){
var out = '',
j = 0;
// we do not want the outer container
if(node.childNodes && node.childNodes.length > 0){
for (j = 0; j < node.childNodes.length; j++) {
var text = this.walkTreeForHtml( node.childNodes[j] );
if(text !== undefined){
out += text;
}
}
}
return out;
},
/**
* walks the DOM tree parsing the html string from the nodes
*
* @param {DOM Document} doc
* @param {DOM Node} node
* @return {String}
*/
walkTreeForHtml: function( node ) {
var out = '',
j = 0;
// if node is a text node get its text
if(node.nodeType && node.nodeType === 3){
out += modules.domUtils.getElementText( node );
}
// exclude text which has been added with include pattern -
if(node.nodeType && node.nodeType === 1 && modules.domUtils.hasAttribute(node, 'data-include') === false){
// begin tag
out += '<' + node.tagName.toLowerCase();
// add attributes
var attrs = modules.domUtils.getOrderedAttributes(node);
for (j = 0; j < attrs.length; j++) {
out += ' ' + attrs[j].name + '=' + '"' + attrs[j].value + '"';
}
if(this.selfClosingElt.indexOf(node.tagName.toLowerCase()) === -1){
out += '>';
}
// get the text of the child nodes
if(node.childNodes && node.childNodes.length > 0){
for (j = 0; j < node.childNodes.length; j++) {
var text = this.walkTreeForHtml( node.childNodes[j] );
if(text !== undefined){
out += text;
}
}
}
// end tag
if(this.selfClosingElt.indexOf(node.tagName.toLowerCase()) > -1){
out += ' />';
}else{
out += '</' + node.tagName.toLowerCase() + '>';
}
}
return (out === '')? undefined : out;
}
};
return modules;
} (Modules || {}));

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