mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 05:11:03 +00:00
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:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -294,6 +294,7 @@ public:
|
||||
MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
|
||||
NavigatorUserMediaErrorCallback& aOnError,
|
||||
uint64_t aInnerWindowID,
|
||||
const nsAString& aCallID,
|
||||
ErrorResult& aRv);
|
||||
#endif // MOZ_MEDIA_NAVIGATOR
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1862,7 +1862,7 @@ nsNPAPIPluginInstance::WindowVolumeChanged(float aVolume, bool aMuted)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNPAPIPluginInstance::WindowAudioCaptureChanged()
|
||||
nsNPAPIPluginInstance::WindowAudioCaptureChanged(bool aCapture)
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
////
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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) \
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user