diff --git a/accessible/tests/mochitest/jsat/test_gesture_tracker.html b/accessible/tests/mochitest/jsat/test_gesture_tracker.html index 153928d8a691..5a98ef2db400 100644 --- a/accessible/tests/mochitest/jsat/test_gesture_tracker.html +++ b/accessible/tests/mochitest/jsat/test_gesture_tracker.html @@ -31,6 +31,7 @@ gestures.forEach(AccessFuTest.addSequence); AccessFuTest.addFunc(stopGestureTracker); AccessFuTest.waitForExplicitFinish(); + Logger.logLevel = Logger.DEBUG; AccessFuTest.runTests(); }); } diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 2bbc5bf09e38..9ee2a62a2aba 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -156,11 +156,6 @@ pref("browser.search.suggest.enabled", true); // tell the search service that we don't really expose the "current engine" pref("browser.search.noCurrentEngine", true); -// Enable sparse localization by setting a few package locale overrides -pref("chrome.override_package.global", "b2g-l10n"); -pref("chrome.override_package.mozapps", "b2g-l10n"); -pref("chrome.override_package.passwordmgr", "b2g-l10n"); - // enable xul error pages pref("browser.xul.error_pages.enabled", true); @@ -899,9 +894,9 @@ pref("osfile.reset_worker_delay", 5000); pref("apz.asyncscroll.throttle", 40); pref("apz.pan_repaint_interval", 16); -// Maximum fling velocity in inches/ms. Slower devices may need to reduce this -// to avoid checkerboarding. Note, float value must be set as a string. -pref("apz.max_velocity_inches_per_ms", "0.0375"); +// APZ physics settings, tuned by UX designers +pref("apz.max_velocity_inches_per_ms", "0.07"); +pref("apz.fling_friction", "0.003"); // Tweak default displayport values to reduce the risk of running out of // memory when zooming in diff --git a/b2g/locales/en-US/b2g-l10n.js b/b2g/locales/en-US/b2g-l10n.js index 642ad6534a97..d502f0ae54ea 100644 --- a/b2g/locales/en-US/b2g-l10n.js +++ b/b2g/locales/en-US/b2g-l10n.js @@ -5,3 +5,8 @@ #filter substitution pref("general.useragent.locale", "@AB_CD@"); + +// Enable sparse localization by setting a few package locale overrides +pref("chrome.override_package.global", "b2g-l10n"); +pref("chrome.override_package.mozapps", "b2g-l10n"); +pref("chrome.override_package.passwordmgr", "b2g-l10n"); diff --git a/build/pgo/certs/cert8.db b/build/pgo/certs/cert8.db index 38c3dfe1146e..2fad36b23bb7 100644 Binary files a/build/pgo/certs/cert8.db and b/build/pgo/certs/cert8.db differ diff --git a/build/pgo/certs/key3.db b/build/pgo/certs/key3.db index a99595c52a9f..aa24aa986882 100644 Binary files a/build/pgo/certs/key3.db and b/build/pgo/certs/key3.db differ diff --git a/build/pgo/server-locations.txt b/build/pgo/server-locations.txt index e3825633c0b7..fd14ff2774ad 100644 --- a/build/pgo/server-locations.txt +++ b/build/pgo/server-locations.txt @@ -212,3 +212,5 @@ http://example.cn:80 privileged http://example.co.jp:80 privileged http://example.fi:80 privileged +# Hosts for testing marketplace apps installations +https://marketplace.firefox.com:443 privileged diff --git a/configure.in b/configure.in index ccbdbdaa573d..5879525b49da 100644 --- a/configure.in +++ b/configure.in @@ -5964,18 +5964,6 @@ if test "$MOZ_GAMEPAD"; then MOZ_GAMEPAD_BACKEND=cocoa ;; WINNT) - if test -z "$MOZ_HAS_WINSDK_WITH_D3D"; then - if test -n "$MOZ_DIRECTX_SDK_PATH" ; then - if ! test -f "$MOZ_DIRECTX_SDK_PATH"/lib/$MOZ_DIRECTX_SDK_CPU_SUFFIX/dxguid.lib ; then - MOZ_GAMEPAD= - fi - elif test "$GCC" != "yes"; then - MOZ_GAMEPAD= - fi - fi - if test -z "$MOZ_GAMEPAD"; then - AC_MSG_ERROR([Couldn't find the DirectX SDK, needed for gamepad support. Please install it or, reconfigure with --disable-gamepad to disable gamepad support.]) - fi MOZ_GAMEPAD_BACKEND=windows ;; Linux) diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 1472a441f8e2..b398301e471f 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -3209,7 +3209,7 @@ nsObjectLoadingContent::LegacyCall(JSContext* aCx, } for (size_t i = 0; i < args.length(); i++) { - if (!JS_WrapValue(aCx, args.handleAt(i))) { + if (!JS_WrapValue(aCx, args[i])) { aRv.Throw(NS_ERROR_UNEXPECTED); return JS::UndefinedValue(); } diff --git a/content/media/BufferDecoder.h b/content/media/BufferDecoder.h index 975e55a5b2cd..7787bf450b09 100644 --- a/content/media/BufferDecoder.h +++ b/content/media/BufferDecoder.h @@ -32,7 +32,7 @@ public: virtual ReentrantMonitor& GetReentrantMonitor() MOZ_OVERRIDE; - virtual bool IsShutdown() const MOZ_FINAL MOZ_OVERRIDE; + virtual bool IsShutdown() const MOZ_OVERRIDE; virtual bool OnStateMachineThread() const MOZ_OVERRIDE; @@ -40,11 +40,11 @@ public: virtual MediaResource* GetResource() const MOZ_OVERRIDE; - virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE; + virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_OVERRIDE; - virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE; + virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE; - virtual int64_t GetEndMediaTime() const MOZ_FINAL MOZ_OVERRIDE; + virtual int64_t GetEndMediaTime() const MOZ_OVERRIDE; virtual int64_t GetMediaDuration() MOZ_OVERRIDE; @@ -56,25 +56,25 @@ public: virtual void SetTransportSeekable(bool aTransportSeekable) MOZ_OVERRIDE; - virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE; + virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_OVERRIDE; virtual layers::ImageContainer* GetImageContainer() MOZ_OVERRIDE; - virtual bool IsTransportSeekable() MOZ_FINAL MOZ_OVERRIDE; + virtual bool IsTransportSeekable() MOZ_OVERRIDE; - virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE; + virtual bool IsMediaSeekable() MOZ_OVERRIDE; - virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE; - virtual void QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_FINAL MOZ_OVERRIDE; + virtual void MetadataLoaded(int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_OVERRIDE; + virtual void QueueMetadata(int64_t aTime, int aChannels, int aRate, bool aHasAudio, bool aHasVideo, MetadataTags* aTags) MOZ_OVERRIDE; - virtual void SetMediaEndTime(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE; + virtual void SetMediaEndTime(int64_t aTime) MOZ_OVERRIDE; - virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_FINAL MOZ_OVERRIDE; + virtual void UpdatePlaybackPosition(int64_t aTime) MOZ_OVERRIDE; - virtual void OnReadMetadataCompleted() MOZ_FINAL MOZ_OVERRIDE; + virtual void OnReadMetadataCompleted() MOZ_OVERRIDE; virtual MediaDecoderOwner* GetOwner() MOZ_OVERRIDE; - virtual void NotifyWaitingForResourcesStatusChanged() MOZ_FINAL MOZ_OVERRIDE; + virtual void NotifyWaitingForResourcesStatusChanged() MOZ_OVERRIDE; protected: // This monitor object is not really used to synchronize access to anything. diff --git a/content/media/encoder/TrackEncoder.cpp b/content/media/encoder/TrackEncoder.cpp index 89cb79c51be8..6e7358a11724 100644 --- a/content/media/encoder/TrackEncoder.cpp +++ b/content/media/encoder/TrackEncoder.cpp @@ -164,6 +164,14 @@ VideoTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, if (!chunk.IsNull()) { gfx::IntSize imgsize = chunk.mFrame.GetImage()->GetSize(); gfxIntSize intrinsicSize = chunk.mFrame.GetIntrinsicSize(); +#ifdef MOZ_WIDGET_GONK + // Block the video frames come from video source. + if (chunk.mFrame.GetImage()->GetFormat() != ImageFormat::PLANAR_YCBCR) { + LOG("Can't encode this ImageFormat %x", chunk.mFrame.GetImage()->GetFormat()); + NotifyCancel(); + break; + } +#endif nsresult rv = Init(imgsize.width, imgsize.height, intrinsicSize.width, intrinsicSize.height, aTrackRate); diff --git a/content/media/mediasource/MediaSourceDecoder.cpp b/content/media/mediasource/MediaSourceDecoder.cpp index a267d4f9c5c4..fc4461599f95 100644 --- a/content/media/mediasource/MediaSourceDecoder.cpp +++ b/content/media/mediasource/MediaSourceDecoder.cpp @@ -89,7 +89,9 @@ public: return false; } - MaybeSwitchVideoReaders(aTimeThreshold); + if (MaybeSwitchVideoReaders(aTimeThreshold)) { + GetVideoReader()->DecodeToTarget(aTimeThreshold); + } bool rv = GetVideoReader()->DecodeVideoFrame(aKeyFrameSkip, aTimeThreshold); @@ -129,7 +131,7 @@ public: void CallDecoderInitialization(); private: - void MaybeSwitchVideoReaders(int64_t aTimeThreshold) { + bool MaybeSwitchVideoReaders(int64_t aTimeThreshold) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); MOZ_ASSERT(mActiveVideoDecoder != -1); @@ -146,10 +148,11 @@ private: MSE_DEBUG("%p MSR::DecodeVF switching to %d", this, mActiveVideoDecoder); GetVideoReader()->SetActive(); - GetVideoReader()->DecodeToTarget(aTimeThreshold); - break; + return true; } } + + return false; } MediaDecoderReader* GetAudioReader() { diff --git a/content/media/mediasource/SourceBuffer.cpp b/content/media/mediasource/SourceBuffer.cpp index 8631706622f5..b1f9a69f8458 100644 --- a/content/media/mediasource/SourceBuffer.cpp +++ b/content/media/mediasource/SourceBuffer.cpp @@ -66,6 +66,12 @@ SubBufferDecoder::GetResource() const return static_cast(mResource.get()); } +void +SubBufferDecoder::NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) +{ + return mParentDecoder->NotifyDecodedFrames(aParsed, aDecoded); +} + void SubBufferDecoder::SetMediaDuration(int64_t aDuration) { @@ -192,12 +198,8 @@ SourceBuffer::GetBuffered(ErrorResult& aRv) return nullptr; } nsRefPtr ranges = new TimeRanges(); - for (uint32_t i = 0; i < mDecoders.Length(); ++i) { - nsRefPtr r = new TimeRanges(); - mDecoders[i]->GetBuffered(r); - if (r->Length() > 0) { - ranges->Add(r->GetStartTime(), r->GetEndTime()); - } + if (mDecoder) { + mDecoder->GetBuffered(ranges); } ranges->Normalize(); return ranges.forget(); @@ -264,10 +266,10 @@ SourceBuffer::Abort(ErrorResult& aRv) mAppendWindowStart = 0; mAppendWindowEnd = PositiveInfinity(); - MSE_DEBUG("%p Abort: Discarding decoders.", this); - if (mCurrentDecoder) { - mCurrentDecoder->GetResource()->Ended(); - mCurrentDecoder = nullptr; + MSE_DEBUG("%p Abort: Discarding decoder.", this); + if (mDecoder) { + mDecoder->GetResource()->Ended(); + mDecoder = nullptr; } } @@ -294,16 +296,15 @@ void SourceBuffer::Detach() { Ended(); - mDecoders.Clear(); - mCurrentDecoder = nullptr; + mDecoder = nullptr; mMediaSource = nullptr; } void SourceBuffer::Ended() { - for (uint32_t i = 0; i < mDecoders.Length(); ++i) { - mDecoders[i]->GetResource()->Ended(); + if (mDecoder) { + mDecoder->GetResource()->Ended(); } } @@ -316,6 +317,7 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType) , mTimestampOffset(0) , mAppendMode(SourceBufferAppendMode::Segments) , mUpdating(false) + , mDecoderInit(false) { MOZ_ASSERT(aMediaSource); if (mType.EqualsIgnoreCase("video/webm") || mType.EqualsIgnoreCase("audio/webm")) { @@ -324,6 +326,8 @@ SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType) // XXX: Plug in parsers for MPEG4, etc. here. mParser = new ContainerParser(); } + MSE_DEBUG("%p SourceBuffer: Creating initial decoder.", this); + InitNewDecoder(); } already_AddRefed @@ -335,8 +339,8 @@ SourceBuffer::Create(MediaSource* aMediaSource, const nsACString& aType) SourceBuffer::~SourceBuffer() { - for (uint32_t i = 0; i < mDecoders.Length(); ++i) { - mDecoders[i]->GetResource()->Ended(); + if (mDecoder) { + mDecoder->GetResource()->Ended(); } } @@ -375,11 +379,7 @@ SourceBuffer::InitNewDecoder() if (!decoder) { return false; } - mDecoders.AppendElement(decoder); - // XXX: At this point, we really want to push through any remaining - // processing for the old decoder and discard it, rather than hanging on - // to all of them in mDecoders. - mCurrentDecoder = decoder; + mDecoder = decoder; return true; } @@ -424,21 +424,24 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR // TODO: Test buffer full flag. StartUpdating(); // TODO: Run buffer append algorithm asynchronously (would call StopUpdating()). - if (mParser->IsInitSegmentPresent(aData, aLength) || !mCurrentDecoder) { - MSE_DEBUG("%p AppendBuffer: New initialization segment, switching decoders.", this); - if (mCurrentDecoder) { - mCurrentDecoder->GetResource()->Ended(); - } - if (!InitNewDecoder()) { - aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling. - return; + if (!mDecoder || mParser->IsInitSegmentPresent(aData, aLength)) { + if (!mDecoder || mDecoderInit) { + MSE_DEBUG("%p AppendBuffer: New initialization segment, creating decoder.", this); + mDecoder->GetResource()->Ended(); + + if (!InitNewDecoder()) { + aRv.Throw(NS_ERROR_FAILURE); // XXX: Review error handling. + return; + } } + MSE_DEBUG("%p AppendBuffer: Decoder marked as initialized.", this); + mDecoderInit = true; } // XXX: For future reference: NDA call must run on the main thread. - mCurrentDecoder->NotifyDataArrived(reinterpret_cast(aData), - aLength, - mCurrentDecoder->GetResource()->GetLength()); - mCurrentDecoder->GetResource()->AppendData(aData, aLength); + mDecoder->NotifyDataArrived(reinterpret_cast(aData), + aLength, + mDecoder->GetResource()->GetLength()); + mDecoder->GetResource()->AppendData(aData, aLength); // Eviction uses a byte threshold. If the buffer is greater than the // number of bytes then data is evicted. The time range for this @@ -446,7 +449,7 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR // evict data before that range across all SourceBuffer's it knows // about. const int evict_threshold = 1000000; - bool evicted = mCurrentDecoder->GetResource()->EvictData(evict_threshold); + bool evicted = mDecoder->GetResource()->EvictData(evict_threshold); if (evicted) { double start = 0.0; double end = 0.0; @@ -479,14 +482,15 @@ SourceBuffer::GetBufferedStartEndTime(double* aStart, double* aEnd) void SourceBuffer::Evict(double aStart, double aEnd) { - for (uint32_t i = 0; i < mDecoders.Length(); ++i) { - // Need to map time to byte offset then evict - int64_t end = mDecoders[i]->ConvertToByteOffset(aEnd); - if (end <= 0) { - NS_WARNING("SourceBuffer::Evict failed"); - continue; - } - mDecoders[i]->GetResource()->EvictBefore(end); + if (!mDecoder) { + return; + } + // Need to map time to byte offset then evict + int64_t end = mDecoder->ConvertToByteOffset(aEnd); + if (end > 0) { + mDecoder->GetResource()->EvictBefore(end); + } else { + NS_WARNING("SourceBuffer::Evict failed"); } } diff --git a/content/media/mediasource/SourceBuffer.h b/content/media/mediasource/SourceBuffer.h index 1c10312ec1a4..c154e76ce383 100644 --- a/content/media/mediasource/SourceBuffer.h +++ b/content/media/mediasource/SourceBuffer.h @@ -140,10 +140,7 @@ private: nsAutoPtr mParser; - // XXX: We only want to keep the current decoder alive, but need a way to - // query @buffered for everything this SourceBuffer is responsible for. - nsTArray> mDecoders; - nsRefPtr mCurrentDecoder; + nsRefPtr mDecoder; double mAppendWindowStart; double mAppendWindowEnd; @@ -152,6 +149,8 @@ private: SourceBufferAppendMode mAppendMode; bool mUpdating; + + bool mDecoderInit; }; } // namespace dom diff --git a/content/media/mediasource/SubBufferDecoder.h b/content/media/mediasource/SubBufferDecoder.h index 8ddea5ca76f1..62aa79ee2d11 100644 --- a/content/media/mediasource/SubBufferDecoder.h +++ b/content/media/mediasource/SubBufferDecoder.h @@ -40,6 +40,7 @@ public: virtual bool OnStateMachineThread() const MOZ_OVERRIDE; virtual bool OnDecodeThread() const MOZ_OVERRIDE; virtual SourceBufferResource* GetResource() const MOZ_OVERRIDE; + virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_OVERRIDE; virtual void SetMediaDuration(int64_t aDuration) MOZ_OVERRIDE; virtual void UpdateEstimatedMediaDuration(int64_t aDuration) MOZ_OVERRIDE; virtual void SetMediaSeekable(bool aMediaSeekable) MOZ_OVERRIDE; diff --git a/content/media/webrtc/MediaEngineWebRTCVideo.cpp b/content/media/webrtc/MediaEngineWebRTCVideo.cpp index bd935fe745f2..b95f505a3526 100644 --- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp +++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp @@ -678,6 +678,7 @@ MediaEngineWebRTCVideoSource::Shutdown() void MediaEngineWebRTCVideoSource::AllocImpl() { MOZ_ASSERT(NS_IsMainThread()); + ReentrantMonitorAutoEnter sync(mCallbackMonitor); mCameraControl = ICameraControl::Create(mCaptureIndex); if (mCameraControl) { @@ -818,6 +819,7 @@ MediaEngineWebRTCVideoSource::OnError(CameraErrorContext aContext, CameraError a void MediaEngineWebRTCVideoSource::OnTakePictureComplete(uint8_t* aData, uint32_t aLength, const nsAString& aMimeType) { + ReentrantMonitorAutoEnter sync(mCallbackMonitor); mLastCapture = static_cast(new nsDOMMemoryFile(static_cast(aData), static_cast(aLength), diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index a56dd47bf73f..dfa0065d335f 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -2244,8 +2244,7 @@ this.DOMApplicationRegistry = { queuedDownload: {}, queuedPackageDownload: {}, -onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, - aDontNeedNetwork) { + onInstallSuccessAck: function(aManifestURL, aDontNeedNetwork) { // If we are offline, register to run when we'll be online. if ((Services.io.offline) && !aDontNeedNetwork) { let onlineWrapper = { @@ -2345,7 +2344,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, let dir = this._getAppDir(aId).path; let manFile = OS.Path.join(dir, manifestName); - this._writeFile(manFile, JSON.stringify(aJsonManifest)); + return this._writeFile(manFile, JSON.stringify(aJsonManifest)); }, // Add an app that is already installed to the registry. @@ -2418,7 +2417,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, }); }), - confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) { + confirmInstall: Task.async(function*(aData, aProfileDir, aInstallSuccessCallback) { debug("confirmInstall"); let origin = Services.io.newURI(aData.app.origin, null, null); @@ -2443,7 +2442,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, let app = this._setupApp(aData, id); let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest; - this._writeManifestFile(id, aData.isPackage, jsonManifest); + yield this._writeManifestFile(id, aData.isPackage, jsonManifest); debug("app.origin: " + app.origin); let manifest = new ManifestHelper(jsonManifest, app.origin); @@ -2477,42 +2476,15 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, aData.app[prop] = appObject[prop]; } + let dontNeedNetwork = false; + if (manifest.appcache_path) { this.queuedDownload[app.manifestURL] = { manifest: manifest, app: appObject, profileDir: aProfileDir } - } - - // We notify about the successful installation via mgmt.oninstall and the - // corresponging DOMRequest.onsuccess event as soon as the app is properly - // saved in the registry. - this._saveApps().then(() => { - this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject }); - if (aData.isPackage && aData.autoInstall) { - // Skip directly to onInstallSuccessAck, since there isn't - // a WebappsRegistry to receive Webapps:Install:Return:OK and respond - // Webapps:Install:Return:Ack when an app is being auto-installed. - this.onInstallSuccessAck(app.manifestURL); - } else { - // Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify - // the installing page about the successful install, after which it'll - // respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck. - this.broadcastMessage("Webapps:Install:Return:OK", aData); - } - if (!aData.isPackage) { - this.updateAppHandlers(null, app.manifest, app); - if (aInstallSuccessCallback) { - aInstallSuccessCallback(app.manifest); - } - } - Services.obs.notifyObservers(null, "webapps-installed", - JSON.stringify({ manifestURL: app.manifestURL })); - }); - - let dontNeedNetwork = false; - if (manifest.package_path) { + } else if (manifest.package_path) { // If it is a local app then it must been installed from a local file // instead of web. #ifdef MOZ_ANDROID_SYNTHAPKS @@ -2537,12 +2509,40 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, }; } + // We notify about the successful installation via mgmt.oninstall and the + // corresponding DOMRequest.onsuccess event as soon as the app is properly + // saved in the registry. + yield this._saveApps(); + + this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject }); + if (aData.isPackage && aData.autoInstall) { + // Skip directly to onInstallSuccessAck, since there isn't + // a WebappsRegistry to receive Webapps:Install:Return:OK and respond + // Webapps:Install:Return:Ack when an app is being auto-installed. + this.onInstallSuccessAck(app.manifestURL); + } else { + // Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify + // the installing page about the successful install, after which it'll + // respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck. + this.broadcastMessage("Webapps:Install:Return:OK", aData); + } + + if (!aData.isPackage) { + this.updateAppHandlers(null, app.manifest, app); + if (aInstallSuccessCallback) { + aInstallSuccessCallback(app.manifest); + } + } + + Services.obs.notifyObservers(null, "webapps-installed", + JSON.stringify({ manifestURL: app.manifestURL })); + if (aData.forceSuccessAck) { // If it's a local install, there's no content process so just // ack the install. this.onInstallSuccessAck(app.manifestURL, dontNeedNetwork); } - }, + }), /** * Install the package after successfully downloading it @@ -3104,7 +3104,10 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, throw "CERTDB_ERROR"; } - let [result, zipReader] = yield this._openSignedPackage(aZipFile, certDb); + let [result, zipReader] = yield this._openSignedPackage(aApp.installOrigin, + aApp.manifestURL, + aZipFile, + certDb); // We cannot really know if the system date is correct or // not. What we can know is if it's after the build date or not, @@ -3147,11 +3150,39 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, }).bind(this)); }, - _openSignedPackage: function(aZipFile, aCertDb) { + _openSignedPackage: function(aInstallOrigin, aManifestURL, aZipFile, aCertDb) { let deferred = Promise.defer(); + let root = TrustedRootCertificate.index; + + let useReviewerCerts = false; + try { + useReviewerCerts = Services.prefs. + getBoolPref("dom.mozApps.use_reviewer_certs"); + } catch (ex) { } + + // We'll use the reviewer and dev certificates only if the pref is set to + // true. + if (useReviewerCerts) { + let manifestPath = Services.io.newURI(aManifestURL, null, null).path; + + switch (aInstallOrigin) { + case "https://marketplace.firefox.com": + root = manifestPath.startsWith("/reviewers/") + ? Ci.nsIX509CertDB.AppMarketplaceProdReviewersRoot + : Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot; + break; + + case "https://marketplace-dev.allizom.org": + root = manifestPath.startsWith("/reviewers/") + ? Ci.nsIX509CertDB.AppMarketplaceDevReviewersRoot + : Ci.nsIX509CertDB.AppMarketplaceDevPublicRoot; + break; + } + } + aCertDb.openSignedAppFileAsync( - TrustedRootCertificate.index, aZipFile, + root, aZipFile, function(aRv, aZipReader) { deferred.resolve([aRv, aZipReader]); } diff --git a/dom/apps/tests/marketplace/marketplace_app.webapp b/dom/apps/tests/marketplace/marketplace_app.webapp new file mode 100755 index 000000000000..7460c2b3c71f --- /dev/null +++ b/dom/apps/tests/marketplace/marketplace_app.webapp @@ -0,0 +1,28 @@ +{ + "version" : "2.0", + "name" : "Flashlight (Linterna)", + "description" : "Simple Flashlight that you can use everywhere without internet connection and also with cool modes: * Flashlight mode - * Disco mode - * Colors mode", + "launch_path" : "/index.html", + "icons": { + "16": "/img/icons/mortar-16.png", + "48": "/img/icons/mortar-48.png", + "60": "/img/icons/mortar-60.png", + "128": "/img/icons/mortar-128.png" + }, + "developer": { + "name": "William Vargas", + "url" : "https://twitter.com/tecnowilliam" + }, + "installs_allowed_from": ["*"], + "locales": { + "es": { + "description": "Una simple linterna que puedes utilizar en cualquier lugar sin conexión a internet y con opciones geniales: * Modo Linterna - * Modo Disco - * Modo Colores", + "developer": { + "name": "William Vargas", + "url" : "https://twitter.com/tecnowilliam" + } + } + }, + "default_locale": "en", + "package_path": "marketplace_app.zip" +} diff --git a/dom/apps/tests/marketplace/marketplace_app.webapp^headers^ b/dom/apps/tests/marketplace/marketplace_app.webapp^headers^ new file mode 100644 index 000000000000..a2367b11c78b --- /dev/null +++ b/dom/apps/tests/marketplace/marketplace_app.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/x-web-app-manifest+json diff --git a/dom/apps/tests/marketplace/marketplace_app.zip b/dom/apps/tests/marketplace/marketplace_app.zip new file mode 100644 index 000000000000..0a412a2e0925 Binary files /dev/null and b/dom/apps/tests/marketplace/marketplace_app.zip differ diff --git a/dom/apps/tests/marketplace/marketplace_privileged_app.webapp b/dom/apps/tests/marketplace/marketplace_privileged_app.webapp new file mode 100644 index 000000000000..1e2664c7f6e5 --- /dev/null +++ b/dom/apps/tests/marketplace/marketplace_privileged_app.webapp @@ -0,0 +1,50 @@ +{ + "version": "0.2.2", + "name": "KitchenSink", + "description": "Tests and report APIs available on the device", + "launch_path": "/index.html", + "developer": { + "name": "Piotr Zalewa", + "url": "http://www.mozillalabs.com" + }, + "icons": { + "16": "/img/icons/logo-16.png", + "32": "/img/icons/logo-32.png", + "64": "/img/icons/logo-64.png", + "128": "/img/icons/logo-128.png", + "256": "/img/icons/logo-256.png" + }, + "type": "privileged", + "permissions": { + "alarms": { + "description": "Testing" + }, + "browser": { + "description": "Testing" + }, + "geolocation": { + "description": "Testing" + }, + "contacts": { + "access": "readwrite", + "description": "Testing" + }, + "device-storage:sdcard": { + "access": "readwrite", + "description": "Testing" + }, + "fmradio": { + "description": "Testing" + }, + "storage": { + "description": "Testing" + }, + "systemXHR": { + "description": "Testing" + }, + "tcp-socket": { + "description": "Testing" + } + }, + "package_path": "marketplace_privileged_app.zip" +} diff --git a/dom/apps/tests/marketplace/marketplace_privileged_app.webapp^headers^ b/dom/apps/tests/marketplace/marketplace_privileged_app.webapp^headers^ new file mode 100644 index 000000000000..a2367b11c78b --- /dev/null +++ b/dom/apps/tests/marketplace/marketplace_privileged_app.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/x-web-app-manifest+json diff --git a/dom/apps/tests/marketplace/marketplace_privileged_app.zip b/dom/apps/tests/marketplace/marketplace_privileged_app.zip new file mode 100644 index 000000000000..d56532ec9898 Binary files /dev/null and b/dom/apps/tests/marketplace/marketplace_privileged_app.zip differ diff --git a/dom/apps/tests/marketplace/marketplace_reviewers_app.webapp b/dom/apps/tests/marketplace/marketplace_reviewers_app.webapp new file mode 100644 index 000000000000..55a07151caf6 --- /dev/null +++ b/dom/apps/tests/marketplace/marketplace_reviewers_app.webapp @@ -0,0 +1,20 @@ +{ + "name": "Stopwatch", + "description": "Simple stopwatch", + "launch_path": "/index.html", + "icons": { + "128": "/static/img/icon.png" + }, + "developer": { + "name": "Andy McKay", + "url": "http://www.agmweb.ca/blog/andy/" + }, + "locales": { + "fr": { + "description": "Simple chronomètre" + } + }, + "installs_allowed_from": ["*"], + "default_locale": "en", + "package_path": "marketplace_reviewers_app.zip" +} diff --git a/dom/apps/tests/marketplace/marketplace_reviewers_app.webapp^headers^ b/dom/apps/tests/marketplace/marketplace_reviewers_app.webapp^headers^ new file mode 100644 index 000000000000..a2367b11c78b --- /dev/null +++ b/dom/apps/tests/marketplace/marketplace_reviewers_app.webapp^headers^ @@ -0,0 +1 @@ +Content-Type: application/x-web-app-manifest+json diff --git a/dom/apps/tests/marketplace/marketplace_reviewers_app.zip b/dom/apps/tests/marketplace/marketplace_reviewers_app.zip new file mode 100644 index 000000000000..9d48dbae7d58 Binary files /dev/null and b/dom/apps/tests/marketplace/marketplace_reviewers_app.zip differ diff --git a/dom/apps/tests/mochitest.ini b/dom/apps/tests/mochitest.ini index 26f845b860e7..47baedde67f7 100644 --- a/dom/apps/tests/mochitest.ini +++ b/dom/apps/tests/mochitest.ini @@ -14,10 +14,14 @@ support-files = signed_app_template.webapp signed/* test_packaged_app_common.js + marketplace/* + pkg_install_iframe.html [test_app_update.html] [test_bug_795164.html] [test_install_receipts.html] +[test_marketplace_pkg_install.html] +skip-if = buildapp == "b2g" || toolkit == "android" # see bug 989806 [test_packaged_app_install.html] [test_packaged_app_update.html] [test_receipt_operations.html] diff --git a/dom/apps/tests/pkg_install_iframe.html b/dom/apps/tests/pkg_install_iframe.html new file mode 100644 index 000000000000..6668aafb04cb --- /dev/null +++ b/dom/apps/tests/pkg_install_iframe.html @@ -0,0 +1,26 @@ + + + + + Cross Origin Helper + + + + + diff --git a/dom/apps/tests/signed_app.sjs b/dom/apps/tests/signed_app.sjs index a20cb1ddb1e8..ff85a80e4afc 100644 --- a/dom/apps/tests/signed_app.sjs +++ b/dom/apps/tests/signed_app.sjs @@ -102,7 +102,7 @@ function readFile(path) { function makeResource(templatePath, version, packagePath, packageSize, appName, developerName, developerUrl) { - var res = readFile(templatePath, false). + var res = readFile(templatePath). replace(/VERSIONTOKEN/g, version). replace(/PACKAGEPATHTOKEN/g, packagePath). replace(/PACKAGESIZETOKEN/g, packageSize). diff --git a/dom/apps/tests/test_marketplace_pkg_install.html b/dom/apps/tests/test_marketplace_pkg_install.html new file mode 100644 index 000000000000..80ec1283085e --- /dev/null +++ b/dom/apps/tests/test_marketplace_pkg_install.html @@ -0,0 +1,198 @@ + + + + + + Test for Bug 989806 + + + + + + + +Mozilla Bug 989806 +

+ +
+
+
+
+ + diff --git a/dom/apps/tests/test_packaged_app_common.js b/dom/apps/tests/test_packaged_app_common.js index 27b493dda8cc..9a0bddbff4c3 100644 --- a/dom/apps/tests/test_packaged_app_common.js +++ b/dom/apps/tests/test_packaged_app_common.js @@ -12,31 +12,18 @@ var PackagedTestHelper = (function PackagedTestHelper() { var gAppName = "appname"; var gApp = null; var gInstallOrigin = "http://mochi.test:8888"; - var timeoutID; - - function timeoutError() { - ok(false, "Timeout! Probably waiting on a app installation event"); - info("Finishing this test suite!"); - finish(); - } function debug(aMsg) { //dump("== PackageTestHelper debug == " + aMsg + "\n"); } function next() { - if (timeoutID) { - clearTimeout(timeoutID); - } index += 1; if (index >= steps.length) { ok(false, "Shouldn't get here!"); return; } try { - // There's nothing here that should take more than 30 seconds, even on - // heavy loads. So there's no need to stop further tests for five minutes. - timeoutID = setTimeout(timeoutError, 30000); steps[index](); } catch(ex) { ok(false, "Caught exception", ex); @@ -48,10 +35,8 @@ var PackagedTestHelper = (function PackagedTestHelper() { } function finish() { - if (timeoutID) { - clearTimeout(timeoutID); - } SpecialPowers.removePermission("webapps-manage", document); + SpecialPowers.removePermission("browser", document); SimpleTest.finish(); } @@ -165,7 +150,7 @@ var PackagedTestHelper = (function PackagedTestHelper() { is(aApp.manifest.size, aExpectedApp.size, "Check size"); } if (aApp.manifest) { - is(aApp.manifest.launch_path, gSJSPath, "Check launch path"); + is(aApp.manifest.launch_path, aExpectedApp.launch_path || gSJSPath, "Check launch path"); } if (aExpectedApp.manifestURL) { is(aApp.manifestURL, aExpectedApp.manifestURL, "Check manifestURL"); diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp index 890c76a79047..63dc12cdca7e 100644 --- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -476,10 +476,7 @@ private: arguments.AppendElement(value); } - console->ProfileMethod(cx, mAction, arguments, error); - if (error.Failed()) { - NS_WARNING("Failed to call call profile() method to the ConsoleAPI."); - } + console->ProfileMethod(cx, mAction, arguments); } private: @@ -662,23 +659,20 @@ Console::TimeEnd(JSContext* aCx, const JS::Handle aTime) } void -Console::Profile(JSContext* aCx, const Sequence& aData, - ErrorResult& aRv) +Console::Profile(JSContext* aCx, const Sequence& aData) { - ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData, aRv); + ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData); } void -Console::ProfileEnd(JSContext* aCx, const Sequence& aData, - ErrorResult& aRv) +Console::ProfileEnd(JSContext* aCx, const Sequence& aData) { - ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData, aRv); + ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData); } void Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, - const Sequence& aData, - ErrorResult& aRv) + const Sequence& aData) { if (!NS_IsMainThread()) { // Here we are in a worker thread. @@ -688,6 +682,8 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, return; } + ClearException ce(aCx); + RootedDictionary event(aCx); event.mAction = aAction; @@ -700,15 +696,14 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, JS::Rooted eventValue(aCx); if (!event.ToObject(aCx, &eventValue)) { - aRv.Throw(NS_ERROR_FAILURE); return; } JS::Rooted eventObj(aCx, &eventValue.toObject()); MOZ_ASSERT(eventObj); - if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) { - aRv.Throw(NS_ERROR_FAILURE); + if (!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventValue, + JSPROP_ENUMERATE)) { return; } @@ -717,7 +712,6 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, const nsIID& iid = NS_GET_IID(nsISupports); if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) { - aRv.Throw(NS_ERROR_FAILURE); return; } @@ -836,13 +830,14 @@ Console::Method(JSContext* aCx, MethodName aMethodName, ConsoleCallData* callData = new ConsoleCallData(); mQueuedCalls.insertBack(callData); + ClearException ce(aCx); + callData->Initialize(aCx, aMethodName, aMethodString, aData); RAII raii(mQueuedCalls); if (mWindow) { nsCOMPtr webNav = do_GetInterface(mWindow); if (!webNav) { - Throw(aCx, NS_ERROR_FAILURE); return; } @@ -857,7 +852,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, nsCOMPtr stack = CreateStack(aCx, maxDepth); if (!stack) { - Throw(aCx, NS_ERROR_FAILURE); return; } @@ -866,7 +860,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, uint32_t language; nsresult rv = stack->GetLanguage(&language); if (NS_FAILED(rv)) { - Throw(aCx, rv); return; } @@ -877,7 +870,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, callData->mTopStackFrame.ref(), language); if (NS_FAILED(rv)) { - Throw(aCx, rv); return; } @@ -887,7 +879,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, nsCOMPtr caller; rv = stack->GetCaller(getter_AddRefs(caller)); if (NS_FAILED(rv)) { - Throw(aCx, rv); return; } @@ -902,7 +893,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, callData->mReifiedStack.construct(); nsresult rv = ReifyStack(stack, callData->mReifiedStack.ref()); if (NS_WARN_IF(NS_FAILED(rv))) { - Throw(aCx, rv); return; } } @@ -915,7 +905,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, ErrorResult rv; nsRefPtr performance = win->GetPerformance(rv); if (rv.Failed()) { - Throw(aCx, rv.ErrorCode()); return; } @@ -1111,7 +1100,6 @@ Console::ProcessCallData(ConsoleCallData* aData) JS::Rooted eventValue(cx); if (!event.ToObject(cx, &eventValue)) { - Throw(cx, NS_ERROR_FAILURE); return; } diff --git a/dom/base/Console.h b/dom/base/Console.h index f9f3fb866f4d..67e6e1b56387 100644 --- a/dom/base/Console.h +++ b/dom/base/Console.h @@ -87,12 +87,10 @@ public: TimeEnd(JSContext* aCx, const JS::Handle aTime); void - Profile(JSContext* aCx, const Sequence& aData, - ErrorResult& aRv); + Profile(JSContext* aCx, const Sequence& aData); void - ProfileEnd(JSContext* aCx, const Sequence& aData, - ErrorResult& aRv); + ProfileEnd(JSContext* aCx, const Sequence& aData); void Assert(JSContext* aCx, bool aCondition, const Sequence& aData); @@ -181,8 +179,7 @@ private: void ProfileMethod(JSContext* aCx, const nsAString& aAction, - const Sequence& aData, - ErrorResult& aRv); + const Sequence& aData); JS::Value IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame, diff --git a/dom/base/nsDOMClassInfo.cpp b/dom/base/nsDOMClassInfo.cpp index 6b2b9e2f61e0..da7f00514187 100644 --- a/dom/base/nsDOMClassInfo.cpp +++ b/dom/base/nsDOMClassInfo.cpp @@ -1975,11 +1975,11 @@ BaseStubConstructor(nsIWeakReference* aWeakOwner, nsCOMPtr currentWin(do_GetInterface(currentInner)); rv = WrapNative(cx, currentWin, &NS_GET_IID(nsIDOMWindow), - true, argv.handleAt(0)); + true, argv[0]); for (size_t i = 1; i < argc; ++i) { - argv[i] = args[i - 1]; - if (!JS_WrapValue(cx, argv.handleAt(i))) + argv[i].set(args[i - 1]); + if (!JS_WrapValue(cx, argv[i])) return NS_ERROR_FAILURE; } diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 441da91694e5..8100f6e80bb2 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -1002,7 +1002,7 @@ nsJSContext::SetProperty(JS::Handle aTarget, const char* aPropName, n // got the arguments, now attach them. for (uint32_t i = 0; i < args.length(); ++i) { - if (!JS_WrapValue(mContext, args.handleAt(i))) { + if (!JS_WrapValue(mContext, args[i])) { return NS_ERROR_FAILURE; } } @@ -1066,7 +1066,7 @@ nsJSContext::ConvertSupportsTojsvals(nsISupports* aArgs, if (argsArray) { for (uint32_t argCtr = 0; argCtr < argCount && NS_SUCCEEDED(rv); argCtr++) { nsCOMPtr arg; - JS::MutableHandle thisVal = aArgsOut.handleAt(argCtr); + JS::MutableHandle thisVal = aArgsOut[argCtr]; argsArray->QueryElementAt(argCtr, NS_GET_IID(nsISupports), getter_AddRefs(arg)); if (!arg) { @@ -1099,7 +1099,7 @@ nsJSContext::ConvertSupportsTojsvals(nsISupports* aArgs, } else { nsCOMPtr variant = do_QueryInterface(aArgs); if (variant) { - rv = xpc->VariantToJS(cx, aScope, variant, aArgsOut.handleAt(0)); + rv = xpc->VariantToJS(cx, aScope, variant, aArgsOut[0]); } else { NS_ERROR("Not an array, not an interface?"); rv = NS_ERROR_UNEXPECTED; diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 12cfe9a10994..97db5fa44fc7 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -2054,7 +2054,7 @@ inline bool AddStringToIDVector(JSContext* cx, JS::AutoIdVector& vector, const char* name) { return vector.growBy(1) && - InternJSString(cx, vector[vector.length() - 1], name); + InternJSString(cx, *(vector[vector.length() - 1]).address(), name); } // Implementation of the bits that XrayWrapper needs diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index bd09d7f75047..e0b1eec49f82 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -12525,8 +12525,8 @@ class CallbackMember(CGNativeMember): { 'result': result, 'successCode': "continue;\n" if arg.variadic else "break;\n", - 'jsvalRef': "argv.handleAt(%s)" % jsvalIndex, - 'jsvalHandle': "argv.handleAt(%s)" % jsvalIndex, + 'jsvalRef': "argv[%s]" % jsvalIndex, + 'jsvalHandle': "argv[%s]" % jsvalIndex, # XXXbz we don't have anything better to use for 'obj', # really... It's OK to use CallbackPreserveColor because # CallSetup already handled the unmark-gray bits for us. @@ -12558,7 +12558,7 @@ class CallbackMember(CGNativeMember): // This is our current trailing argument; reduce argc --argc; } else { - argv[${i}] = JS::UndefinedValue(); + argv[${i}].setUndefined(); } """, argName=arg.identifier.name, @@ -12816,7 +12816,7 @@ class CallbackSetter(CallbackAccessor): return fill( """ MOZ_ASSERT(argv.length() == 1); - if (!JS_SetProperty(cx, CallbackPreserveColor(), "${attrName}", argv.handleAt(0))) { + if (!JS_SetProperty(cx, CallbackPreserveColor(), "${attrName}", argv[0])) { aRv.Throw(NS_ERROR_UNEXPECTED); return${errorReturn}; } diff --git a/dom/bindings/ToJSValue.h b/dom/bindings/ToJSValue.h index 9056a423c993..092453c409ea 100644 --- a/dom/bindings/ToJSValue.h +++ b/dom/bindings/ToJSValue.h @@ -244,7 +244,7 @@ ToJSValue(JSContext* aCx, return false; } for (size_t i = 0; i < aLength; ++i) { - if (!ToJSValue(aCx, aArguments[i], v.handleAt(i))) { + if (!ToJSValue(aCx, aArguments[i], v[i])) { return false; } } diff --git a/dom/gamepad/Gamepad.h b/dom/gamepad/Gamepad.h index c17aae54cbe0..56cd5890d15f 100644 --- a/dom/gamepad/Gamepad.h +++ b/dom/gamepad/Gamepad.h @@ -22,6 +22,19 @@ enum GamepadMappingType StandardMapping = 1 }; +// Per spec: +// https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#remapping +const int kStandardGamepadButtons = 17; +const int kStandardGamepadAxes = 4; + +const int kButtonLeftTrigger = 6; +const int kButtonRightTrigger = 7; + +const int kLeftStickXAxis = 0; +const int kLeftStickYAxis = 1; +const int kRightStickXAxis = 2; +const int kRightStickYAxis = 3; + class Gamepad : public nsISupports, public nsWrapperCache { diff --git a/dom/media/tests/mochitest/head.js b/dom/media/tests/mochitest/head.js index 8cd8ebeb66ef..159f49619b61 100644 --- a/dom/media/tests/mochitest/head.js +++ b/dom/media/tests/mochitest/head.js @@ -97,7 +97,9 @@ function createMediaElement(type, label) { * The error callback if the stream fails to be retrieved */ function getUserMedia(constraints, onSuccess, onError) { - constraints["fake"] = FAKE_ENABLED; + if (!("fake" in constraints)) { + constraints["fake"] = FAKE_ENABLED; + } info("Call getUserMedia for " + JSON.stringify(constraints)); navigator.mozGetUserMedia(constraints, onSuccess, onError); diff --git a/dom/mobilemessage/src/MobileMessageManager.cpp b/dom/mobilemessage/src/MobileMessageManager.cpp index d69b8b2216b4..8a4fea87b027 100644 --- a/dom/mobilemessage/src/MobileMessageManager.cpp +++ b/dom/mobilemessage/src/MobileMessageManager.cpp @@ -131,7 +131,7 @@ MobileMessageManager::Send(JSContext* aCx, JS::Handle aGlobal, uint32_t aServiceId, JS::Handle aNumber, const nsAString& aMessage, - JS::Value* aRequest) + JS::MutableHandle aRequest) { nsCOMPtr smsService = do_GetService(SMS_SERVICE_CONTRACTID); NS_ENSURE_TRUE(smsService, NS_ERROR_FAILURE); @@ -149,16 +149,14 @@ MobileMessageManager::Send(JSContext* aCx, JS::Handle aGlobal, NS_ENSURE_SUCCESS(rv, rv); js::AssertSameCompartment(aCx, aGlobal); - JS::Rooted rval(aCx); rv = nsContentUtils::WrapNative(aCx, static_cast(request.get()), - &rval); + aRequest); if (NS_FAILED(rv)) { NS_ERROR("Failed to create the js value!"); return rv; } - *aRequest = rval; return NS_OK; } @@ -208,7 +206,7 @@ MobileMessageManager::Send(JS::Handle aNumber, if (aNumber.isString()) { JS::Rooted str(aCx, aNumber.toString()); - return Send(aCx, global, serviceId, str, aMessage, aReturn.address()); + return Send(aCx, global, serviceId, str, aMessage, aReturn); } // Must be an array then. @@ -236,7 +234,7 @@ MobileMessageManager::Send(JS::Handle aNumber, return NS_ERROR_FAILURE; } - nsresult rv = Send(aCx, global, serviceId, str, aMessage, &requests[i]); + nsresult rv = Send(aCx, global, serviceId, str, aMessage, requests[i]); NS_ENSURE_SUCCESS(rv, rv); } diff --git a/dom/mobilemessage/src/MobileMessageManager.h b/dom/mobilemessage/src/MobileMessageManager.h index 7d42097d1fe8..f53e0306279c 100644 --- a/dom/mobilemessage/src/MobileMessageManager.h +++ b/dom/mobilemessage/src/MobileMessageManager.h @@ -38,7 +38,7 @@ private: uint32_t aServiceId, JS::Handle aNumber, const nsAString& aMessage, - JS::Value* aRequest); + JS::MutableHandle aRequest); nsresult DispatchTrustedSmsEventToSelf(const char* aTopic, const nsAString& aEventName, diff --git a/dom/src/notification/NotificationDB.jsm b/dom/src/notification/NotificationDB.jsm index fa42873a4aa2..f082176f51da 100644 --- a/dom/src/notification/NotificationDB.jsm +++ b/dom/src/notification/NotificationDB.jsm @@ -272,6 +272,7 @@ let NotificationDB = { var id = data.id; if (!this.notifications[origin]) { if (DEBUG) { debug("No notifications found for origin: " + origin); } + callback(); return; } @@ -279,6 +280,7 @@ let NotificationDB = { var oldNotification = this.notifications[origin][id]; if (!oldNotification) { if (DEBUG) { debug("No notification found with id: " + id); } + callback(); return; } diff --git a/dom/src/notification/moz.build b/dom/src/notification/moz.build index 7d8375ef8dde..552eb3af0c97 100644 --- a/dom/src/notification/moz.build +++ b/dom/src/notification/moz.build @@ -34,3 +34,4 @@ LOCAL_INCLUDES += [ '/dom/ipc', ] +XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini'] diff --git a/dom/src/notification/test/unit/test_notificationdb.js b/dom/src/notification/test/unit/test_notificationdb.js new file mode 100644 index 000000000000..b274b48606bc --- /dev/null +++ b/dom/src/notification/test/unit/test_notificationdb.js @@ -0,0 +1,338 @@ +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", + "@mozilla.org/childprocessmessagemanager;1", + "nsIMessageSender"); + +let systemNotification = { + origin: "app://system.gaiamobile.org/manifest.webapp", + id: "{2bc883bf-2809-4432-b0f4-f54e10372764}", + title: "SystemNotification:" + Date.now(), + dir: "auto", + lang: "", + body: "System notification body", + tag: "", + icon: "icon.png" +}; + +let calendarNotification = { + origin: "app://calendar.gaiamobile.org/manifest.webapp", + id: "{d8d11299-a58e-429b-9a9a-57c562982fbf}", + title: "CalendarNotification:" + Date.now(), + dir: "auto", + lang: "", + body: "Calendar notification body", + tag: "", + icon: "icon.png" +}; + +function run_test() { + do_get_profile(); + Cu.import("resource://gre/modules/NotificationDB.jsm"); + run_next_test(); +} + +// Helper function to add a listener, send message and treat the reply +function addAndSend(msg, reply, callback, payload, runNext = true) { + let handler = { + receiveMessage: function(message) { + if (message.name === reply) { + cpmm.removeMessageListener(reply, handler); + callback(message); + if (runNext) { + run_next_test(); + } + } + } + }; + cpmm.addMessageListener(reply, handler); + cpmm.sendAsyncMessage(msg, payload); +} + +// helper fonction, comparing two notifications +function compareNotification(notif1, notif2) { + // retrieved notification should be the second one sent + for (let prop in notif1) { + // compare each property + do_check_eq(notif1[prop], notif2[prop]); + } +} + +// Get one notification, none exists +add_test(function test_get_none() { + let requestID = 0; + let msgReply = "Notification:GetAll:Return:OK"; + let msgHandler = function(message) { + do_check_eq(requestID, message.data.requestID); + do_check_eq(0, message.data.notifications.length); + }; + + addAndSend("Notification:GetAll", msgReply, msgHandler, { + origin: systemNotification.origin, + requestID: requestID + }); +}); + +// Store one notification +add_test(function test_send_one() { + let requestID = 1; + let msgReply = "Notification:Save:Return:OK"; + let msgHandler = function(message) { + do_check_eq(requestID, message.data.requestID); + }; + + addAndSend("Notification:Save", msgReply, msgHandler, { + origin: systemNotification.origin, + notification: systemNotification, + requestID: requestID + }); +}); + +// Get one notification, one exists +add_test(function test_get_one() { + let requestID = 2; + let msgReply = "Notification:GetAll:Return:OK"; + let msgHandler = function(message) { + do_check_eq(requestID, message.data.requestID); + do_check_eq(1, message.data.notifications.length); + // compare the content + compareNotification(systemNotification, message.data.notifications[0]); + }; + + addAndSend("Notification:GetAll", msgReply, msgHandler, { + origin: systemNotification.origin, + requestID: requestID + }); +}); + +// Delete one notification +add_test(function test_delete_one() { + let requestID = 3; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function(message) { + do_check_eq(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: systemNotification.id, + requestID: requestID + }); +}); + +// Get one notification, none exists +add_test(function test_get_none_again() { + let requestID = 4; + let msgReply = "Notification:GetAll:Return:OK"; + let msgHandler = function(message) { + do_check_eq(requestID, message.data.requestID); + do_check_eq(0, message.data.notifications.length); + }; + + addAndSend("Notification:GetAll", msgReply, msgHandler, { + origin: systemNotification.origin, + requestID: requestID + }); +}); + +// Delete one notification that do not exists anymore +add_test(function test_delete_one_nonexistent() { + let requestID = 5; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function(message) { + do_check_eq(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: systemNotification.id, + requestID: requestID + }); +}); + +// Store two notifications with the same id +add_test(function test_send_two_get_one() { + let requestID = 6; + let calls = 0; + + let msgGetReply = "Notification:GetAll:Return:OK"; + let msgGetHandler = function(message) { + do_check_eq(requestID + 2, message.data.requestID); + do_check_eq(1, message.data.notifications.length); + // compare the content + compareNotification(systemNotification, message.data.notifications[0]); + }; + + let msgSaveReply = "Notification:Save:Return:OK"; + let msgSaveHandler = function(message) { + calls += 1; + if (calls === 2) { + addAndSend("Notification:GetAll", msgGetReply, msgGetHandler, { + origin: systemNotification.origin, + requestID: (requestID + 2) + }); + } + }; + + addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, { + origin: systemNotification.origin, + notification: systemNotification, + requestID: requestID + }, false); + + addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, { + origin: systemNotification.origin, + notification: systemNotification, + requestID: (requestID + 1) + }, false); +}); + +// Delete previous notification +add_test(function test_delete_previous() { + let requestID = 8; + let msgReply = "Notification:Delete:Return:OK"; + let msgHandler = function(message) { + do_check_eq(requestID, message.data.requestID); + }; + + addAndSend("Notification:Delete", msgReply, msgHandler, { + origin: systemNotification.origin, + id: systemNotification.id, + requestID: requestID + }); +}); + +// Store two notifications from same origin with the same tag +add_test(function test_send_two_get_one() { + let requestID = 10; + let tag = "voicemail"; + + let systemNotification1 = systemNotification; + systemNotification1.id = "{f271f9ee-3955-4c10-b1f2-af552fb270ee}"; + systemNotification1.tag = tag; + + let systemNotification2 = systemNotification; + systemNotification2.id = "{8ef9a628-f0f4-44b4-820d-c117573c33e3}"; + systemNotification2.tag = tag; + + let msgGetReply = "Notification:GetAll:Return:OK"; + let msgGetNotifHandler = { + receiveMessage: function(message) { + if (message.name === msgGetReply) { + cpmm.removeMessageListener(msgGetReply, msgGetNotifHandler); + let notifications = message.data.notifications; + // same tag, so replaced + do_check_eq(1, notifications.length); + // compare the content + compareNotification(systemNotification2, notifications[0]); + run_next_test(); + } + } + }; + + cpmm.addMessageListener(msgGetReply, msgGetNotifHandler); + + let msgSaveReply = "Notification:Save:Return:OK"; + let msgSaveCalls = 0; + let msgSaveHandler = function(message) { + msgSaveCalls++; + // Once both request have been sent, trigger getall + if (msgSaveCalls === 2) { + cpmm.sendAsyncMessage("Notification:GetAll", { + origin: systemNotification1.origin, + requestID: message.data.requestID + 2 // 12, 13 + }); + } + }; + + addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, { + origin: systemNotification1.origin, + notification: systemNotification1, + requestID: requestID // 10 + }, false); + + addAndSend("Notification:Save", msgSaveReply, msgSaveHandler, { + origin: systemNotification2.origin, + notification: systemNotification2, + requestID: (requestID + 1) // 11 + }, false); +}); + +// Store two notifications from two origins with the same tag +add_test(function test_send_two_get_two() { + let requestID = 20; + let tag = "voicemail"; + + let systemNotification1 = systemNotification; + systemNotification1.tag = tag; + + let calendarNotification2 = calendarNotification; + calendarNotification2.tag = tag; + + let msgGetReply = "Notification:GetAll:Return:OK"; + let msgGetCalls = 0; + let msgGetHandler = { + receiveMessage: function(message) { + if (message.name === msgGetReply) { + msgGetCalls++; + let notifications = message.data.notifications; + + // one notification per origin + do_check_eq(1, notifications.length); + + // first call should be system notification + if (msgGetCalls === 1) { + compareNotification(systemNotification1, notifications[0]); + } + + // second and last call should be calendar notification + if (msgGetCalls === 2) { + cpmm.removeMessageListener(msgGetReply, msgGetHandler); + compareNotification(calendarNotification2, notifications[0]); + run_next_test(); + } + } + } + }; + cpmm.addMessageListener(msgGetReply, msgGetHandler); + + let msgSaveReply = "Notification:Save:Return:OK"; + let msgSaveCalls = 0; + let msgSaveHandler = { + receiveMessage: function(message) { + if (message.name === msgSaveReply) { + msgSaveCalls++; + if (msgSaveCalls === 2) { + cpmm.removeMessageListener(msgSaveReply, msgSaveHandler); + + // Trigger getall for each origin + cpmm.sendAsyncMessage("Notification:GetAll", { + origin: systemNotification1.origin, + requestID: message.data.requestID + 1 // 22 + }); + + cpmm.sendAsyncMessage("Notification:GetAll", { + origin: calendarNotification2.origin, + requestID: message.data.requestID + 2 // 23 + }); + } + } + } + }; + cpmm.addMessageListener(msgSaveReply, msgSaveHandler); + + cpmm.sendAsyncMessage("Notification:Save", { + origin: systemNotification1.origin, + notification: systemNotification1, + requestID: requestID // 20 + }); + + cpmm.sendAsyncMessage("Notification:Save", { + origin: calendarNotification2.origin, + notification: calendarNotification2, + requestID: (requestID + 1) // 21 + }); +}); diff --git a/dom/src/notification/test/unit/xpcshell.ini b/dom/src/notification/test/unit/xpcshell.ini new file mode 100644 index 000000000000..53a4e970d635 --- /dev/null +++ b/dom/src/notification/test/unit/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = +tail = + +[test_notificationdb.js] diff --git a/dom/webidl/Console.webidl b/dom/webidl/Console.webidl index bdaac9c8cf81..9684129f09cb 100644 --- a/dom/webidl/Console.webidl +++ b/dom/webidl/Console.webidl @@ -20,10 +20,7 @@ interface Console { void time(optional any time); void timeEnd(optional any time); - [Throws] void profile(any... data); - - [Throws] void profileEnd(any... data); void assert(boolean condition, any... data); diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index 8710769659cb..1fd6267d17f7 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -728,7 +728,7 @@ APZCTreeManager::HandOffFling(AsyncPanZoomController* aPrev, ScreenPoint aVeloci // otherwise built on touch-start and cleared on touch-end, and a fling // happens after touch-end. Note that, unlike DispatchScroll() which is // called on every touch-move during overscroll panning, - // HandleFlingOverscroll() is only called once during a fling handoff, + // HandOffFling() is only called once during a fling handoff, // so it's not worth trying to avoid building the handoff chain here. BuildOverscrollHandoffChain(aPrev); diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 346f2255e9ba..f4bd5baf0bc7 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -66,7 +66,7 @@ #define APZC_LOG_FM(fm, prefix, ...) \ APZC_LOG(prefix ":" \ " i=(%ld %lld) cb=(%d %d %d %d) rcs=(%.3f %.3f) dp=(%.3f %.3f %.3f %.3f) dpm=(%.3f %.3f %.3f %.3f) um=%d " \ - "v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z=(%.3f %.3f %.3f %.3f) u=(%d %lu)\n", \ + "v=(%.3f %.3f %.3f %.3f) s=(%.3f %.3f) sr=(%.3f %.3f %.3f %.3f) z(ld=%.3f r=%.3f cr=%.3f z=%.3f ts=%.3f) u=(%d %lu)\n", \ __VA_ARGS__, \ fm.mPresShellId, fm.GetScrollId(), \ fm.mCompositionBounds.x, fm.mCompositionBounds.y, fm.mCompositionBounds.width, fm.mCompositionBounds.height, \ @@ -77,7 +77,7 @@ fm.mViewport.x, fm.mViewport.y, fm.mViewport.width, fm.mViewport.height, \ fm.GetScrollOffset().x, fm.GetScrollOffset().y, \ fm.mScrollableRect.x, fm.mScrollableRect.y, fm.mScrollableRect.width, fm.mScrollableRect.height, \ - fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.GetZoom().scale, \ + fm.mDevPixelsPerCSSPixel.scale, fm.mResolution.scale, fm.mCumulativeResolution.scale, fm.GetZoom().scale, fm.mTransformScale.scale, \ fm.GetScrollOffsetUpdated(), fm.GetScrollGeneration()); \ // Static helper functions @@ -163,6 +163,20 @@ typedef GeckoContentController::APZStateChange APZStateChange; * generated displayport's size is beyond that of the scrollable rect on the * opposite axis. * + * "apz.fling_accel_interval_ms" + * The time in milliseconds that determines whether a second fling will be + * treated as accelerated. If two flings are started within this interval, + * the second one will be accelerated. Setting an interval of 0 means that + * acceleration will be disabled. + * + * "apz.fling_accel_base_mult" + * "apz.fling_accel_supplemental_mult" + * When applying an acceleration on a fling, the new computed velocity is + * (new_fling_velocity * base_mult) + (old_velocity * supplemental_mult). + * The base_mult and supplemental_mult multiplier values are controlled by + * these prefs. Note that "old_velocity" here is the initial velocity of the + * previous fling _after_ acceleration was applied to it (if applicable). + * * "apz.fling_friction" * Amount of friction applied during flings. * @@ -330,10 +344,40 @@ GetFrameTime() { class FlingAnimation: public AsyncPanZoomAnimation { public: - FlingAnimation(AsyncPanZoomController& aApzc) + FlingAnimation(AsyncPanZoomController& aApzc, bool aApplyAcceleration) : AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gfxPrefs::APZFlingRepaintInterval())) , mApzc(aApzc) - {} + { + TimeStamp now = GetFrameTime(); + ScreenPoint velocity(mApzc.mX.GetVelocity(), mApzc.mY.GetVelocity()); + + // If the last fling was very recent and in the same direction as this one, + // boost the velocity to be the sum of the two. Check separate axes separately + // because we could have two vertical flings with small horizontal components + // on the opposite side of zero, and we still want the y-fling to get accelerated. + // Note that the acceleration code is only applied on the APZC that receives the + // actual touch event; the accelerated velocities are then handed off using the + // normal HandOffFling codepath. + if (aApplyAcceleration && !mApzc.mLastFlingTime.IsNull() + && (now - mApzc.mLastFlingTime).ToMilliseconds() < gfxPrefs::APZFlingAccelInterval()) { + if (SameDirection(velocity.x, mApzc.mLastFlingVelocity.x)) { + velocity.x = Accelerate(velocity.x, mApzc.mLastFlingVelocity.x); + APZC_LOG("%p Applying fling x-acceleration from %f to %f (delta %f)\n", + &mApzc, mApzc.mX.GetVelocity(), velocity.x, mApzc.mLastFlingVelocity.x); + mApzc.mX.SetVelocity(velocity.x); + } + if (SameDirection(velocity.y, mApzc.mLastFlingVelocity.y)) { + velocity.y = Accelerate(velocity.y, mApzc.mLastFlingVelocity.y); + APZC_LOG("%p Applying fling y-acceleration from %f to %f (delta %f)\n", + &mApzc, mApzc.mY.GetVelocity(), velocity.y, mApzc.mLastFlingVelocity.y); + mApzc.mY.SetVelocity(velocity.y); + } + } + + mApzc.mLastFlingTime = now; + mApzc.mLastFlingVelocity = velocity; + } + /** * Advances a fling by an interpolated amount based on the passed in |aDelta|. * This should be called whenever sampling the content transform for this @@ -344,6 +388,19 @@ public: const TimeDuration& aDelta); private: + static bool SameDirection(float aVelocity1, float aVelocity2) + { + return (aVelocity1 == 0.0f) + || (aVelocity2 == 0.0f) + || (IsNegative(aVelocity1) == IsNegative(aVelocity2)); + } + + static float Accelerate(float aBase, float aSupplemental) + { + return (aBase * gfxPrefs::APZFlingAccelBaseMultiplier()) + + (aSupplemental * gfxPrefs::APZFlingAccelSupplementalMultiplier()); + } + AsyncPanZoomController& mApzc; }; @@ -755,7 +812,7 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) mX.EndTouch(); mY.EndTouch(); SetState(FLING); - StartAnimation(new FlingAnimation(*this)); + StartAnimation(new FlingAnimation(*this, true)); return nsEventStatus_eConsumeNoDefault; case PINCHING: @@ -1183,7 +1240,7 @@ void AsyncPanZoomController::TakeOverFling(ScreenPoint aVelocity) { mX.SetVelocity(mX.GetVelocity() + aVelocity.x); mY.SetVelocity(mY.GetVelocity() + aVelocity.y); SetState(FLING); - StartAnimation(new FlingAnimation(*this)); + StartAnimation(new FlingAnimation(*this, false)); } void AsyncPanZoomController::CallDispatchScroll(const ScreenPoint& aStartPoint, const ScreenPoint& aEndPoint, @@ -1294,20 +1351,20 @@ bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics, velocity.y = 0; } - // To hand off the fling, we call APZCTreeManager::HandleFlingOverscroll() + // To hand off the fling, we call APZCTreeManager::HandOffFling() // which starts a new fling in the next APZC in the handoff chain with // the same velocity. For simplicity, the actual overscroll of the current // sample is discarded rather than being handed off. The compositor should // sample animations sufficiently frequently that this is not noticeable. // Make a local copy of the tree manager pointer and check if it's not - // null before calling HandleFlingOverscroll(). This is necessary because + // null before calling HandOffFling(). This is necessary because // Destroy(), which nulls out mTreeManager, could be called concurrently. APZCTreeManager* treeManagerLocal = mApzc.mTreeManager; if (treeManagerLocal) { - // APZC is holding mMonitor, so directly calling HandleFlingOverscroll() + // APZC is holding mMonitor, so directly calling HandOffFling() // (which acquires the tree lock) would violate the lock ordering. Instead - // we schedule HandleFlingOverscroll() to be called after mMonitor is + // we schedule HandOffFling() to be called after mMonitor is // released. mDeferredTasks.append(NewRunnableMethod(treeManagerLocal, &APZCTreeManager::HandOffFling, @@ -1718,11 +1775,11 @@ gfx3DMatrix AsyncPanZoomController::GetTransformToLastDispatchedPaint() { void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) { ReentrantMonitorAutoEnter lock(mMonitor); + bool isDefault = mFrameMetrics.IsDefault(); mLastContentPaintMetrics = aLayerMetrics; UpdateTransformScale(); - bool isDefault = mFrameMetrics.IsDefault(); mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners; APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d", this, aIsFirstPaint); diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index 3bae19f1531b..85ba2138508e 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -294,12 +294,6 @@ public: */ void CancelAnimation(); - /** - * Take over a fling with the given velocity from another APZC. Used for - * during overscroll handoff for a fling. - */ - void TakeOverFling(ScreenPoint aVelocity); - /** * Returns allowed touch behavior for the given point on the scrollable layer. * Internally performs a kind of hit testing based on the regions constructed @@ -501,14 +495,6 @@ protected: */ void DispatchRepaintRequest(const FrameMetrics& aFrameMetrics); - /** - * Advances a fling by an interpolated amount based on the passed in |aDelta|. - * This should be called whenever sampling the content transform for this - * frame. Returns true if the fling animation should be advanced by one frame, - * or false if there is no fling or the fling has ended. - */ - bool DoFling(const TimeDuration& aDelta); - /** * Gets the current frame metrics. This is *not* the Gecko copy stored in the * layers code. @@ -777,7 +763,25 @@ private: RefPtr mAnimation; friend class Axis; + + + /* =================================================================== + * The functions and members in this section are used to manage + * fling animations. + */ +public: + /** + * Take over a fling with the given velocity from another APZC. Used for + * during overscroll handoff for a fling. + */ + void TakeOverFling(ScreenPoint aVelocity); + +private: friend class FlingAnimation; + // The initial velocity of the most recent fling. + ScreenPoint mLastFlingVelocity; + // The time at which the most recent fling started. + TimeStamp mLastFlingTime; /* =================================================================== diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index b8930d41b560..2ffc8c6926f1 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -111,6 +111,9 @@ private: DECL_GFX_PREF(Live, "apz.content_response_timeout", APZContentResponseTimeout, int32_t, 300); DECL_GFX_PREF(Live, "apz.cross_slide.enabled", APZCrossSlideEnabled, bool, false); DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped", APZEnlargeDisplayPortWhenClipped, bool, false); + DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms", APZFlingAccelInterval, int32_t, 500); + DECL_GFX_PREF(Live, "apz.fling_accel_base_mult", APZFlingAccelBaseMultiplier, float, 1.0f); + DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult", APZFlingAccelSupplementalMultiplier, float, 1.0f); DECL_GFX_PREF(Once, "apz.fling_friction", APZFlingFriction, float, 0.002f); DECL_GFX_PREF(Live, "apz.fling_repaint_interval", APZFlingRepaintInterval, int32_t, 75); DECL_GFX_PREF(Once, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f); diff --git a/hal/windows/WindowsGamepad.cpp b/hal/windows/WindowsGamepad.cpp index 1ff7d36f18b5..d5ed6d9953ba 100644 --- a/hal/windows/WindowsGamepad.cpp +++ b/hal/windows/WindowsGamepad.cpp @@ -1,3 +1,4 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ @@ -5,139 +6,259 @@ #include #include -#include #ifndef UNICODE #define UNICODE #endif #include -#define DIRECTINPUT_VERSION 0x0800 -#include +#include +#include +#include #include "nsIComponentManager.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsITimer.h" #include "nsTArray.h" -#include "nsThreadUtils.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/dom/GamepadService.h" -#include "mozilla/Mutex.h" #include "mozilla/Services.h" namespace { -using mozilla::dom::GamepadService; -using mozilla::Mutex; -using mozilla::MutexAutoLock; +using namespace mozilla::dom; +using mozilla::ArrayLength; + +// USB HID usage tables, page 1 (Hat switch) +const unsigned kUsageDpad = 0x39; +// USB HID usage tables, page 1, 0x30 = X +const unsigned kFirstAxis = 0x30; + +// USB HID usage tables +const unsigned kDesktopUsagePage = 0x1; +const unsigned kButtonUsagePage = 0x9; + +// Arbitrary. In practice 10 buttons/6 axes is the near maximum. +const unsigned kMaxButtons = 32; +const unsigned kMaxAxes = 32; -const LONG kMaxAxisValue = 65535; -const DWORD BUTTON_DOWN_MASK = 0x80; // Multiple devices-changed notifications can be sent when a device // is connected, because USB devices consist of multiple logical devices. // Therefore, we wait a bit after receiving one before looking for // device changes. const uint32_t kDevicesChangedStableDelay = 200; +// XInput is a purely polling-driven API, so we need to +// poll it periodically. 50ms is arbitrarily chosen. +const uint32_t kXInputPollInterval = 50; + +#ifndef XUSER_MAX_COUNT +#define XUSER_MAX_COUNT 4 +#endif + +const struct { + int usagePage; + int usage; +} kUsagePages[] = { + // USB HID usage tables, page 1 + { kDesktopUsagePage, 4 }, // Joystick + { kDesktopUsagePage, 5 } // Gamepad +}; + +const struct { + WORD button; + int mapped; +} kXIButtonMap[] = { + { XINPUT_GAMEPAD_DPAD_UP, 12 }, + { XINPUT_GAMEPAD_DPAD_DOWN, 13 }, + { XINPUT_GAMEPAD_DPAD_LEFT, 14 }, + { XINPUT_GAMEPAD_DPAD_RIGHT, 15 }, + { XINPUT_GAMEPAD_START, 9 }, + { XINPUT_GAMEPAD_BACK, 8 }, + { XINPUT_GAMEPAD_LEFT_THUMB, 10 }, + { XINPUT_GAMEPAD_RIGHT_THUMB, 11 }, + { XINPUT_GAMEPAD_LEFT_SHOULDER, 4 }, + { XINPUT_GAMEPAD_RIGHT_SHOULDER, 5 }, + { XINPUT_GAMEPAD_A, 0 }, + { XINPUT_GAMEPAD_B, 1 }, + { XINPUT_GAMEPAD_X, 2 }, + { XINPUT_GAMEPAD_Y, 3 } +}; +const size_t kNumMappings = ArrayLength(kXIButtonMap); + +enum GamepadType { + kNoGamepad = 0, + kRawInputGamepad, + kXInputGamepad +}; class WindowsGamepadService; WindowsGamepadService* gService = nullptr; -typedef struct { - float x,y; -} HatState; - struct Gamepad { - // From DirectInput, unique to this device+computer combination. - GUID guidInstance; - // The ID assigned by the base GamepadService - int globalID; - // A somewhat unique string consisting of the USB vendor/product IDs, - // and the controller name. - char idstring[128]; - // USB vendor and product IDs - int vendorID; - int productID; + GamepadType type; + + // Handle to raw input device + HANDLE handle; + + // XInput Index of the user's controller. Passed to XInputGetState. + DWORD userIndex; + + // Last-known state of the controller. + XINPUT_STATE state; + + // ID from the GamepadService, also used as the index into + // WindowsGamepadService::mGamepads. + int id; + // Information about the physical device. - int numAxes; - int numHats; - int numButtons; - // The human-readable device name. - char name[128]; - // The DirectInput device. - nsRefPtr device; - // A handle that DirectInput signals when there is new data from - // the device. - HANDLE event; - // The state of any POV hats on the device. - HatState hatState[4]; + unsigned numAxes; + unsigned numButtons; + bool hasDpad; + HIDP_VALUE_CAPS dpadCaps; + + bool buttons[kMaxButtons]; + struct { + HIDP_VALUE_CAPS caps; + double value; + } axes[kMaxAxes]; + // Used during rescan to find devices that were disconnected. bool present; - // Passed back from the main thread to indicate a device can - // now be removed. - bool remove; }; -// Given DWORD |hatPos| representing the position of the POV hat per: -// http://msdn.microsoft.com/en-us/library/ee418260%28v=VS.85%29.aspx -// fill |axes| with the position of the x and y axes. -// -//XXX: ostensibly the values could be arbitrary degrees for a hat with -// full rotation, but we'll punt on that for now. This should handle -// 8-way D-pads exposed as POV hats. -static void -HatPosToAxes(DWORD hatPos, HatState& axes) { - // hatPos is in hundredths of a degree clockwise from north. - if (LOWORD(hatPos) == 0xFFFF) { - // centered - axes.x = axes.y = 0.0; +// Drop this in favor of decltype when we require a new enough SDK. +typedef void (WINAPI *XInputEnable_func)(BOOL); + +// RAII class to wrap loading the XInput DLL +class XInputLoader { +public: + XInputLoader() : module(nullptr), + mXInputEnable(nullptr), + mXInputGetState(nullptr) { + // xinput1_4.dll exists on Windows 8 + // xinput9_1_0.dll exists on Windows 7 and Vista + // xinput1_3.dll shipped with the DirectX SDK + const wchar_t* dlls[] = {L"xinput1_4.dll", + L"xinput9_1_0.dll", + L"xinput1_3.dll"}; + const size_t kNumDLLs = ArrayLength(dlls); + for (size_t i = 0; i < kNumDLLs; ++i) { + module = LoadLibraryW(dlls[i]); + if (module) { + mXInputEnable = reinterpret_cast( + GetProcAddress(module, "XInputEnable")); + mXInputGetState = reinterpret_cast( + GetProcAddress(module, "XInputGetState")); + if (mXInputEnable) { + mXInputEnable(TRUE); + } + break; + } + } } - else if (hatPos == 0) { - // Up - axes.x = 0.0; - axes.y = -1.0; + + ~XInputLoader() { + //mXInputEnable = nullptr; + mXInputGetState = nullptr; + + if (module) { + FreeLibrary(module); + } } - else if (hatPos == 45 * DI_DEGREES) { - // Up-right - axes.x = 1.0; - axes.y = -1.0; + + operator bool() { + return module && mXInputGetState; } - else if (hatPos == 90 * DI_DEGREES) { - // Right - axes.x = 1.0; - axes.y = 0.0; + + HMODULE module; + decltype(XInputGetState) *mXInputGetState; + XInputEnable_func mXInputEnable; +}; + +bool +GetPreparsedData(HANDLE handle, nsTArray& data) +{ + UINT size; + if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0) { + return false; } - else if (hatPos == 135 * DI_DEGREES) { - // Down-right - axes.x = 1.0; - axes.y = 1.0; + data.SetLength(size); + return GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, + data.Elements(), &size) > 0; +} + +/* + * Given an axis value and a minimum and maximum range, + * scale it to be in the range -1.0 .. 1.0. + */ +double +ScaleAxis(ULONG value, LONG min, LONG max) +{ + return 2.0 * (value - min) / (max - min) - 1.0; +} + +/* + * Given a value from a d-pad (POV hat in USB HID terminology), + * represent it as 4 buttons, one for each cardinal direction. + */ +void +UnpackDpad(LONG dpad_value, const Gamepad* gamepad, bool buttons[kMaxButtons]) +{ + const unsigned kUp = gamepad->numButtons - 4; + const unsigned kDown = gamepad->numButtons - 3; + const unsigned kLeft = gamepad->numButtons - 2; + const unsigned kRight = gamepad->numButtons - 1; + + // Different controllers have different ways of representing + // "nothing is pressed", but they're all outside the range of values. + if (dpad_value < gamepad->dpadCaps.LogicalMin + || dpad_value > gamepad->dpadCaps.LogicalMax) { + // Nothing is pressed. + return; } - else if (hatPos == 180 * DI_DEGREES) { - // Down - axes.x = 0.0; - axes.y = 1.0; + + // Normalize value to start at 0. + int value = dpad_value - gamepad->dpadCaps.LogicalMin; + + // Value will be in the range 0-7. The value represents the + // position of the d-pad around a circle, with 0 being straight up, + // 2 being right, 4 being straight down, and 6 being left. + if (value < 2 || value > 6) { + buttons[kUp] = true; } - else if (hatPos == 225 * DI_DEGREES) { - // Down-left - axes.x = -1.0; - axes.y = 1.0; + if (value > 2 && value < 6) { + buttons[kDown] = true; } - else if (hatPos == 270 * DI_DEGREES) { - // Left - axes.x = -1.0; - axes.y = 0.0; + if (value > 4) { + buttons[kLeft] = true; } - else if (hatPos == 315 * DI_DEGREES) { - // Up-left - axes.x = -1.0; - axes.y = -1.0; + if (value > 0 && value < 4) { + buttons[kRight] = true; } } +/* + * Return true if this USB HID usage page and usage are of a type we + * know how to handle. + */ +bool +SupportedUsage(USHORT page, USHORT usage) +{ + for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) { + if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) { + return true; + } + } + return false; +} + class Observer : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER Observer(WindowsGamepadService& svc) : mSvc(svc), - mObserving(true) { + mObserving(true) + { nsresult rv; mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); nsCOMPtr observerService = @@ -147,7 +268,8 @@ public: false); } - void Stop() { + void Stop() + { if (mTimer) { mTimer->Cancel(); } @@ -159,12 +281,14 @@ public: } } - virtual ~Observer() { + virtual ~Observer() + { Stop(); } - void SetDeviceChangeTimer() { - // set stable timer, since we will get multiple devices-changed + void SetDeviceChangeTimer() + { + // Set stable timer, since we will get multiple devices-changed // notifications at once if (mTimer) { mTimer->Cancel(); @@ -181,17 +305,63 @@ private: NS_IMPL_ISUPPORTS(Observer, nsIObserver); +class HIDLoader { +public: + HIDLoader() : mModule(LoadLibraryW(L"hid.dll")), + mHidD_GetProductString(nullptr), + mHidP_GetCaps(nullptr), + mHidP_GetButtonCaps(nullptr), + mHidP_GetValueCaps(nullptr), + mHidP_GetUsages(nullptr), + mHidP_GetUsageValue(nullptr), + mHidP_GetScaledUsageValue(nullptr) + { + if (mModule) { + mHidD_GetProductString = reinterpret_cast(GetProcAddress(mModule, "HidD_GetProductString")); + mHidP_GetCaps = reinterpret_cast(GetProcAddress(mModule, "HidP_GetCaps")); + mHidP_GetButtonCaps = reinterpret_cast(GetProcAddress(mModule, "HidP_GetButtonCaps")); + mHidP_GetValueCaps = reinterpret_cast(GetProcAddress(mModule, "HidP_GetValueCaps")); + mHidP_GetUsages = reinterpret_cast(GetProcAddress(mModule, "HidP_GetUsages")); + mHidP_GetUsageValue = reinterpret_cast(GetProcAddress(mModule, "HidP_GetUsageValue")); + mHidP_GetScaledUsageValue = reinterpret_cast(GetProcAddress(mModule, "HidP_GetScaledUsageValue")); + } + } + + ~HIDLoader() { + if (mModule) { + FreeLibrary(mModule); + } + } + + operator bool() { + return mModule && + mHidD_GetProductString && + mHidP_GetCaps && + mHidP_GetButtonCaps && + mHidP_GetValueCaps && + mHidP_GetUsages && + mHidP_GetUsageValue && + mHidP_GetScaledUsageValue; + } + + decltype(HidD_GetProductString) *mHidD_GetProductString; + decltype(HidP_GetCaps) *mHidP_GetCaps; + decltype(HidP_GetButtonCaps) *mHidP_GetButtonCaps; + decltype(HidP_GetValueCaps) *mHidP_GetValueCaps; + decltype(HidP_GetUsages) *mHidP_GetUsages; + decltype(HidP_GetUsageValue) *mHidP_GetUsageValue; + decltype(HidP_GetScaledUsageValue) *mHidP_GetScaledUsageValue; + +private: + HMODULE mModule; +}; + class WindowsGamepadService { public: WindowsGamepadService(); - virtual ~WindowsGamepadService() { + virtual ~WindowsGamepadService() + { Cleanup(); - CloseHandle(mThreadExitEvent); - CloseHandle(mThreadRescanEvent); - if (dinput) { - dinput->Release(); - dinput = nullptr; - } } enum DeviceChangeType { @@ -201,491 +371,550 @@ public: void DevicesChanged(DeviceChangeType type); void Startup(); void Shutdown(); - void SetGamepadID(int localID, int globalID); - void RemoveGamepad(int localID); + // Parse gamepad input from a WM_INPUT message. + bool HandleRawInput(HRAWINPUT handle); private: void ScanForDevices(); - void Cleanup(); - void CleanupGamepad(Gamepad& gamepad); - // Callback for enumerating axes on a device - static BOOL CALLBACK EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, - LPVOID pvRef); - // Callback for enumerating devices via DInput - static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef); - // Thread function to wait on device events - static DWORD WINAPI DInputThread(LPVOID arg); + // Look for connected raw input devices. + void ScanForRawInputDevices(); + // Look for connected XInput devices. + bool ScanForXInputDevices(); + bool HaveXInputGamepad(int userIndex); - // Used to signal the background thread to exit. - HANDLE mThreadExitEvent; - // Used to signal the background thread to rescan devices. - HANDLE mThreadRescanEvent; - HANDLE mThread; + // Timer callback for XInput polling + static void XInputPollTimerCallback(nsITimer* aTimer, void* aClosure); + void PollXInput(); + void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state); + + // Get information about a raw input gamepad. + bool GetRawGamepad(HANDLE handle); + void Cleanup(); // List of connected devices. nsTArray mGamepads; - // Used to lock mutation of mGamepads. - Mutex mMutex; - // List of event handles used for signaling. - nsTArray mEvents; - - LPDIRECTINPUT8 dinput; nsRefPtr mObserver; + nsCOMPtr mXInputPollTimer; + + HIDLoader mHID; + XInputLoader mXInput; }; -// Used to post events from the background thread to the foreground thread. -class GamepadEvent : public nsRunnable { -public: - typedef enum { - Axis, - Button, - HatX, - HatY, - HatXY, - Unknown - } Type; - - GamepadEvent(const Gamepad& gamepad, - Type type, - int which, - DWORD data) : mGlobalID(gamepad.globalID), - mGamepadAxes(gamepad.numAxes), - mType(type), - mWhich(which), - mData(data) { - } - - NS_IMETHOD Run() { - nsRefPtr gamepadsvc(GamepadService::GetService()); - if (!gamepadsvc) { - return NS_OK; - } - - switch (mType) { - case Button: - gamepadsvc->NewButtonEvent(mGlobalID, mWhich, mData & BUTTON_DOWN_MASK); - break; - case Axis: { - float adjustedData = ((float)mData * 2.0f) / (float)kMaxAxisValue - 1.0f; - gamepadsvc->NewAxisMoveEvent(mGlobalID, mWhich, adjustedData); - } - case HatX: - case HatY: - case HatXY: { - // Synthesize 2 axes per POV hat for convenience. - HatState hatState; - HatPosToAxes(mData, hatState); - int xAxis = mGamepadAxes + 2 * mWhich; - int yAxis = mGamepadAxes + 2 * mWhich + 1; - //TODO: ostensibly we could not fire an event if one axis hasn't - // changed, but it's a pain to track that. - if (mType == HatX || mType == HatXY) { - gamepadsvc->NewAxisMoveEvent(mGlobalID, xAxis, hatState.x); - } - if (mType == HatY || mType == HatXY) { - gamepadsvc->NewAxisMoveEvent(mGlobalID, yAxis, hatState.y); - } - break; - } - case Unknown: - break; - } - return NS_OK; - } - - int mGlobalID; - int mGamepadAxes; - // Type of event - Type mType; - // Which button/axis is involved - int mWhich; - // Data specific to event - DWORD mData; -}; - -class GamepadChangeEvent : public nsRunnable { -public: - enum Type { - Added, - Removed - }; - GamepadChangeEvent(Gamepad& gamepad, - int localID, - Type type) : mLocalID(localID), - mName(gamepad.idstring), - mGlobalID(gamepad.globalID), - mGamepadButtons(gamepad.numButtons), - mGamepadAxes(gamepad.numAxes), - mGamepadHats(gamepad.numHats), - mType(type) { - } - - NS_IMETHOD Run() { - nsRefPtr gamepadsvc(GamepadService::GetService()); - if (!gamepadsvc) { - return NS_OK; - } - if (mType == Added) { - int globalID = gamepadsvc->AddGamepad(mName.get(), - mozilla::dom::NoMapping, - mGamepadButtons, - mGamepadAxes + - mGamepadHats*2); - if (gService) { - gService->SetGamepadID(mLocalID, globalID); - } - } else { - gamepadsvc->RemoveGamepad(mGlobalID); - if (gService) { - gService->RemoveGamepad(mLocalID); - } - } - return NS_OK; - } - -private: - // ID in WindowsGamepadService::mGamepads - int mLocalID; - nsCString mName; - int mGamepadButtons; - int mGamepadAxes; - int mGamepadHats; - // ID from GamepadService - uint32_t mGlobalID; - Type mType; -}; WindowsGamepadService::WindowsGamepadService() - : mThreadExitEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)), - mThreadRescanEvent(CreateEventW(nullptr, FALSE, FALSE, nullptr)), - mThread(nullptr), - mMutex("Windows Gamepad Service"), - dinput(nullptr) { +{ + nsresult rv; + mXInputPollTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); mObserver = new Observer(*this); - // Initialize DirectInput - CoInitialize(nullptr); - if (CoCreateInstance(CLSID_DirectInput8, - nullptr, - CLSCTX_INPROC_SERVER, - IID_IDirectInput8W, - (LPVOID*)&dinput) == S_OK) { - if (dinput->Initialize(GetModuleHandle(nullptr), - DIRECTINPUT_VERSION) != DI_OK) { - dinput->Release(); - dinput = nullptr; - } - } -} - -// static -BOOL CALLBACK -WindowsGamepadService::EnumObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, - LPVOID pvRef) { - // Ensure that all axes are using the same range. - Gamepad* gamepad = reinterpret_cast(pvRef); - DIPROPRANGE dp; - dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); - dp.diph.dwSize = sizeof(DIPROPRANGE); - dp.diph.dwHow = DIPH_BYID; - dp.diph.dwObj = lpddoi->dwType; - dp.lMin = 0; - dp.lMax = kMaxAxisValue; - gamepad->device->SetProperty(DIPROP_RANGE, &dp.diph); - return DIENUM_CONTINUE; -} - -// static -BOOL CALLBACK -WindowsGamepadService::EnumCallback(LPCDIDEVICEINSTANCE lpddi, - LPVOID pvRef) { - WindowsGamepadService* self = - reinterpret_cast(pvRef); - // See if this device is already present in our list. - { - MutexAutoLock lock(self->mMutex); - for (unsigned int i = 0; i < self->mGamepads.Length(); i++) { - if (memcmp(&lpddi->guidInstance, &self->mGamepads[i].guidInstance, - sizeof(GUID)) == 0) { - self->mGamepads[i].present = true; - return DIENUM_CONTINUE; - } - } - } - - Gamepad gamepad; - memset(&gamepad, 0, sizeof(Gamepad)); - if (self->dinput->CreateDevice(lpddi->guidInstance, - getter_AddRefs(gamepad.device), - nullptr) - == DI_OK) { - gamepad.present = true; - memcpy(&gamepad.guidInstance, &lpddi->guidInstance, sizeof(GUID)); - - DIDEVICEINSTANCE info; - info.dwSize = sizeof(DIDEVICEINSTANCE); - if (gamepad.device->GetDeviceInfo(&info) == DI_OK) { - WideCharToMultiByte(CP_UTF8, 0, info.tszProductName, -1, - gamepad.name, sizeof(gamepad.name), nullptr, nullptr); - } - // Get vendor id and product id - DIPROPDWORD dp; - dp.diph.dwSize = sizeof(DIPROPDWORD); - dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); - dp.diph.dwObj = 0; - dp.diph.dwHow = DIPH_DEVICE; - if (gamepad.device->GetProperty(DIPROP_VIDPID, &dp.diph) == DI_OK) { - sprintf(gamepad.idstring, "%x-%x-%s", - LOWORD(dp.dwData), HIWORD(dp.dwData), gamepad.name); - } - DIDEVCAPS caps; - caps.dwSize = sizeof(DIDEVCAPS); - if (gamepad.device->GetCapabilities(&caps) == DI_OK) { - gamepad.numAxes = caps.dwAxes; - gamepad.numHats = caps.dwPOVs; - gamepad.numButtons = caps.dwButtons; - //XXX: handle polled devices? - // (caps.dwFlags & DIDC_POLLEDDATAFORMAT || caps.dwFlags & DIDC_POLLEDDEVICE) - } - // Set min/max range for all axes on the device. - gamepad.device->EnumObjects(EnumObjectsCallback, &gamepad, DIDFT_AXIS); - // Set up structure for setting buffer size for buffered data - dp.diph.dwHeaderSize = sizeof(DIPROPHEADER); - dp.diph.dwSize = sizeof(DIPROPDWORD); - dp.diph.dwObj = 0; - dp.diph.dwHow = DIPH_DEVICE; - dp.dwData = 64; // arbitrary - // Create event so DInput can signal us when there's new data. - gamepad.event = CreateEventW(nullptr, FALSE, FALSE, nullptr); - // Set data format, event notification, and acquire device - if (gamepad.device->SetDataFormat(&c_dfDIJoystick) == DI_OK && - gamepad.device->SetProperty(DIPROP_BUFFERSIZE, &dp.diph) == DI_OK && - gamepad.device->SetEventNotification(gamepad.event) == DI_OK && - gamepad.device->Acquire() == DI_OK) { - MutexAutoLock lock(self->mMutex); - self->mGamepads.AppendElement(gamepad); - // Inform the GamepadService - int localID = self->mGamepads.Length() - 1; - nsRefPtr event = - new GamepadChangeEvent(self->mGamepads[localID], - localID, - GamepadChangeEvent::Added); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - } - else { - if (gamepad.device) { - gamepad.device->SetEventNotification(nullptr); - } - CloseHandle(gamepad.event); - } - } - return DIENUM_CONTINUE; } void -WindowsGamepadService::ScanForDevices() { - { - MutexAutoLock lock(mMutex); - for (int i = mGamepads.Length() - 1; i >= 0; i--) { - if (mGamepads[i].remove) { - - // Main thread has already handled this, safe to remove. - CleanupGamepad(mGamepads[i]); - mGamepads.RemoveElementAt(i); - } else { - mGamepads[i].present = false; - } - } +WindowsGamepadService::ScanForRawInputDevices() +{ + if (!mHID) { + return; } - dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, - (LPDIENUMDEVICESCALLBACK)EnumCallback, - this, - DIEDFL_ATTACHEDONLY); - - // Look for devices that are no longer present and inform the main thread. - { - MutexAutoLock lock(mMutex); - for (int i = mGamepads.Length() - 1; i >= 0; i--) { - if (!mGamepads[i].present) { - nsRefPtr event = - new GamepadChangeEvent(mGamepads[i], - i, - GamepadChangeEvent::Removed); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - } - } - - mEvents.Clear(); - for (unsigned int i = 0; i < mGamepads.Length(); i++) { - mEvents.AppendElement(mGamepads[i].event); - } + UINT numDevices; + if (GetRawInputDeviceList(nullptr, &numDevices, sizeof(RAWINPUTDEVICELIST)) + == -1) { + return; + } + nsTArray devices(numDevices); + devices.SetLength(numDevices); + if (GetRawInputDeviceList(devices.Elements(), &numDevices, + sizeof(RAWINPUTDEVICELIST)) == -1) { + return; } - - // These events must be the last elements in the array, so that - // the other elements match mGamepads in order. - mEvents.AppendElement(mThreadRescanEvent); - mEvents.AppendElement(mThreadExitEvent); + for (unsigned i = 0; i < devices.Length(); i++) { + if (devices[i].dwType == RIM_TYPEHID) { + GetRawGamepad(devices[i].hDevice); + } + } } -// static -DWORD WINAPI -WindowsGamepadService::DInputThread(LPVOID arg) { - WindowsGamepadService* self = reinterpret_cast(arg); - self->ScanForDevices(); - - while (true) { - DWORD result = WaitForMultipleObjects(self->mEvents.Length(), - self->mEvents.Elements(), - FALSE, - INFINITE); - if (result == WAIT_FAILED || - result == WAIT_OBJECT_0 + self->mEvents.Length() - 1) { - // error, or the main thread signaled us to exit - break; +bool +WindowsGamepadService::HaveXInputGamepad(int userIndex) +{ + for (unsigned int i = 0; i < mGamepads.Length(); i++) { + if (mGamepads[i].type == kXInputGamepad + && mGamepads[i].userIndex == userIndex) { + mGamepads[i].present = true; + return true; } + } + return false; +} - unsigned int i = result - WAIT_OBJECT_0; +bool +WindowsGamepadService::ScanForXInputDevices() +{ + MOZ_ASSERT(mXInput, "XInput should be present!"); - if (i == self->mEvents.Length() - 2) { - // Main thread is signaling for a device rescan. - self->ScanForDevices(); + nsRefPtr gamepadsvc(GamepadService::GetService()); + if (!gamepadsvc) { + return false; + } + + bool found = false; + for (int i = 0; i < XUSER_MAX_COUNT; i++) { + XINPUT_STATE state = {}; + if (mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) { + continue; + } + found = true; + // See if this device is already present in our list. + if (HaveXInputGamepad(i)) { continue; } - { - MutexAutoLock lock(self->mMutex); - if (i >= self->mGamepads.Length()) { - // Something would be terribly wrong here, possibly we got - // a WAIT_ABANDONED_x result. + // Not already present, add it. + Gamepad gamepad = {}; + gamepad.type = kXInputGamepad; + gamepad.present = true; + gamepad.state = state; + gamepad.userIndex = i; + gamepad.numButtons = kStandardGamepadButtons; + gamepad.numAxes = kStandardGamepadAxes; + gamepad.id = gamepadsvc->AddGamepad("xinput", + StandardMapping, + kStandardGamepadButtons, + kStandardGamepadAxes); + mGamepads.AppendElement(gamepad); + } + + return found; +} + +void +WindowsGamepadService::ScanForDevices() +{ + for (int i = mGamepads.Length() - 1; i >= 0; i--) { + mGamepads[i].present = false; + } + + if (mHID) { + ScanForRawInputDevices(); + } + if (mXInput) { + mXInputPollTimer->Cancel(); + if (ScanForXInputDevices()) { + mXInputPollTimer->InitWithFuncCallback(XInputPollTimerCallback, + this, + kXInputPollInterval, + nsITimer::TYPE_REPEATING_SLACK); + } + } + + nsRefPtr gamepadsvc(GamepadService::GetService()); + if (!gamepadsvc) { + return; + } + // Look for devices that are no longer present and remove them. + for (int i = mGamepads.Length() - 1; i >= 0; i--) { + if (!mGamepads[i].present) { + gamepadsvc->RemoveGamepad(mGamepads[i].id); + mGamepads.RemoveElementAt(i); + } + } +} + +// static +void +WindowsGamepadService::XInputPollTimerCallback(nsITimer* aTimer, + void* aClosure) +{ + WindowsGamepadService* self = + reinterpret_cast(aClosure); + self->PollXInput(); +} + +void +WindowsGamepadService::PollXInput() +{ + for (unsigned int i = 0; i < mGamepads.Length(); i++) { + if (mGamepads[i].type != kXInputGamepad) { + continue; + } + + XINPUT_STATE state = {}; + DWORD res = mXInput.mXInputGetState(mGamepads[i].userIndex, &state); + if (res == ERROR_SUCCESS + && state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) { + CheckXInputChanges(mGamepads[i], state); + } + } +} + +void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad, + XINPUT_STATE& state) { + nsRefPtr gamepadsvc(GamepadService::GetService()); + // Handle digital buttons first + for (size_t b = 0; b < kNumMappings; b++) { + if (state.Gamepad.wButtons & kXIButtonMap[b].button && + !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) { + // Button pressed + gamepadsvc->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true); + } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) && + gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) { + // Button released + gamepadsvc->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false); + } + } + + // Then triggers + if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) { + bool pressed = + state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + gamepadsvc->NewButtonEvent(gamepad.id, kButtonLeftTrigger, + pressed, state.Gamepad.bLeftTrigger / 255.0); + } + if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) { + bool pressed = + state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; + gamepadsvc->NewButtonEvent(gamepad.id, kButtonRightTrigger, + pressed, state.Gamepad.bRightTrigger / 255.0); + } + + // Finally deal with analog sticks + // TODO: bug 1001955 - Support deadzones. + if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) { + gamepadsvc->NewAxisMoveEvent(gamepad.id, kLeftStickXAxis, + state.Gamepad.sThumbLX / 32767.0); + } + if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) { + gamepadsvc->NewAxisMoveEvent(gamepad.id, kLeftStickYAxis, + -1.0 * state.Gamepad.sThumbLY / 32767.0); + } + if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) { + gamepadsvc->NewAxisMoveEvent(gamepad.id, kRightStickXAxis, + state.Gamepad.sThumbRX / 32767.0); + } + if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) { + gamepadsvc->NewAxisMoveEvent(gamepad.id, kRightStickYAxis, + -1.0 * state.Gamepad.sThumbRY / 32767.0); + } + gamepad.state = state; +} + +// Used to sort a list of axes by HID usage. +class HidValueComparator { +public: + bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const + { + return c1.UsagePage == c2.UsagePage && c1.Range.UsageMin == c2.Range.UsageMin; + } + bool LessThan(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const + { + if (c1.UsagePage == c2.UsagePage) { + return c1.Range.UsageMin < c2.Range.UsageMin; + } + return c1.UsagePage < c2.UsagePage; + } +}; + +bool +WindowsGamepadService::GetRawGamepad(HANDLE handle) +{ + if (!mHID) { + return false; + } + + for (unsigned i = 0; i < mGamepads.Length(); i++) { + if (mGamepads[i].type == kRawInputGamepad && mGamepads[i].handle == handle) { + mGamepads[i].present = true; + return true; + } + } + + RID_DEVICE_INFO rdi = {}; + UINT size = rdi.cbSize = sizeof(RID_DEVICE_INFO); + if (GetRawInputDeviceInfo(handle, RIDI_DEVICEINFO, &rdi, &size) < 0) { + return false; + } + // Ensure that this is a device we care about + if (!SupportedUsage(rdi.hid.usUsagePage, rdi.hid.usUsage)) { + return false; + } + + Gamepad gamepad = {}; + + // Device name is a mostly-opaque string. + if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, nullptr, &size) < 0) { + return false; + } + + nsTArray devname(size); + devname.SetLength(size); + if (GetRawInputDeviceInfo(handle, RIDI_DEVICENAME, devname.Elements(), &size) + <= 0) { + return false; + } + + // Per http://msdn.microsoft.com/en-us/library/windows/desktop/ee417014.aspx + // device names containing "IG_" are XInput controllers. Ignore those + // devices since we'll handle them with XInput. + if (wcsstr(devname.Elements(), L"IG_")) { + return false; + } + + // Product string is a human-readable name. + // Per http://msdn.microsoft.com/en-us/library/windows/hardware/ff539681%28v=vs.85%29.aspx + // "For USB devices, the maximum string length is 126 wide characters (not including the terminating NULL character)." + wchar_t name[128] = { 0 }; + size = sizeof(name); + nsTArray gamepad_name; + HANDLE hid_handle = CreateFile(devname.Elements(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL); + if (hid_handle) { + if (mHID.mHidD_GetProductString(hid_handle, &name, size)) { + int bytes = WideCharToMultiByte(CP_UTF8, 0, name, -1, nullptr, 0, nullptr, + nullptr); + gamepad_name.SetLength(bytes); + WideCharToMultiByte(CP_UTF8, 0, name, -1, gamepad_name.Elements(), + bytes, nullptr, nullptr); + } + CloseHandle(hid_handle); + } + if (gamepad_name.Length() == 0 || !gamepad_name[0]) { + const char kUnknown[] = "Unknown Gamepad"; + gamepad_name.SetLength(ArrayLength(kUnknown)); + strcpy_s(gamepad_name.Elements(), gamepad_name.Length(), kUnknown); + } + + char gamepad_id[256] = { 0 }; + _snprintf_s(gamepad_id, _TRUNCATE, "%04x-%04x-%s", rdi.hid.dwVendorId, + rdi.hid.dwProductId, gamepad_name.Elements()); + + nsTArray preparsedbytes; + if (!GetPreparsedData(handle, preparsedbytes)) { + return false; + } + + PHIDP_PREPARSED_DATA parsed = + reinterpret_cast(preparsedbytes.Elements()); + HIDP_CAPS caps; + if (mHID.mHidP_GetCaps(parsed, &caps) != HIDP_STATUS_SUCCESS) { + return false; + } + + // Enumerate buttons. + USHORT count = caps.NumberInputButtonCaps; + nsTArray buttonCaps(count); + buttonCaps.SetLength(count); + if (mHID.mHidP_GetButtonCaps(HidP_Input, buttonCaps.Elements(), &count, parsed) + != HIDP_STATUS_SUCCESS) { + return false; + } + for (unsigned i = 0; i < count; i++) { + // Each buttonCaps is typically a range of buttons. + gamepad.numButtons += + buttonCaps[i].Range.UsageMax - buttonCaps[i].Range.UsageMin + 1; + } + gamepad.numButtons = std::min(gamepad.numButtons, kMaxButtons); + + // Enumerate value caps, which represent axes and d-pads. + count = caps.NumberInputValueCaps; + nsTArray valueCaps(count); + valueCaps.SetLength(count); + if (mHID.mHidP_GetValueCaps(HidP_Input, valueCaps.Elements(), &count, parsed) + != HIDP_STATUS_SUCCESS) { + return false; + } + nsTArray axes; + // Sort the axes by usagePage and usage to expose a consistent ordering. + HidValueComparator comparator; + for (unsigned i = 0; i < count; i++) { + if (valueCaps[i].UsagePage == kDesktopUsagePage + && valueCaps[i].Range.UsageMin == kUsageDpad + // Don't know how to handle d-pads that return weird values. + && valueCaps[i].LogicalMax - valueCaps[i].LogicalMin == 7 + // Can't overflow buttons + && gamepad.numButtons + 4 < kMaxButtons) { + // d-pad gets special handling. + // Ostensibly HID devices can expose multiple d-pads, but this + // doesn't happen in practice. + gamepad.hasDpad = true; + gamepad.dpadCaps = valueCaps[i]; + // Expose d-pad as 4 additional buttons. + gamepad.numButtons += 4; + } else { + axes.InsertElementSorted(valueCaps[i], comparator); + } + } + + gamepad.numAxes = std::min(axes.Length(), kMaxAxes); + for (unsigned i = 0; i < gamepad.numAxes; i++) { + if (i >= kMaxAxes) { + break; + } + gamepad.axes[i].caps = axes[i]; + } + gamepad.type = kRawInputGamepad; + gamepad.handle = handle; + gamepad.present = true; + + nsRefPtr gamepadsvc(GamepadService::GetService()); + if (!gamepadsvc) { + return false; + } + + gamepad.id = gamepadsvc->AddGamepad(gamepad_id, + NoMapping, + gamepad.numButtons, + gamepad.numAxes); + mGamepads.AppendElement(gamepad); + return true; +} + +bool +WindowsGamepadService::HandleRawInput(HRAWINPUT handle) +{ + if (!mHID) { + return false; + } + nsRefPtr gamepadsvc(GamepadService::GetService()); + if (!gamepadsvc) { + return false; + } + + // First, get data from the handle + UINT size; + GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER)); + nsTArray data(size); + data.SetLength(size); + if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size, + sizeof(RAWINPUTHEADER)) < 0) { + return false; + } + PRAWINPUT raw = reinterpret_cast(data.Elements()); + + Gamepad* gamepad = nullptr; + for (unsigned i = 0; i < mGamepads.Length(); i++) { + if (mGamepads[i].type == kRawInputGamepad + && mGamepads[i].handle == raw->header.hDevice) { + gamepad = &mGamepads[i]; + break; + } + } + if (gamepad == nullptr) { + return false; + } + + // Second, get the preparsed data + nsTArray parsedbytes; + if (!GetPreparsedData(raw->header.hDevice, parsedbytes)) { + return false; + } + PHIDP_PREPARSED_DATA parsed = + reinterpret_cast(parsedbytes.Elements()); + + // Get all the pressed buttons. + nsTArray usages(gamepad->numButtons); + usages.SetLength(gamepad->numButtons); + ULONG usageLength = gamepad->numButtons; + if (mHID.mHidP_GetUsages(HidP_Input, kButtonUsagePage, 0, usages.Elements(), + &usageLength, parsed, (PCHAR)raw->data.hid.bRawData, + raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { + return false; + } + + bool buttons[kMaxButtons] = { false }; + usageLength = std::min(usageLength, kMaxButtons); + for (unsigned i = 0; i < usageLength; i++) { + buttons[usages[i] - 1] = true; + } + + if (gamepad->hasDpad) { + // Get d-pad position as 4 buttons. + ULONG value; + if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) { + UnpackDpad(static_cast(value), gamepad, buttons); + } + } + + for (unsigned i = 0; i < gamepad->numButtons; i++) { + if (gamepad->buttons[i] != buttons[i]) { + gamepadsvc->NewButtonEvent(gamepad->id, i, buttons[i]); + gamepad->buttons[i] = buttons[i]; + } + } + + // Get all axis values. + for (unsigned i = 0; i < gamepad->numAxes; i++) { + double new_value; + if (gamepad->axes[i].caps.LogicalMin < 0) { +LONG value; + if (mHID.mHidP_GetScaledUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, + 0, gamepad->axes[i].caps.Range.UsageMin, + &value, parsed, + (PCHAR)raw->data.hid.bRawData, + raw->data.hid.dwSizeHid) + != HIDP_STATUS_SUCCESS) { + continue; + } + new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin, + gamepad->axes[i].caps.LogicalMax); + } + else { + ULONG value; + if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0, + gamepad->axes[i].caps.Range.UsageMin, &value, + parsed, (PCHAR)raw->data.hid.bRawData, + raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) { continue; } - // first query for the number of items in the buffer - DWORD items = INFINITE; - nsRefPtr device = self->mGamepads[i].device; - if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), - nullptr, - &items, - DIGDD_PEEK)== DI_OK) { - while (items > 0) { - // now read each buffered event - //TODO: read more than one event at a time - DIDEVICEOBJECTDATA data; - DWORD readCount = sizeof(data) / sizeof(DIDEVICEOBJECTDATA); - if (device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), - &data, &readCount, 0) == DI_OK) { - //TODO: data.dwTimeStamp - GamepadEvent::Type type = GamepadEvent::Unknown; - int which; - if (data.dwOfs >= DIJOFS_BUTTON0 && data.dwOfs < DIJOFS_BUTTON(32)) { - type = GamepadEvent::Button; - which = data.dwOfs - DIJOFS_BUTTON0; - } - else if(data.dwOfs >= DIJOFS_X && data.dwOfs < DIJOFS_SLIDER(2)) { - // axis/slider - type = GamepadEvent::Axis; - which = (data.dwOfs - DIJOFS_X) / sizeof(LONG); - } - else if (data.dwOfs >= DIJOFS_POV(0) && data.dwOfs < DIJOFS_POV(4)) { - HatState hatState; - HatPosToAxes(data.dwData, hatState); - which = (data.dwOfs - DIJOFS_POV(0)) / sizeof(DWORD); - // Only send out axis move events for the axes that moved - // in this hat move. - if (hatState.x != self->mGamepads[i].hatState[which].x) { - type = GamepadEvent::HatX; - } - if (hatState.y != self->mGamepads[i].hatState[which].y) { - if (type == GamepadEvent::HatX) { - type = GamepadEvent::HatXY; - } - else { - type = GamepadEvent::HatY; - } - } - self->mGamepads[i].hatState[which].x = hatState.x; - self->mGamepads[i].hatState[which].y = hatState.y; - } - - if (type != GamepadEvent::Unknown) { - nsRefPtr event = - new GamepadEvent(self->mGamepads[i], type, which, data.dwData); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - } - } - items--; - } - } + new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin, + gamepad->axes[i].caps.LogicalMax); + } + if (gamepad->axes[i].value != new_value) { + gamepadsvc->NewAxisMoveEvent(gamepad->id, i, new_value); + gamepad->axes[i].value = new_value; } } - return 0; + + return true; } void -WindowsGamepadService::Startup() { - mThread = CreateThread(nullptr, - 0, - DInputThread, - this, - 0, - nullptr); +WindowsGamepadService::Startup() +{ + ScanForDevices(); } void -WindowsGamepadService::Shutdown() { - if (mThread) { - SetEvent(mThreadExitEvent); - WaitForSingleObject(mThread, INFINITE); - CloseHandle(mThread); - } +WindowsGamepadService::Shutdown() +{ Cleanup(); } -// This method is called from the main thread. void -WindowsGamepadService::SetGamepadID(int localID, int globalID) { - MutexAutoLock lock(mMutex); - mGamepads[localID].globalID = globalID; -} - -// This method is called from the main thread. -void WindowsGamepadService::RemoveGamepad(int localID) { - MutexAutoLock lock(mMutex); - mGamepads[localID].remove = true; - // Signal background thread to remove device. - DevicesChanged(DeviceChangeStable); -} - -void -WindowsGamepadService::Cleanup() { - for (unsigned int i = 0; i < mGamepads.Length(); i++) { - CleanupGamepad(mGamepads[i]); +WindowsGamepadService::Cleanup() +{ + if (mXInputPollTimer) { + mXInputPollTimer->Cancel(); } mGamepads.Clear(); } void -WindowsGamepadService::CleanupGamepad(Gamepad& gamepad) { - gamepad.device->Unacquire(); - gamepad.device->SetEventNotification(nullptr); - CloseHandle(gamepad.event); -} - -void -WindowsGamepadService::DevicesChanged(DeviceChangeType type) { +WindowsGamepadService::DevicesChanged(DeviceChangeType type) +{ if (type == DeviceChangeNotification) { mObserver->SetDeviceChangeTimer(); } else if (type == DeviceChangeStable) { - SetEvent(mThreadRescanEvent); + ScanForDevices(); } } NS_IMETHODIMP Observer::Observe(nsISupports* aSubject, const char* aTopic, - const char16_t* aData) { + const char16_t* aData) +{ if (strcmp(aTopic, "timer-callback") == 0) { mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable); } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) { @@ -696,20 +925,50 @@ Observer::Observe(nsISupports* aSubject, HWND sHWnd = nullptr; +bool +RegisterRawInput(HWND hwnd, bool enable) +{ + nsTArray rid(ArrayLength(kUsagePages)); + rid.SetLength(ArrayLength(kUsagePages)); + + for (unsigned i = 0; i < rid.Length(); i++) { + rid[i].usUsagePage = kUsagePages[i].usagePage; + rid[i].usUsage = kUsagePages[i].usage; + rid[i].dwFlags = + enable ? RIDEV_EXINPUTSINK | RIDEV_DEVNOTIFY : RIDEV_REMOVE; + rid[i].hwndTarget = hwnd; + } + + if (!RegisterRawInputDevices(rid.Elements(), rid.Length(), + sizeof(RAWINPUTDEVICE))) { + return false; + } + return true; +} + static LRESULT CALLBACK -GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { +GamepadWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ const unsigned int DBT_DEVICEARRIVAL = 0x8000; const unsigned int DBT_DEVICEREMOVECOMPLETE = 0x8004; const unsigned int DBT_DEVNODES_CHANGED = 0x7; - if (msg == WM_DEVICECHANGE && - (wParam == DBT_DEVICEARRIVAL || - wParam == DBT_DEVICEREMOVECOMPLETE || - wParam == DBT_DEVNODES_CHANGED)) { - if (gService) { - gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification); + switch (msg) { + case WM_DEVICECHANGE: + if (wParam == DBT_DEVICEARRIVAL || + wParam == DBT_DEVICEREMOVECOMPLETE || + wParam == DBT_DEVNODES_CHANGED) { + if (gService) { + gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification); + } } + break; + case WM_INPUT: + if (gService) { + gService->HandleRawInput(reinterpret_cast(lParam)); + } + break; } return DefWindowProc(hwnd, msg, wParam, lParam); } @@ -721,8 +980,9 @@ namespace hal_impl { void StartMonitoringGamepadStatus() { - if (gService) + if (gService) { return; + } gService = new WindowsGamepadService(); gService->Startup(); @@ -742,15 +1002,18 @@ void StartMonitoringGamepadStatus() sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher", 0, 0, 0, 0, 0, nullptr, nullptr, hSelf, nullptr); + RegisterRawInput(sHWnd, true); } } void StopMonitoringGamepadStatus() { - if (!gService) + if (!gService) { return; + } if (sHWnd) { + RegisterRawInput(sHWnd, false); DestroyWindow(sHWnd); sHWnd = nullptr; } diff --git a/js/ipc/JavaScriptChild.cpp b/js/ipc/JavaScriptChild.cpp index 6e65fed5453b..a7b8e9b0f216 100644 --- a/js/ipc/JavaScriptChild.cpp +++ b/js/ipc/JavaScriptChild.cpp @@ -501,7 +501,7 @@ JavaScriptChild::AnswerCall(const ObjectId &objId, const nsTArray &argv ContextOptionsRef(cx).setDontReportUncaught(true); HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2); - bool success = JS::Call(cx, vals.handleAt(1), vals.handleAt(0), args, &rval); + bool success = JS::Call(cx, vals[1], vals[0], args, &rval); if (!success) return fail(cx, rs); } @@ -536,7 +536,7 @@ JavaScriptChild::AnswerCall(const ObjectId &objId, const nsTArray &argv // treat this as the outparam never having been set. for (size_t i = 0; i < vals.length(); i++) { JSVariant variant; - if (!toVariant(cx, vals.handleAt(i), &variant)) + if (!toVariant(cx, vals[i], &variant)) return fail(cx, rs); outparams->ReplaceElementAt(i, JSParam(variant)); } @@ -596,7 +596,7 @@ JavaScriptChild::AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &f for (size_t i = 0; i < props.length(); i++) { nsString name; - if (!convertIdToGeckoString(cx, props.handleAt(i), &name)) + if (!convertIdToGeckoString(cx, props[i], &name)) return fail(cx, rs); names->AppendElement(name); diff --git a/js/public/Value.h b/js/public/Value.h index a84f54186e6b..b7fa420b6469 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -236,7 +236,7 @@ typedef enum JSWhyMagic JS_BLOCK_NEEDS_CLONE, /* value of static block object slot */ JS_HASH_KEY_EMPTY, /* see class js::HashableValue */ JS_ION_ERROR, /* error while running Ion code */ - JS_ION_BAILOUT, /* status code to signal EnterIon will OSR into Interpret */ + JS_ION_BAILOUT, /* missing recover instruction result */ JS_OPTIMIZED_OUT, /* optimized out slot */ JS_GENERIC_MAGIC /* for local use */ } JSWhyMagic; diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index bd76bcfee5c9..ee33dfecc599 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -930,7 +930,7 @@ StructMetaTypeDescr::create(JSContext *cx, // userFieldTypes[id] = typeObj if (!JSObject::defineGeneric(cx, userFieldTypes, id, - fieldTypeObjs.handleAt(i), nullptr, nullptr, + fieldTypeObjs[i], nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) return nullptr; diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp index 2761e2385785..8c90470a630e 100644 --- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -1163,27 +1163,27 @@ InitTypeClasses(JSContext* cx, HandleObject parent) if (!InitTypeConstructor(cx, parent, CTypeProto, CDataProto, sPointerFunction, nullptr, sPointerProps, sPointerInstanceFunctions, sPointerInstanceProps, - protos.handleAt(SLOT_POINTERPROTO), protos.handleAt(SLOT_POINTERDATAPROTO))) + protos[SLOT_POINTERPROTO], protos[SLOT_POINTERDATAPROTO])) return false; if (!InitTypeConstructor(cx, parent, CTypeProto, CDataProto, sArrayFunction, nullptr, sArrayProps, sArrayInstanceFunctions, sArrayInstanceProps, - protos.handleAt(SLOT_ARRAYPROTO), protos.handleAt(SLOT_ARRAYDATAPROTO))) + protos[SLOT_ARRAYPROTO], protos[SLOT_ARRAYDATAPROTO])) return false; if (!InitTypeConstructor(cx, parent, CTypeProto, CDataProto, sStructFunction, sStructFunctions, sStructProps, sStructInstanceFunctions, nullptr, - protos.handleAt(SLOT_STRUCTPROTO), protos.handleAt(SLOT_STRUCTDATAPROTO))) + protos[SLOT_STRUCTPROTO], protos[SLOT_STRUCTDATAPROTO])) return false; - if (!InitTypeConstructor(cx, parent, CTypeProto, protos.handleAt(SLOT_POINTERDATAPROTO), + if (!InitTypeConstructor(cx, parent, CTypeProto, protos[SLOT_POINTERDATAPROTO], sFunctionFunction, nullptr, sFunctionProps, sFunctionInstanceFunctions, nullptr, - protos.handleAt(SLOT_FUNCTIONPROTO), protos.handleAt(SLOT_FUNCTIONDATAPROTO))) + protos[SLOT_FUNCTIONPROTO], protos[SLOT_FUNCTIONDATAPROTO])) return false; - protos[SLOT_CDATAPROTO] = CDataProto; + protos[SLOT_CDATAPROTO].set(CDataProto); // Create and attach the ctypes.{Int64,UInt64} constructors. // Each of these has, respectively: @@ -1193,18 +1193,18 @@ InitTypeClasses(JSContext* cx, HandleObject parent) // * 'prototype' property: // * [[Class]] {"Int64Proto","UInt64Proto"} // * 'constructor' property === ctypes.{Int64,UInt64} - protos[SLOT_INT64PROTO] = InitInt64Class(cx, parent, &sInt64ProtoClass, - Int64::Construct, sInt64Functions, sInt64StaticFunctions); + protos[SLOT_INT64PROTO].set(InitInt64Class(cx, parent, &sInt64ProtoClass, + Int64::Construct, sInt64Functions, sInt64StaticFunctions)); if (!protos[SLOT_INT64PROTO]) return false; - protos[SLOT_UINT64PROTO] = InitInt64Class(cx, parent, &sUInt64ProtoClass, - UInt64::Construct, sUInt64Functions, sUInt64StaticFunctions); + protos[SLOT_UINT64PROTO].set(InitInt64Class(cx, parent, &sUInt64ProtoClass, + UInt64::Construct, sUInt64Functions, sUInt64StaticFunctions)); if (!protos[SLOT_UINT64PROTO]) return false; // Finally, store a pointer to the global ctypes object. // Note that there is no other reliable manner of locating this object. - protos[SLOT_CTYPES] = parent; + protos[SLOT_CTYPES].set(parent); // Attach the prototypes just created to each of ctypes.CType.prototype, // and the special type constructors, so we can access them when constructing @@ -4827,7 +4827,7 @@ StructType::DefineInternal(JSContext* cx, JSObject* typeObj_, JSObject* fieldsOb Rooted name(cx, ExtractStructField(cx, item, fieldType.address())); if (!name) return false; - fieldRoots[i] = JS::ObjectValue(*fieldType); + fieldRoots[i].setObject(*fieldType); // Make sure each field name is unique FieldInfoHash::AddPtr entryPtr = fields->lookupForAdd(name); @@ -5135,7 +5135,7 @@ StructType::BuildFieldsArray(JSContext* cx, JSObject* obj) for (FieldInfoHash::Range r = fields->all(); !r.empty(); r.popFront()) { const FieldInfoHash::Entry& entry = r.front(); // Add the field descriptor to the array. - if (!AddFieldToArray(cx, &fieldsVec[entry.value().mIndex], + if (!AddFieldToArray(cx, fieldsVec[entry.value().mIndex].address(), entry.key(), entry.value().mType)) return nullptr; } @@ -5620,7 +5620,7 @@ FunctionType::Create(JSContext* cx, unsigned argc, jsval* vp) // Pull out the argument types from the array, if any. JS_ASSERT_IF(argTypes.length(), arrayObj); for (uint32_t i = 0; i < argTypes.length(); ++i) { - if (!JS_GetElement(cx, arrayObj, i, argTypes.handleAt(i))) + if (!JS_GetElement(cx, arrayObj, i, argTypes[i])) return false; } @@ -5946,7 +5946,7 @@ FunctionType::ArgTypesGetter(JSContext* cx, JS::CallArgs args) return false; for (size_t i = 0; i < len; ++i) - vec[i] = JS::ObjectValue(*fninfo->mArgTypes[i]); + vec[i].setObject(*fninfo->mArgTypes[i]); argTypes = JS_NewArrayObject(cx, vec); if (!argTypes) @@ -6182,7 +6182,7 @@ CClosure::ClosureStub(ffi_cif* cif, void* result, void** args, void* userData) // Convert each argument, and have any CData objects created depend on // the existing buffers. RootedObject argType(cx, fninfo->mArgTypes[i]); - if (!ConvertToJS(cx, argType, NullPtr(), args[i], false, false, &argv[i])) + if (!ConvertToJS(cx, argType, NullPtr(), args[i], false, false, argv[i].address())) return; } diff --git a/js/src/jit-test/tests/ion/dce-with-rinstructions.js b/js/src/jit-test/tests/ion/dce-with-rinstructions.js new file mode 100644 index 000000000000..fbd9b0ab3711 --- /dev/null +++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js @@ -0,0 +1,72 @@ +setJitCompilerOption("baseline.usecount.trigger", 10); +setJitCompilerOption("ion.usecount.trigger", 20); +var i; + +// Check that we are able to remove the addition inside "ra" functions, when we +// inline the first version of uceFault, and ensure that the bailout is correct +// when uceFault is replaced (which cause an invalidation bailout) + +var uceFault = function (i) { + if (i > 98) + uceFault = function (i) { return true; }; + return false; +} + +var uceFault_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_number')); +function ra_number(i) { + var x = 1 + i; + if (uceFault_number(i) || uceFault_number(i)) + assertEq(x, 100 /* = 1 + 99 */); + return i; +} + +var uceFault_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_float')); +function ra_float(i) { + var t = Math.fround(1/3); + var fi = Math.fround(i); + var x = Math.fround(Math.fround(Math.fround(Math.fround(t + fi) + t) + fi) + t); + if (uceFault_float(i) || uceFault_float(i)) + assertEq(x, 199); /* != 199.00000002980232 (when computed with double additions) */ + return i; +} + +var uceFault_string = eval(uneval(uceFault).replace('uceFault', 'uceFault_string')); +function ra_string(i) { + var x = "s" + i; + if (uceFault_string(i) || uceFault_string(i)) + assertEq(x, "s99"); + return i; +} + +var uceFault_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_object')); +function ra_object(i) { + var x = {} + i; + if (uceFault_object(i) || uceFault_object(i)) + assertEq(x, "[object Object]99"); + return i; +} + +for (i = 0; i < 100; i++) { + ra_number(i); + ra_float(i); + ra_string(i); + ra_object(i); +} + +// Test that we can refer multiple time to the same recover instruction, as well +// as chaining recover instructions. + +function alignedAlloc($size, $alignment) { + var $1 = $size + 4 | 0; + var $2 = $alignment - 1 | 0; + var $3 = $1 + $2 | 0; + var $4 = malloc($3); +} + +function malloc($bytes) { + var $189 = undefined; + var $198 = $189 + 8 | 0; +} + +for (i = 0; i < 50; i++) + alignedAlloc(608, 16); diff --git a/js/src/jit/AsmJSLink.cpp b/js/src/jit/AsmJSLink.cpp index b07808d1a569..f4dd71ea986b 100644 --- a/js/src/jit/AsmJSLink.cpp +++ b/js/src/jit/AsmJSLink.cpp @@ -239,7 +239,7 @@ ValidateFFI(JSContext *cx, AsmJSModule::Global &global, HandleValue importVal, if (!v.isObject() || !v.toObject().is()) return LinkFail(cx, "FFI imports must be functions"); - (*ffis)[global.ffiIndex()] = &v.toObject().as(); + (*ffis)[global.ffiIndex()].set(&v.toObject().as()); return true; } diff --git a/js/src/jit/Bailouts.cpp b/js/src/jit/Bailouts.cpp index b54dfd58f9b9..9231d8dda93e 100644 --- a/js/src/jit/Bailouts.cpp +++ b/js/src/jit/Bailouts.cpp @@ -47,7 +47,8 @@ SnapshotIterator::SnapshotIterator(const IonBailoutIterator &iter) iter.ionScript()->recoversSize()), fp_(iter.jsFrame()), machine_(iter.machineState()), - ionScript_(iter.ionScript()) + ionScript_(iter.ionScript()), + instructionResults_(nullptr) { } diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index e17f370fca7d..dbf48f867418 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -558,9 +558,6 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, if (callerPC == nullptr) { IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on top frame!"); flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME; - } else if (js_JitOptions.profileInlineFrames) { - IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on inline frame!"); - flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME; } } @@ -674,7 +671,7 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, size_t argOffset = builder.framePushed() + IonJSFrameLayout::offsetOfActualArg(i); *builder.valuePointerAtStackOffset(argOffset) = arg; } else { - startFrameFormals[i] = arg; + startFrameFormals[i].set(arg); } } } @@ -758,7 +755,7 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, if (!savedCallerArgs.resize(inlined_args)) return false; for (uint32_t i = 0; i < inlined_args; i++) - savedCallerArgs[i] = iter.read(); + savedCallerArgs[i].set(iter.read()); if (IsSetPropPC(pc)) { // We would love to just save all the arguments and leave them @@ -988,55 +985,24 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, blFrame->unsetPushedSPSFrame(); if (cx->runtime()->spsProfiler.enabled()) { - if (js_JitOptions.profileInlineFrames) { - // If SPS is enabled, there are two corner cases to handle: - // 1. If resuming into the prologue, and innermost frame is an inlined - // frame, and bailout is because of argument check failure, then: - // Top SPS profiler entry would be for caller frame. - // Ion would not have set the PC index field on that frame - // (since this bailout happens before MFunctionBoundary). - // Make sure that's done now. - // 2. If resuming into the prologue, and the bailout is NOT because of an - // argument check, then: - // Top SPS profiler entry would be for callee frame. - // Ion would already have pushed an SPS entry for this frame. - // The pc for this entry would be set to nullptr. - // Make sure it's set to script->pc. - if (caller && bailoutKind == Bailout_ArgumentCheck) { - IonSpew(IonSpew_BaselineBailouts, " Setting PCidx on innermost " - "inlined frame's parent's SPS entry (%s:%d) (pcIdx=%d)!", - caller->filename(), caller->lineno(), - caller->pcToOffset(callerPC)); - cx->runtime()->spsProfiler.updatePC(caller, callerPC); - - } else if (bailoutKind != Bailout_ArgumentCheck) { - IonSpew(IonSpew_BaselineBailouts, - " Popping SPS entry for innermost inlined frame"); - cx->runtime()->spsProfiler.exit(script, fun); - } - - } else { - // If not profiling inline frames, then this is logically simpler. - // - // 1. If resuming into inline code, then the top SPS entry will be - // for the outermost caller, and will have an uninitialized PC. - // This will be fixed up later in BailoutIonToBaseline. - // - // 2. If resuming into top-level code prologue, with ArgumentCheck, - // no SPS entry will have been pushed. Can be left alone. - // - // 3. If resuming into top-level code prologue, without ArgumentCheck, - // an SPS entry will have been pushed, and needs to be popped. - // - // 4. If resuming into top-level code main body, an SPS entry will - // have been pushed, and can be left alone. - // - // Only need to handle case 3 here. - if (!caller && bailoutKind != Bailout_ArgumentCheck) { - IonSpew(IonSpew_BaselineBailouts, - " Popping SPS entry for outermost frame"); - cx->runtime()->spsProfiler.exit(script, fun); - } + // 1. If resuming into inline code, then the top SPS entry will be + // for the outermost caller, and will have an uninitialized PC. + // This will be fixed up later in BailoutIonToBaseline. + // + // 2. If resuming into top-level code prologue, with ArgumentCheck, + // no SPS entry will have been pushed. Can be left alone. + // + // 3. If resuming into top-level code prologue, without ArgumentCheck, + // an SPS entry will have been pushed, and needs to be popped. + // + // 4. If resuming into top-level code main body, an SPS entry will + // have been pushed, and can be left alone. + // + // Only need to handle case 3 here. + if (!caller && bailoutKind != Bailout_ArgumentCheck) { + IonSpew(IonSpew_BaselineBailouts, + " Popping SPS entry for outermost frame"); + cx->runtime()->spsProfiler.exit(script, fun); } } } else { @@ -1370,8 +1336,12 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt return BAILOUT_RETURN_FATAL_ERROR; IonSpew(IonSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame()); + AutoValueVector instructionResults(cx); SnapshotIterator snapIter(iter); + if (!snapIter.initIntructionResults(instructionResults)) + return BAILOUT_RETURN_FATAL_ERROR; + RootedFunction callee(cx, iter.maybeCallee()); RootedScript scr(cx, iter.script()); if (callee) { @@ -1399,7 +1369,12 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt jsbytecode *topCallerPC = nullptr; while (true) { - MOZ_ASSERT(snapIter.instruction()->isResumePoint()); + if (!snapIter.instruction()->isResumePoint()) { + if (!snapIter.instruction()->recover(cx, snapIter)) + return BAILOUT_RETURN_FATAL_ERROR; + snapIter.nextInstruction(); + continue; + } if (frameNo > 0) { TraceLogStartEvent(logger, TraceLogCreateTextId(logger, scr)); @@ -1453,10 +1428,9 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, IonBailoutIt } IonSpew(IonSpew_BaselineBailouts, " Done restoring frames"); - // If there were multiple inline frames unpacked, and inline frame profiling - // is off, then the current top SPS frame is for the outermost caller, and - // has an uninitialized PC. Initialize it now. - if (frameNo > 0 && !js_JitOptions.profileInlineFrames) + // If there were multiple inline frames unpacked, then the current top SPS frame + // is for the outermost caller, and has an uninitialized PC. Initialize it now. + if (frameNo > 0) cx->runtime()->spsProfiler.updatePC(topCaller, topCallerPC); BailoutKind bailoutKind = snapIter.bailoutKind(); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index a552722b588d..96c76a80d086 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -2342,6 +2342,58 @@ BaselineCompiler::emit_JSOP_INITELEM_SETTER() return emitInitElemGetterSetter(); } +bool +BaselineCompiler::emit_JSOP_INITELEM_INC() +{ + // Keep the object and rhs on the stack. + frame.syncStack(0); + + // Load object in R0, index in R1. + masm.loadValue(frame.addressOfStackValue(frame.peek(-3)), R0); + masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1); + + // Call IC. + ICSetElem_Fallback::Compiler stubCompiler(cx); + if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) + return false; + + // Pop the rhs + frame.pop(); + + // Increment index + Address indexAddr = frame.addressOfStackValue(frame.peek(-1)); + masm.incrementInt32Value(indexAddr); + return true; +} + +typedef bool (*SpreadFn)(JSContext *, HandleObject, HandleValue, + HandleValue, MutableHandleValue); +static const VMFunction SpreadInfo = FunctionInfo(js::SpreadOperation); + +bool +BaselineCompiler::emit_JSOP_SPREAD() +{ + // Load index and iterable in R0 and R1, but keep values on the stack for + // the decompiler. + frame.syncStack(0); + masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); + masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); + + prepareVMCall(); + + pushArg(R1); + pushArg(R0); + masm.extractObject(frame.addressOfStackValue(frame.peek(-3)), R0.scratchReg()); + pushArg(R0.scratchReg()); + + if (!callVM(SpreadInfo)) + return false; + + frame.popn(2); + frame.push(R0); + return true; +} + bool BaselineCompiler::emit_JSOP_GETLOCAL() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 03dc9640a9a1..e72155f0cfbc 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -97,6 +97,8 @@ namespace jit { _(JSOP_INITELEM) \ _(JSOP_INITELEM_GETTER) \ _(JSOP_INITELEM_SETTER) \ + _(JSOP_INITELEM_INC) \ + _(JSOP_SPREAD) \ _(JSOP_MUTATEPROTO) \ _(JSOP_INITPROP) \ _(JSOP_INITPROP_GETTER) \ diff --git a/js/src/jit/BaselineFrame.cpp b/js/src/jit/BaselineFrame.cpp index b0fd64d7fa75..e5e40943d659 100644 --- a/js/src/jit/BaselineFrame.cpp +++ b/js/src/jit/BaselineFrame.cpp @@ -111,7 +111,7 @@ BaselineFrame::copyRawFrameSlots(AutoValueVector *vec) const mozilla::PodCopy(vec->begin(), argv(), nformals); for (unsigned i = 0; i < nfixed; i++) - (*vec)[nformals + i] = *valueSlot(i); + (*vec)[nformals + i].set(*valueSlot(i)); return true; } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 5a0315286269..48148319418f 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -5002,7 +5002,8 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub_ JS_ASSERT(op == JSOP_SETELEM || op == JSOP_INITELEM || - op == JSOP_INITELEM_ARRAY); + op == JSOP_INITELEM_ARRAY || + op == JSOP_INITELEM_INC); RootedObject obj(cx, ToObjectFromStack(cx, objv)); if (!obj) @@ -5025,6 +5026,9 @@ DoSetElemFallback(JSContext *cx, BaselineFrame *frame, ICSetElem_Fallback *stub_ JS_ASSERT(uint32_t(index.toInt32()) == GET_UINT24(pc)); if (!InitArrayElemOperation(cx, pc, obj, index.toInt32(), rhs)) return false; + } else if (op == JSOP_INITELEM_INC) { + if (!InitArrayElemOperation(cx, pc, obj, index.toInt32(), rhs)) + return false; } else { if (!SetObjectElement(cx, obj, index, rhs, script->strict(), script, pc)) return false; diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 91e88b3e517c..fa1fba2969ba 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -1728,7 +1728,7 @@ CodeGenerator::visitTypeBarrierV(LTypeBarrierV *lir) Register scratch = ToTempRegisterOrInvalid(lir->temp()); Label miss; - masm.guardTypeSet(operand, lir->mir()->resultTypeSet(), scratch, &miss); + masm.guardTypeSet(operand, lir->mir()->resultTypeSet(), lir->mir()->barrierKind(), scratch, &miss); if (!bailoutFrom(&miss, lir->snapshot())) return false; return true; @@ -1737,6 +1737,8 @@ CodeGenerator::visitTypeBarrierV(LTypeBarrierV *lir) bool CodeGenerator::visitTypeBarrierO(LTypeBarrierO *lir) { + MOZ_ASSERT(lir->mir()->barrierKind() != BarrierKind::TypeTagOnly); + Register obj = ToRegister(lir->object()); Register scratch = ToTempRegisterOrInvalid(lir->temp()); @@ -1754,7 +1756,7 @@ CodeGenerator::visitMonitorTypes(LMonitorTypes *lir) Register scratch = ToTempUnboxRegister(lir->temp()); Label matched, miss; - masm.guardTypeSet(operand, lir->mir()->typeSet(), scratch, &miss); + masm.guardTypeSet(operand, lir->mir()->typeSet(), lir->mir()->barrierKind(), scratch, &miss); if (!bailoutFrom(&miss, lir->snapshot())) return false; return true; @@ -2710,7 +2712,7 @@ CodeGenerator::generateArgumentsChecks(bool bailout) // ... * sizeof(Value) - Scale by value size. // ArgToStackOffset(...) - Compute displacement within arg vector. int32_t offset = ArgToStackOffset((i - info.startArgSlot()) * sizeof(Value)); - masm.guardTypeSet(Address(StackPointer, offset), types, temp, &miss); + masm.guardTypeSet(Address(StackPointer, offset), types, BarrierKind::TypeSet, temp, &miss); } if (miss.used()) { @@ -3186,7 +3188,7 @@ CodeGenerator::emitValueResultChecks(LInstruction *lir, MDefinition *mir) if (mir->resultTypeSet() && !mir->resultTypeSet()->unknown()) { // We have a result TypeSet, assert this value is in it. Label miss, ok; - masm.guardTypeSet(output, mir->resultTypeSet(), temp1, &miss); + masm.guardTypeSet(output, mir->resultTypeSet(), BarrierKind::TypeSet, temp1, &miss); masm.jump(&ok); masm.bind(&miss); @@ -8051,7 +8053,7 @@ CodeGenerator::visitProfilerStackOp(LProfilerStackOp *lir) case MProfilerStackOp::Enter: if (gen->options.spsSlowAssertionsEnabled()) { - if (!inlinedFunction || js_JitOptions.profileInlineFrames) { + if (!inlinedFunction) { saveLive(lir); pushArg(ImmGCPtr(lir->script())); if (!callVM(SPSEnterInfo, lir)) @@ -8074,7 +8076,7 @@ CodeGenerator::visitProfilerStackOp(LProfilerStackOp *lir) case MProfilerStackOp::Exit: if (gen->options.spsSlowAssertionsEnabled()) { - if (!inlinedFunction || js_JitOptions.profileInlineFrames) { + if (!inlinedFunction) { saveLive(lir); pushArg(ImmGCPtr(lir->script())); // Once we've exited, then we shouldn't emit instrumentation for diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 9c8a778447d2..485d0cad417f 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -171,6 +171,9 @@ jit::EliminateDeadCode(MIRGenerator *mir, MIRGraph &graph) !inst->hasUses() && !inst->isGuard() && !inst->isControlInstruction()) { inst = block->discardAt(inst); + } else if (!inst->hasLiveDefUses() && inst->canRecoverOnBailout()) { + inst->setRecoveredOnBailout(); + inst++; } else { inst++; } @@ -1382,11 +1385,6 @@ jit::AssertBasicGraphCoherency(MIRGraph &graph) for (size_t i = 0; i < block->numPredecessors(); i++) JS_ASSERT(CheckPredecessorImpliesSuccessor(*block, block->getPredecessor(i))); - // Assert that use chains are valid for this instruction. - for (MDefinitionIterator iter(*block); iter; iter++) { - for (uint32_t i = 0, e = iter->numOperands(); i < e; i++) - JS_ASSERT(CheckOperandImpliesUse(*iter, iter->getOperand(i))); - } for (MResumePointIterator iter(block->resumePointsBegin()); iter != block->resumePointsEnd(); iter++) { for (uint32_t i = 0, e = iter->numOperands(); i < e; i++) { if (iter->getUseFor(i)->hasProducer()) @@ -1395,11 +1393,16 @@ jit::AssertBasicGraphCoherency(MIRGraph &graph) } for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++) { JS_ASSERT(phi->numOperands() == block->numPredecessors()); + MOZ_ASSERT(!phi->isRecoveredOnBailout()); } for (MDefinitionIterator iter(*block); iter; iter++) { JS_ASSERT(iter->block() == *block); - for (MUseIterator i(iter->usesBegin()); i != iter->usesEnd(); i++) - JS_ASSERT(CheckUseImpliesOperand(*iter, *i)); + + // Assert that use chains are valid for this instruction. + for (uint32_t i = 0, end = iter->numOperands(); i < end; i++) + JS_ASSERT(CheckOperandImpliesUse(*iter, iter->getOperand(i))); + for (MUseIterator use(iter->usesBegin()); use != iter->usesEnd(); use++) + JS_ASSERT(CheckUseImpliesOperand(*iter, *use)); if (iter->isInstruction()) { if (MResumePoint *resume = iter->toInstruction()->resumePoint()) { @@ -1407,6 +1410,9 @@ jit::AssertBasicGraphCoherency(MIRGraph &graph) JS_ASSERT(ins->block() == iter->block()); } } + + if (iter->isRecoveredOnBailout()) + MOZ_ASSERT(!iter->hasLiveDefUses()); } } diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index d0c692c55208..1831ab8afb84 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -5049,7 +5049,7 @@ IonBuilder::jsop_funapplyarguments(uint32_t argc) return false; types::TemporaryTypeSet *types = bytecodeTypes(pc); - return pushTypeBarrier(apply, types, true); + return pushTypeBarrier(apply, types, BarrierKind::TypeSet); } // When inlining we have the arguments the function gets called with @@ -5378,7 +5378,7 @@ IonBuilder::makeCall(JSFunction *target, CallInfo &callInfo, bool cloneAtCallsit if (call->isCallDOMNative()) return pushDOMTypeBarrier(call, types, call->getSingleTarget()); - return pushTypeBarrier(call, types, true); + return pushTypeBarrier(call, types, BarrierKind::TypeSet); } bool @@ -5426,7 +5426,7 @@ IonBuilder::jsop_eval(uint32_t argc) if (!string->mightBeType(MIRType_String)) { current->push(string); types::TemporaryTypeSet *types = bytecodeTypes(pc); - return pushTypeBarrier(string, types, true); + return pushTypeBarrier(string, types, BarrierKind::TypeSet); } current->pushSlot(info().thisSlot()); @@ -5465,7 +5465,7 @@ IonBuilder::jsop_eval(uint32_t argc) current->push(ins); types::TemporaryTypeSet *types = bytecodeTypes(pc); - return resumeAfter(ins) && pushTypeBarrier(ins, types, true); + return resumeAfter(ins) && pushTypeBarrier(ins, types, BarrierKind::TypeSet); } return jsop_call(argc, /* constructing = */ false); @@ -6274,7 +6274,7 @@ IonBuilder::testSingletonPropertyTypes(MDefinition *obj, JSObject *singleton, Pr // instruction replaces the top of the stack. // (5) Lastly, a type barrier instruction replaces the top of the stack. bool -IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, bool needsBarrier) +IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, BarrierKind kind) { // Barriers are never needed for instructions whose result will not be used. if (BytecodeIsPopped(pc)) @@ -6286,7 +6286,7 @@ IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, // must be a resume point capturing the original def, and resuming // to that point will explicitly monitor the new type. - if (!needsBarrier) { + if (kind == BarrierKind::NoBarrier) { MDefinition *replace = ensureDefiniteType(def, observed->getKnownMIRType()); if (replace != def) { current->pop(); @@ -6301,7 +6301,7 @@ IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, current->pop(); - MInstruction *barrier = MTypeBarrier::New(alloc(), def, observed); + MInstruction *barrier = MTypeBarrier::New(alloc(), def, observed, kind); current->add(barrier); if (barrier->type() == MIRType_Undefined) @@ -6342,7 +6342,8 @@ IonBuilder::pushDOMTypeBarrier(MInstruction *ins, types::TemporaryTypeSet *obser JS_ASSERT(barrier); } - return pushTypeBarrier(replace, observed, barrier); + return pushTypeBarrier(replace, observed, + barrier ? BarrierKind::TypeSet : BarrierKind::NoBarrier); } MDefinition * @@ -6449,13 +6450,13 @@ IonBuilder::getStaticName(JSObject *staticObject, PropertyName *name, bool *psuc } types::TemporaryTypeSet *types = bytecodeTypes(pc); - bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticType, - name, types, /* updateObserved = */ true); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), staticType, + name, types, /* updateObserved = */ true); JSObject *singleton = types->getSingleton(); MIRType knownType = types->getKnownMIRType(); - if (!barrier) { + if (barrier == BarrierKind::NoBarrier) { if (singleton) { // Try to inline a known constant value. if (testSingletonProperty(staticObject, name) == singleton) @@ -6470,7 +6471,7 @@ IonBuilder::getStaticName(JSObject *staticObject, PropertyName *name, bool *psuc MInstruction *obj = constant(ObjectValue(*staticObject)); MIRType rvalType = types->getKnownMIRType(); - if (barrier) + if (barrier != BarrierKind::NoBarrier) rvalType = MIRType_Value; return loadSlot(obj, property.maybeTypes()->definiteSlot(), NumFixedSlots(staticObject), @@ -6612,7 +6613,7 @@ IonBuilder::jsop_getname(PropertyName *name) return false; types::TemporaryTypeSet *types = bytecodeTypes(pc); - return pushTypeBarrier(ins, types, true); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); } bool @@ -6631,7 +6632,7 @@ IonBuilder::jsop_intrinsic(PropertyName *name) if (!resumeAfter(ins)) return false; - return pushTypeBarrier(ins, types, true); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); } // Bake in the intrinsic. Make sure that TI agrees with us on the type. @@ -6696,7 +6697,7 @@ IonBuilder::jsop_getelem() return false; types::TemporaryTypeSet *types = bytecodeTypes(pc); - return pushTypeBarrier(ins, types, true); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); } bool emitted = false; @@ -6738,7 +6739,7 @@ IonBuilder::jsop_getelem() return false; types::TemporaryTypeSet *types = bytecodeTypes(pc); - return pushTypeBarrier(ins, types, true); + return pushTypeBarrier(ins, types, BarrierKind::TypeSet); } bool @@ -7004,7 +7005,7 @@ IonBuilder::pushDerivedTypedObject(bool *emitted, { derivedTypedObj->setResultTypeSet(observedTypes); } else { - if (!pushTypeBarrier(derivedTypedObj, observedTypes, true)) + if (!pushTypeBarrier(derivedTypedObj, observedTypes, BarrierKind::TypeSet)) return false; } @@ -7186,7 +7187,7 @@ IonBuilder::getElemTryArguments(bool *emitted, MDefinition *obj, MDefinition *in current->push(load); types::TemporaryTypeSet *types = bytecodeTypes(pc); - if (!pushTypeBarrier(load, types, true)) + if (!pushTypeBarrier(load, types, BarrierKind::TypeSet)) return false; *emitted = true; @@ -7255,18 +7256,19 @@ IonBuilder::getElemTryCache(bool *emitted, MDefinition *obj, MDefinition *index) // Emit GetElementCache. types::TemporaryTypeSet *types = bytecodeTypes(pc); - bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, nullptr, types); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, + nullptr, types); // Always add a barrier if the index might be a string, so that the cache // can attach stubs for particular properties. if (index->mightBeType(MIRType_String)) - barrier = true; + barrier = BarrierKind::TypeSet; // See note about always needing a barrier in jsop_getprop. if (needsToMonitorMissingProperties(types)) - barrier = true; + barrier = BarrierKind::TypeSet; - MInstruction *ins = MGetElementCache::New(alloc(), obj, index, barrier); + MInstruction *ins = MGetElementCache::New(alloc(), obj, index, barrier != BarrierKind::NoBarrier); current->add(ins); current->push(ins); @@ -7275,7 +7277,7 @@ IonBuilder::getElemTryCache(bool *emitted, MDefinition *obj, MDefinition *index) return false; // Spice up type information. - if (index->type() == MIRType_Int32 && !barrier) { + if (index->type() == MIRType_Int32 && barrier == BarrierKind::NoBarrier) { bool needHoleCheck = !ElementAccessIsPacked(constraints(), obj); MIRType knownType = GetElemKnownType(needHoleCheck, types); @@ -7302,7 +7304,8 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index) AddObjectsForPropertyRead(obj, nullptr, types); } - bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, nullptr, types); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), obj, + nullptr, types); bool needsHoleCheck = !ElementAccessIsPacked(constraints(), obj); // Reads which are on holes in the object do not have to bail out if @@ -7313,7 +7316,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index) !ElementAccessHasExtraIndexedProperty(constraints(), obj); MIRType knownType = MIRType_Value; - if (!barrier) + if (barrier == BarrierKind::NoBarrier) knownType = GetElemKnownType(needsHoleCheck, types); // Ensure index is an integer. @@ -7341,7 +7344,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index) ExecutionMode executionMode = info().executionMode(); bool loadDouble = executionMode == SequentialExecution && - !barrier && + barrier == BarrierKind::NoBarrier && loopDepth_ && !readOutOfBounds && !needsHoleCheck && @@ -7392,7 +7395,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index) // NB: we have not added a MConvertElementsToDoubles MIR, so we // cannot *assume* the result is a double. if (executionMode == ParallelExecution && - barrier && + barrier != BarrierKind::NoBarrier && types->getKnownMIRType() == MIRType_Int32 && objTypes && objTypes->convertDoubleElements(constraints()) == types::TemporaryTypeSet::AlwaysConvertToDoubles) @@ -7402,7 +7405,7 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index) if (!types) return false; - barrier = false; // Don't need a barrier anymore + barrier = BarrierKind::NoBarrier; // Don't need a barrier anymore } if (knownType != MIRType_Value) @@ -7561,7 +7564,7 @@ IonBuilder::jsop_getelem_typed(MDefinition *obj, MDefinition *index, // observed (we've only read out-of-bounds values). Note that for // Uint32Array, we only check for int32: if allowDouble is false we // will bailout when we read a double. - bool needsBarrier = true; + BarrierKind barrier = BarrierKind::TypeSet; switch (arrayType) { case ScalarTypeDescr::TYPE_INT8: case ScalarTypeDescr::TYPE_UINT8: @@ -7571,12 +7574,12 @@ IonBuilder::jsop_getelem_typed(MDefinition *obj, MDefinition *index, case ScalarTypeDescr::TYPE_INT32: case ScalarTypeDescr::TYPE_UINT32: if (types->hasType(types::Type::Int32Type())) - needsBarrier = false; + barrier = BarrierKind::NoBarrier; break; case ScalarTypeDescr::TYPE_FLOAT32: case ScalarTypeDescr::TYPE_FLOAT64: if (allowDouble) - needsBarrier = false; + barrier = BarrierKind::NoBarrier; break; default: MOZ_ASSUME_UNREACHABLE("Unknown typed array type"); @@ -7590,7 +7593,7 @@ IonBuilder::jsop_getelem_typed(MDefinition *obj, MDefinition *index, current->add(load); current->push(load); - return pushTypeBarrier(load, types, needsBarrier); + return pushTypeBarrier(load, types, barrier); } } @@ -8494,7 +8497,7 @@ IonBuilder::invalidatedIdempotentCache() bool IonBuilder::loadSlot(MDefinition *obj, size_t slot, size_t nfixed, MIRType rvalType, - bool barrier, types::TemporaryTypeSet *types) + BarrierKind barrier, types::TemporaryTypeSet *types) { if (slot < nfixed) { MLoadFixedSlot *load = MLoadFixedSlot::New(alloc(), obj, slot); @@ -8518,7 +8521,7 @@ IonBuilder::loadSlot(MDefinition *obj, size_t slot, size_t nfixed, MIRType rvalT bool IonBuilder::loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType, - bool barrier, types::TemporaryTypeSet *types) + BarrierKind barrier, types::TemporaryTypeSet *types) { return loadSlot(obj, shape->slot(), shape->numFixedSlots(), rvalType, barrier, types); } @@ -8568,8 +8571,8 @@ IonBuilder::jsop_getprop(PropertyName *name) return emitted; types::TemporaryTypeSet *types = bytecodeTypes(pc); - bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), - current->peek(-1), name, types); + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), + current->peek(-1), name, types); // Always use a call if we are performing analysis and // not actually emitting code, to simplify later analysis. Also skip deeper @@ -8591,7 +8594,7 @@ IonBuilder::jsop_getprop(PropertyName *name) current->pop(); current->push(call); - return resumeAfter(call) && pushTypeBarrier(call, types, true); + return resumeAfter(call) && pushTypeBarrier(call, types, BarrierKind::TypeSet); } // Try to hardcode known constants. @@ -8626,7 +8629,7 @@ IonBuilder::jsop_getprop(PropertyName *name) if (!resumeAfter(call)) return false; - return pushTypeBarrier(call, types, true); + return pushTypeBarrier(call, types, BarrierKind::TypeSet); } bool @@ -8765,7 +8768,7 @@ IonBuilder::getPropTryComplexPropOfTypedObject(bool *emitted, bool IonBuilder::getPropTryDefiniteSlot(bool *emitted, PropertyName *name, - bool barrier, types::TemporaryTypeSet *types) + BarrierKind barrier, types::TemporaryTypeSet *types) { JS_ASSERT(*emitted == false); types::HeapTypeSetKey property; @@ -8781,7 +8784,7 @@ IonBuilder::getPropTryDefiniteSlot(bool *emitted, PropertyName *name, } MLoadFixedSlot *fixed = MLoadFixedSlot::New(alloc(), useObj, property.maybeTypes()->definiteSlot()); - if (!barrier) + if (barrier == BarrierKind::NoBarrier) fixed->setResultType(types->getKnownMIRType()); current->add(fixed); @@ -8903,7 +8906,7 @@ CanInlinePropertyOpShapes(const BaselineInspector::ShapeVector &shapes) bool IonBuilder::getPropTryInlineAccess(bool *emitted, PropertyName *name, - bool barrier, types::TemporaryTypeSet *types) + BarrierKind barrier, types::TemporaryTypeSet *types) { JS_ASSERT(*emitted == false); if (current->peek(-1)->type() != MIRType_Object) @@ -8917,7 +8920,7 @@ IonBuilder::getPropTryInlineAccess(bool *emitted, PropertyName *name, return true; MIRType rvalType = types->getKnownMIRType(); - if (barrier || IsNullOrUndefined(rvalType)) + if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType)) rvalType = MIRType_Value; MDefinition *obj = current->pop(); @@ -8964,7 +8967,7 @@ IonBuilder::getPropTryInlineAccess(bool *emitted, PropertyName *name, bool IonBuilder::getPropTryCache(bool *emitted, PropertyName *name, - bool barrier, types::TemporaryTypeSet *types) + BarrierKind barrier, types::TemporaryTypeSet *types) { JS_ASSERT(*emitted == false); @@ -8981,18 +8984,19 @@ IonBuilder::getPropTryCache(bool *emitted, PropertyName *name, // Since getters have no guaranteed return values, we must barrier in order to be // able to attach stubs for them. if (inspector->hasSeenAccessedGetter(pc)) - barrier = true; + barrier = BarrierKind::TypeSet; if (needsToMonitorMissingProperties(types)) - barrier = true; + barrier = BarrierKind::TypeSet; // Caches can read values from prototypes, so update the barrier to // reflect such possible values. - if (!barrier) + if (barrier == BarrierKind::NoBarrier) barrier = PropertyReadOnPrototypeNeedsTypeBarrier(constraints(), obj, name, types); current->pop(); - MGetPropertyCache *load = MGetPropertyCache::New(alloc(), obj, name, barrier); + MGetPropertyCache *load = MGetPropertyCache::New(alloc(), obj, name, + barrier != BarrierKind::NoBarrier); // Try to mark the cache as idempotent. // @@ -9019,7 +9023,7 @@ IonBuilder::getPropTryCache(bool *emitted, PropertyName *name, return false; MIRType rvalType = types->getKnownMIRType(); - if (barrier || IsNullOrUndefined(rvalType)) + if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType)) rvalType = MIRType_Value; load->setResultType(rvalType); @@ -9863,7 +9867,7 @@ IonBuilder::jsop_getaliasedvar(ScopeCoordinate sc) current->push(load); types::TemporaryTypeSet *types = bytecodeTypes(pc); - return pushTypeBarrier(load, types, true); + return pushTypeBarrier(load, types, BarrierKind::TypeSet); } bool diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 1c5531ee9894..e3fdadec2d56 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -343,7 +343,7 @@ class IonBuilder : public MIRGenerator // Add a guard which ensure that the set of type which goes through this // generated code correspond to the observed types for the bytecode. - bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, bool needBarrier); + bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, BarrierKind kind); // As pushTypeBarrier, but will compute the needBarrier boolean itself based // on observed and the JSFunction that we're planning to call. The @@ -380,9 +380,9 @@ class IonBuilder : public MIRGenerator bool hasStaticScopeObject(ScopeCoordinate sc, JSObject **pcall); bool loadSlot(MDefinition *obj, size_t slot, size_t nfixed, MIRType rvalType, - bool barrier, types::TemporaryTypeSet *types); + BarrierKind barrier, types::TemporaryTypeSet *types); bool loadSlot(MDefinition *obj, Shape *shape, MIRType rvalType, - bool barrier, types::TemporaryTypeSet *types); + BarrierKind barrier, types::TemporaryTypeSet *types); bool storeSlot(MDefinition *obj, size_t slot, size_t nfixed, MDefinition *value, bool needsBarrier, MIRType slotType = MIRType_None); @@ -394,11 +394,11 @@ class IonBuilder : public MIRGenerator bool getPropTryConstant(bool *emitted, PropertyName *name, types::TemporaryTypeSet *types); bool getPropTryDefiniteSlot(bool *emitted, PropertyName *name, - bool barrier, types::TemporaryTypeSet *types); + BarrierKind barrier, types::TemporaryTypeSet *types); bool getPropTryCommonGetter(bool *emitted, PropertyName *name, types::TemporaryTypeSet *types); bool getPropTryInlineAccess(bool *emitted, PropertyName *name, - bool barrier, types::TemporaryTypeSet *types); + BarrierKind barrier, types::TemporaryTypeSet *types); bool getPropTryTypedObject(bool *emitted, PropertyName *name, types::TemporaryTypeSet *resultTypes); bool getPropTryScalarPropOfTypedObject(bool *emitted, @@ -411,7 +411,7 @@ class IonBuilder : public MIRGenerator size_t fieldIndex, types::TemporaryTypeSet *resultTypes); bool getPropTryCache(bool *emitted, PropertyName *name, - bool barrier, types::TemporaryTypeSet *types); + BarrierKind barrier, types::TemporaryTypeSet *types); bool needsToMonitorMissingProperties(types::TemporaryTypeSet *types); // jsop_setprop() helpers. diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 29263af58786..6083da8c8ef4 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -1969,7 +1969,7 @@ GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att Register scratchReg = object; masm.push(scratchReg); - masm.guardTypeSet(valReg, propTypes, scratchReg, &barrierFailure); + masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratchReg, &barrierFailure); masm.pop(object); } } @@ -2521,7 +2521,7 @@ GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &att JS_ASSERT(!propTypes->unknown()); Register scratchReg = object; - masm.guardTypeSet(valReg, propTypes, scratchReg, &failuresPopObject); + masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratchReg, &failuresPopObject); masm.loadPtr(Address(StackPointer, 0), object); } diff --git a/js/src/jit/IonFrames.cpp b/js/src/jit/IonFrames.cpp index 37f2921b55d4..ade6873bf592 100644 --- a/js/src/jit/IonFrames.cpp +++ b/js/src/jit/IonFrames.cpp @@ -631,9 +631,8 @@ HandleException(ResumeFromException *rfe) if (invalidated) popSPSFrame = ionScript->hasSPSInstrumentation(); - // If inline-frames are not profiled, then don't pop an SPS frame - // for them. - if (frames.more() && !js_JitOptions.profileInlineFrames) + // Don't pop an SPS frame for inlined frames, since they are not instrumented. + if (frames.more()) popSPSFrame = false; // When profiling, each frame popped needs a notification that @@ -1339,7 +1338,8 @@ SnapshotIterator::SnapshotIterator(IonScript *ionScript, SnapshotOffset snapshot ionScript->recoversSize()), fp_(fp), machine_(machine), - ionScript_(ionScript) + ionScript_(ionScript), + instructionResults_(nullptr) { JS_ASSERT(snapshotOffset < ionScript->snapshotsListSize()); } @@ -1354,7 +1354,8 @@ SnapshotIterator::SnapshotIterator(const JitFrameIterator &iter) iter.ionScript()->recoversSize()), fp_(iter.jsFrame()), machine_(iter.machineState()), - ionScript_(iter.ionScript()) + ionScript_(iter.ionScript()), + instructionResults_(nullptr) { } @@ -1362,7 +1363,8 @@ SnapshotIterator::SnapshotIterator() : snapshot_(nullptr, 0, 0, 0), recover_(snapshot_, nullptr, 0), fp_(nullptr), - ionScript_(nullptr) + ionScript_(nullptr), + instructionResults_(nullptr) { } @@ -1427,6 +1429,9 @@ SnapshotIterator::allocationReadable(const RValueAllocation &alloc) return hasStack(alloc.stackOffset()); #endif + case RValueAllocation::RECOVER_INSTRUCTION: + return hasInstructionResult(alloc.index()); + default: return true; } @@ -1532,6 +1537,9 @@ SnapshotIterator::allocationValue(const RValueAllocation &alloc) } #endif + case RValueAllocation::RECOVER_INSTRUCTION: + return fromInstructionResult(alloc.index()); + default: MOZ_ASSUME_UNREACHABLE("huh?"); } @@ -1546,7 +1554,7 @@ SnapshotIterator::resumePoint() const uint32_t SnapshotIterator::numAllocations() const { - return resumePoint()->numOperands(); + return instruction()->numOperands(); } uint32_t @@ -1565,6 +1573,43 @@ SnapshotIterator::skipInstruction() nextInstruction(); } +bool +SnapshotIterator::initIntructionResults(AutoValueVector &results) +{ + MOZ_ASSERT(recover_.numInstructionsRead() == 1); + + // The last instruction will always be a resume point, no need to allocate + // space for it. + if (recover_.numInstructions() == 1) + return true; + + MOZ_ASSERT(recover_.numInstructions() > 1); + size_t numResults = recover_.numInstructions() - 1; + if (!results.reserve(numResults)) + return false; + + for (size_t i = 0; i < numResults; i++) + results.infallibleAppend(MagicValue(JS_ION_BAILOUT)); + + instructionResults_ = &results; + return true; +} + +void +SnapshotIterator::storeInstructionResult(Value v) +{ + uint32_t currIns = recover_.numInstructionsRead() - 1; + MOZ_ASSERT((*instructionResults_)[currIns].isMagic(JS_ION_BAILOUT)); + (*instructionResults_)[currIns].set(v); +} + +Value +SnapshotIterator::fromInstructionResult(uint32_t index) const +{ + MOZ_ASSERT(!(*instructionResults_)[index].isMagic(JS_ION_BAILOUT)); + return (*instructionResults_)[index]; +} + void SnapshotIterator::nextFrame() { diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index 2326fe1611da..ea7611cd9224 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -69,9 +69,10 @@ class TypeWrapper { } /* anonymous namespace */ template void -MacroAssembler::guardTypeSet(const Source &address, const TypeSet *types, +MacroAssembler::guardTypeSet(const Source &address, const TypeSet *types, BarrierKind kind, Register scratch, Label *miss) { + JS_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet); JS_ASSERT(!types->unknown()); Label matched; @@ -126,8 +127,10 @@ MacroAssembler::guardTypeSet(const Source &address, const TypeSet *types, // Test specific objects. JS_ASSERT(scratch != InvalidReg); branchTestObject(NotEqual, tag, miss); - Register obj = extractObject(address, scratch); - guardObjectType(obj, types, scratch, miss); + if (kind != BarrierKind::TypeTagOnly) { + Register obj = extractObject(address, scratch); + guardObjectType(obj, types, scratch, miss); + } bind(&matched); } @@ -203,30 +206,30 @@ MacroAssembler::guardType(const Source &address, types::Type type, Register scratch, Label *miss) { TypeWrapper wrapper(type); - guardTypeSet(address, &wrapper, scratch, miss); + guardTypeSet(address, &wrapper, BarrierKind::TypeSet, scratch, miss); } template void MacroAssembler::guardTypeSet(const Address &address, const types::TemporaryTypeSet *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const ValueOperand &value, const types::TemporaryTypeSet *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const Address &address, const types::HeapTypeSet *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const ValueOperand &value, const types::HeapTypeSet *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const TypedOrValueRegister ®, const types::HeapTypeSet *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const Address &address, const types::TypeSet *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const ValueOperand &value, const types::TypeSet *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const Address &address, const TypeWrapper *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardTypeSet(const ValueOperand &value, const TypeWrapper *types, - Register scratch, Label *miss); + BarrierKind kind, Register scratch, Label *miss); template void MacroAssembler::guardObjectType(Register obj, const types::TemporaryTypeSet *types, Register scratch, Label *miss); diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index d3f9b76f1ef0..f5eee3cf1951 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -300,7 +300,7 @@ class MacroAssembler : public MacroAssemblerSpecific // Emits a test of a value against all types in a TypeSet. A scratch // register is required. template - void guardTypeSet(const Source &address, const TypeSet *types, Register scratch, Label *miss); + void guardTypeSet(const Source &address, const TypeSet *types, BarrierKind kind, Register scratch, Label *miss); template void guardObjectType(Register obj, const TypeSet *types, Register scratch, Label *miss); template diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index 306f217611ff..ea462c2cf7dc 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -7,6 +7,8 @@ #ifndef jit_IonTypes_h #define jit_IonTypes_h +#include "mozilla/TypedEnum.h" + #include "jstypes.h" #include "js/Value.h" @@ -324,6 +326,19 @@ enum ABIFunctionType (ArgType_General << (ArgType_Shift * 2)) }; +MOZ_BEGIN_ENUM_CLASS(BarrierKind, uint32_t) + // No barrier is needed. + NoBarrier, + + // The barrier only has to check the value's type tag is in the TypeSet. + // Specific object types don't have to be checked. + TypeTagOnly, + + // Check if the value is in the TypeSet, including the object type if it's + // an object. + TypeSet +MOZ_END_ENUM_CLASS(BarrierKind) + } // namespace jit } // namespace js diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index ba06bf3ce15c..362658c42bd9 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -258,6 +258,7 @@ class SnapshotIterator IonJSFrameLayout *fp_; MachineState machine_; IonScript *ionScript_; + AutoValueVector *instructionResults_; private: // Read a spilled register from the machine state. @@ -281,6 +282,11 @@ class SnapshotIterator } uintptr_t fromStack(int32_t offset) const; + bool hasInstructionResult(uint32_t index) const { + return instructionResults_; + } + Value fromInstructionResult(uint32_t index) const; + Value allocationValue(const RValueAllocation &a); bool allocationReadable(const RValueAllocation &a); void warnUnreadableAllocation(); @@ -338,6 +344,14 @@ class SnapshotIterator return recover_.moreInstructions(); } + // Register a vector used for storing the results of the evaluation of + // recover instructions. This vector should be registered before the + // beginning of the iteration. This function is in charge of allocating + // enough space for all instructions results, and return false iff it fails. + bool initIntructionResults(AutoValueVector &results); + + void storeInstructionResult(Value v); + public: // Handle iterating over frames of the snapshots. void nextFrame(); diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp index b36a51945dfe..8b442dd3b648 100644 --- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -110,9 +110,6 @@ JitOptions::JitOptions() // How many uses of a parallel kernel before we attempt compilation. usesBeforeCompilePar = 1; - - // Whether to profile inlined functions in Ion or not. - profileInlineFrames = false; } bool diff --git a/js/src/jit/JitOptions.h b/js/src/jit/JitOptions.h index a139a17ff4be..412f40e7250d 100644 --- a/js/src/jit/JitOptions.h +++ b/js/src/jit/JitOptions.h @@ -69,7 +69,6 @@ struct JitOptions uint32_t osrPcMismatchesBeforeRecompile; uint32_t smallFunctionMaxBytecodeLength_; uint32_t usesBeforeCompilePar; - bool profileInlineFrames; JitOptions(); bool isSmallFunction(JSScript *script) const; diff --git a/js/src/jit/LIR.cpp b/js/src/jit/LIR.cpp index 82aaf8507b1c..b88a858eb772 100644 --- a/js/src/jit/LIR.cpp +++ b/js/src/jit/LIR.cpp @@ -111,11 +111,16 @@ LBlock::getExitMoveGroup(TempAllocator &alloc) } static size_t -TotalOperandCount(MResumePoint *mir) +TotalOperandCount(LRecoverInfo *recoverInfo) { - size_t accum = mir->numOperands(); - while ((mir = mir->caller())) - accum += mir->numOperands(); + LRecoverInfo::OperandIter it(recoverInfo->begin()); + LRecoverInfo::OperandIter end(recoverInfo->end()); + size_t accum = 0; + + for (; it != end; ++it) { + if (!it->isRecoveredOnBailout()) + accum++; + } return accum; } @@ -137,28 +142,71 @@ LRecoverInfo::New(MIRGenerator *gen, MResumePoint *mir) return recoverInfo; } +bool +LRecoverInfo::appendOperands(MNode *ins) +{ + for (size_t i = 0, end = ins->numOperands(); i < end; i++) { + MDefinition *def = ins->getOperand(i); + + // As there is no cycle in the data-flow (without MPhi), checking for + // isInWorkList implies that the definition is already in the + // instruction vector, and not processed by a caller of the current + // function. + if (def->isRecoveredOnBailout() && !def->isInWorklist()) { + if (!appendDefinition(def)) + return false; + } + } + + return true; +} + +bool +LRecoverInfo::appendDefinition(MDefinition *def) +{ + MOZ_ASSERT(def->isRecoveredOnBailout()); + def->setInWorklist(); + if (!appendOperands(def)) + return false; + return instructions_.append(def); +} + +bool +LRecoverInfo::appendResumePoint(MResumePoint *rp) +{ + if (rp->caller() && !appendResumePoint(rp->caller())) + return false; + + if (!appendOperands(rp)) + return false; + + return instructions_.append(rp); +} + bool LRecoverInfo::init(MResumePoint *rp) { - MResumePoint *it = rp; - // Sort operations in the order in which we need to restore the stack. This // implies that outer frames, as well as operations needed to recover the // current frame, are located before the current frame. The inner-most // resume point should be the last element in the list. - do { - if (!instructions_.append(it)) - return false; - it = it->caller(); - } while (it); + if (!appendResumePoint(rp)) + return false; + + // Remove temporary flags from all definitions. + for (MNode **it = begin(); it != end(); it++) { + if (!(*it)->isDefinition()) + continue; + + (*it)->toDefinition()->setNotInWorklist(); + } - Reverse(instructions_.begin(), instructions_.end()); MOZ_ASSERT(mir() == rp); return true; } LSnapshot::LSnapshot(LRecoverInfo *recoverInfo, BailoutKind kind) - : numSlots_(TotalOperandCount(recoverInfo->mir()) * BOX_PIECES), + : numSlots_(TotalOperandCount(recoverInfo) * BOX_PIECES), slots_(nullptr), recoverInfo_(recoverInfo), snapshotOffset_(INVALID_SNAPSHOT_OFFSET), @@ -368,8 +416,6 @@ LInstruction::dump(FILE *fp) fprintf(fp, "} <- "); printName(fp); - - printInfo(fp); if (numTemps()) { @@ -381,13 +427,13 @@ LInstruction::dump(FILE *fp) } fprintf(fp, ")"); } - fprintf(fp, "\n"); } void LInstruction::dump() { - return dump(stderr); + dump(stderr); + fprintf(stderr, "\n"); } void diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h index 8eec388a940a..2caba77f1050 100644 --- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -879,7 +879,7 @@ class LCallInstructionHelper : public LInstructionHelper class LRecoverInfo : public TempObject { public: - typedef Vector Instructions; + typedef Vector Instructions; private: // List of instructions needed to recover the stack frames. @@ -892,12 +892,17 @@ class LRecoverInfo : public TempObject LRecoverInfo(TempAllocator &alloc); bool init(MResumePoint *mir); + // Fill the instruction vector such as all instructions needed for the + // recovery are pushed before the current instruction. + bool appendOperands(MNode *ins); + bool appendDefinition(MDefinition *def); + bool appendResumePoint(MResumePoint *rp); public: static LRecoverInfo *New(MIRGenerator *gen, MResumePoint *mir); // Resume point of the inner most function. MResumePoint *mir() const { - return instructions_.back(); + return instructions_.back()->toResumePoint(); } RecoverOffset recoverOffset() const { return recoverOffset_; @@ -907,12 +912,47 @@ class LRecoverInfo : public TempObject recoverOffset_ = offset; } - MResumePoint **begin() { + MNode **begin() { return instructions_.begin(); } - MResumePoint **end() { + MNode **end() { return instructions_.end(); } + size_t numInstructions() const { + return instructions_.length(); + } + + class OperandIter + { + private: + MNode **it_; + size_t op_; + + public: + OperandIter(MNode **it) + : it_(it), op_(0) + { } + + MDefinition *operator *() { + return (*it_)->getOperand(op_); + } + MDefinition *operator ->() { + return (*it_)->getOperand(op_); + } + + OperandIter &operator ++() { + ++op_; + if (op_ == (*it_)->numOperands()) { + op_ = 0; + ++it_; + } + return *this; + } + + bool operator !=(const OperandIter &where) const { + return it_ != where.it_ || op_ != where.op_; + } + }; }; // An LSnapshot is the reflection of an MResumePoint in LIR. Unlike MResumePoints, diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index ad9368c75ad5..1e0b18c877d5 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2303,7 +2303,8 @@ LIRGenerator::visitTypeBarrier(MTypeBarrier *ins) } // Handle typebarrier with specific TypeObject/SingleObjects. - if (inputType == MIRType_Object && !types->hasType(types::Type::AnyObjectType())) + if (inputType == MIRType_Object && !types->hasType(types::Type::AnyObjectType()) && + ins->barrierKind() != BarrierKind::TypeTagOnly) { LDefinition tmp = needTemp ? temp() : LDefinition::BogusTemp(); LTypeBarrierO *barrier = new(alloc()) LTypeBarrierO(useRegister(ins->getOperand(0)), tmp); @@ -3597,6 +3598,9 @@ SpewResumePoint(MBasicBlock *block, MInstruction *ins, MResumePoint *resumePoint bool LIRGenerator::visitInstruction(MInstruction *ins) { + if (ins->isRecoveredOnBailout()) + return true; + if (!gen->ensureBallast()) return false; if (!ins->accept(this)) diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 24e3c357a93d..179e6667b1bb 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -385,9 +385,9 @@ IonBuilder::inlineArrayPopShift(CallInfo &callInfo, MArrayPopShift::Mode mode) bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), types::OBJECT_FLAG_NON_PACKED); bool maybeUndefined = returnTypes->hasType(types::Type::UndefinedType()); - bool barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), - callInfo.thisArg(), nullptr, returnTypes); - if (barrier) + BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(), + callInfo.thisArg(), nullptr, returnTypes); + if (barrier != BarrierKind::NoBarrier) returnType = MIRType_Value; MArrayPopShift *ins = MArrayPopShift::New(alloc(), callInfo.thisArg(), mode, @@ -1231,7 +1231,7 @@ IonBuilder::inlineRegExpExec(CallInfo &callInfo) if (!resumeAfter(exec)) return InliningStatus_Error; - if (!pushTypeBarrier(exec, getInlineReturnTypeSet(), true)) + if (!pushTypeBarrier(exec, getInlineReturnTypeSet(), BarrierKind::TypeSet)) return InliningStatus_Error; return InliningStatus_Inlined; @@ -1782,7 +1782,7 @@ IonBuilder::inlineUnsafeGetReservedSlot(CallInfo &callInfo) current->push(load); // We don't track reserved slot types, so always emit a barrier. - if (!pushTypeBarrier(load, getInlineReturnTypeSet(), true)) + if (!pushTypeBarrier(load, getInlineReturnTypeSet(), BarrierKind::TypeSet)) return InliningStatus_Error; return InliningStatus_Inlined; diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 152e121b1ee0..a3134296b62a 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -359,6 +359,20 @@ MDefinition::hasDefUses() const return false; } +bool +MDefinition::hasLiveDefUses() const +{ + for (MUseIterator i(uses_.begin()); i != uses_.end(); i++) { + MNode *ins = (*i)->consumer(); + if (!ins->isDefinition()) + continue; + if (!ins->toDefinition()->isRecoveredOnBailout()) + return true; + } + + return false; +} + MUseIterator MDefinition::removeUse(MUseIterator use) { @@ -3089,7 +3103,7 @@ jit::DenseNativeElementType(types::CompilerConstraintList *constraints, MDefinit return elementType; } -static bool +static BarrierKind PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints, types::TypeObjectKey *object, PropertyName *name, types::TypeSet *observed) @@ -3105,13 +3119,22 @@ PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints, if (object->unknownProperties() || observed->empty() || object->clasp()->isProxy()) { - return true; + return BarrierKind::TypeSet; } jsid id = name ? NameToId(name) : JSID_VOID; types::HeapTypeSetKey property = object->property(id); - if (property.maybeTypes() && !TypeSetIncludes(observed, MIRType_Value, property.maybeTypes())) - return true; + if (property.maybeTypes()) { + if (!TypeSetIncludes(observed, MIRType_Value, property.maybeTypes())) { + // If all possible objects have been observed, we don't have to + // guard on the specific object types. + if (property.maybeTypes()->objectsAreSubset(observed)) { + property.freeze(constraints); + return BarrierKind::TypeTagOnly; + } + return BarrierKind::TypeSet; + } + } // Type information for global objects is not required to reflect the // initial 'undefined' value for properties, in particular global @@ -3121,15 +3144,15 @@ PropertyReadNeedsTypeBarrier(types::CompilerConstraintList *constraints, if (name && types::CanHaveEmptyPropertyTypesForOwnProperty(obj) && (!property.maybeTypes() || property.maybeTypes()->empty())) { - return true; + return BarrierKind::TypeSet; } } property.freeze(constraints); - return false; + return BarrierKind::NoBarrier; } -bool +BarrierKind jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx, types::CompilerConstraintList *constraints, types::TypeObjectKey *object, PropertyName *name, @@ -3177,45 +3200,55 @@ jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx, return PropertyReadNeedsTypeBarrier(constraints, object, name, observed); } -bool +BarrierKind jit::PropertyReadNeedsTypeBarrier(JSContext *propertycx, types::CompilerConstraintList *constraints, MDefinition *obj, PropertyName *name, types::TemporaryTypeSet *observed) { if (observed->unknown()) - return false; + return BarrierKind::NoBarrier; types::TypeSet *types = obj->resultTypeSet(); if (!types || types->unknownObject()) - return true; + return BarrierKind::TypeSet; + + BarrierKind res = BarrierKind::NoBarrier; bool updateObserved = types->getObjectCount() == 1; for (size_t i = 0; i < types->getObjectCount(); i++) { types::TypeObjectKey *object = types->getObject(i); if (object) { - if (PropertyReadNeedsTypeBarrier(propertycx, constraints, object, name, - observed, updateObserved)) - { - return true; + BarrierKind kind = PropertyReadNeedsTypeBarrier(propertycx, constraints, object, name, + observed, updateObserved); + if (kind == BarrierKind::TypeSet) + return BarrierKind::TypeSet; + + if (kind == BarrierKind::TypeTagOnly) { + MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly); + res = BarrierKind::TypeTagOnly; + } else { + MOZ_ASSERT(kind == BarrierKind::NoBarrier); } } } - return false; + return res; } -bool +BarrierKind jit::PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *constraints, MDefinition *obj, PropertyName *name, types::TemporaryTypeSet *observed) { if (observed->unknown()) - return false; + return BarrierKind::NoBarrier; types::TypeSet *types = obj->resultTypeSet(); if (!types || types->unknownObject()) - return true; + return BarrierKind::TypeSet; + + BarrierKind res = BarrierKind::NoBarrier; for (size_t i = 0; i < types->getObjectCount(); i++) { types::TypeObjectKey *object = types->getObject(i); @@ -3223,16 +3256,24 @@ jit::PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *cons continue; while (true) { if (!object->hasTenuredProto()) - return true; + return BarrierKind::TypeSet; if (!object->proto().isObject()) break; object = types::TypeObjectKey::get(object->proto().toObject()); - if (PropertyReadNeedsTypeBarrier(constraints, object, name, observed)) - return true; + BarrierKind kind = PropertyReadNeedsTypeBarrier(constraints, object, name, observed); + if (kind == BarrierKind::TypeSet) + return BarrierKind::TypeSet; + + if (kind == BarrierKind::TypeTagOnly) { + MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly); + res = BarrierKind::TypeTagOnly; + } else { + MOZ_ASSERT(kind == BarrierKind::NoBarrier); + } } } - return false; + return res; } bool @@ -3382,7 +3423,13 @@ TryAddTypeBarrierForWrite(TempAllocator &alloc, types::CompilerConstraintList *c if (!types) return false; - MInstruction *ins = MMonitorTypes::New(alloc, *pvalue, types); + // If all possible objects can be stored without a barrier, we don't have to + // guard on the specific object types. + BarrierKind kind = BarrierKind::TypeSet; + if ((*pvalue)->resultTypeSet() && (*pvalue)->resultTypeSet()->objectsAreSubset(types)) + kind = BarrierKind::TypeTagOnly; + + MInstruction *ins = MMonitorTypes::New(alloc, *pvalue, types, kind); current->add(ins); return true; } diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 1e365491c3c6..572fb7cb3885 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -89,7 +89,14 @@ MIRType MIRTypeFromValue(const js::Value &vp) * Truncate Doubles. So every time removeUse is called, UseRemoved needs * to get set. */ \ - _(UseRemoved) + _(UseRemoved) \ + \ + /* Marks if the current instruction should go to the bailout paths instead + * of producing code as part of the control flow. This flag can only be set + * on instructions which are only used by ResumePoint or by other flagged + * instructions. + */ \ + _(RecoveredOnBailout) class MDefinition; class MInstruction; @@ -208,6 +215,8 @@ class MNode : public TempObject inline MDefinition *toDefinition(); inline MResumePoint *toResumePoint(); + virtual bool writeRecoverData(CompactBufferWriter &writer) const; + protected: // Sets an unset operand, updating use information. virtual void setOperand(size_t index, MDefinition *operand) = 0; @@ -531,6 +540,10 @@ class MDefinition : public MNode // (only counting MDefinitions, ignoring MResumePoints) bool hasDefUses() const; + // Test whether this MDefinition has at least one non-recovered use. + // (only counting MDefinitions, ignoring MResumePoints) + bool hasLiveDefUses() const; + bool hasUses() const { return !uses_.empty(); } @@ -606,6 +619,10 @@ class MDefinition : public MNode JS_ASSERT(getAliasSet().flags() & store->getAliasSet().flags()); return true; } + + virtual bool canRecoverOnBailout() const { + return false; + } }; // An MUseDefIterator walks over uses in a definition, skipping any use that is @@ -4000,6 +4017,11 @@ class MAdd : public MBinaryArithInstruction void computeRange(TempAllocator &alloc); bool truncate(); bool isOperandTruncated(size_t index) const; + + bool writeRecoverData(CompactBufferWriter &writer) const; + bool canRecoverOnBailout() const { + return specialization_ < MIRType_Object; + } }; class MSub : public MBinaryArithInstruction @@ -9070,10 +9092,15 @@ class MTypeBarrier : public MUnaryInstruction, public TypeBarrierPolicy { - MTypeBarrier(MDefinition *def, types::TemporaryTypeSet *types) - : MUnaryInstruction(def) + BarrierKind barrierKind_; + + MTypeBarrier(MDefinition *def, types::TemporaryTypeSet *types, BarrierKind kind) + : MUnaryInstruction(def), + barrierKind_(kind) { - JS_ASSERT(!types->unknown()); + MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet); + + MOZ_ASSERT(!types->unknown()); setResultType(types->getKnownMIRType()); setResultTypeSet(types); @@ -9084,8 +9111,9 @@ class MTypeBarrier public: INSTRUCTION_HEADER(TypeBarrier) - static MTypeBarrier *New(TempAllocator &alloc, MDefinition *def, types::TemporaryTypeSet *types) { - return new(alloc) MTypeBarrier(def, types); + static MTypeBarrier *New(TempAllocator &alloc, MDefinition *def, types::TemporaryTypeSet *types, + BarrierKind kind = BarrierKind::TypeSet) { + return new(alloc) MTypeBarrier(def, types, kind); } void printOpcode(FILE *fp) const; @@ -9103,6 +9131,9 @@ class MTypeBarrier virtual bool neverHoist() const { return resultTypeSet()->empty(); } + BarrierKind barrierKind() const { + return barrierKind_; + } bool alwaysBails() const { // If mirtype of input doesn't agree with mirtype of barrier, @@ -9122,20 +9153,25 @@ class MTypeBarrier class MMonitorTypes : public MUnaryInstruction, public BoxInputsPolicy { const types::TemporaryTypeSet *typeSet_; + BarrierKind barrierKind_; - MMonitorTypes(MDefinition *def, const types::TemporaryTypeSet *types) + MMonitorTypes(MDefinition *def, const types::TemporaryTypeSet *types, BarrierKind kind) : MUnaryInstruction(def), - typeSet_(types) + typeSet_(types), + barrierKind_(kind) { + MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet); + setGuard(); - JS_ASSERT(!types->unknown()); + MOZ_ASSERT(!types->unknown()); } public: INSTRUCTION_HEADER(MonitorTypes) - static MMonitorTypes *New(TempAllocator &alloc, MDefinition *def, const types::TemporaryTypeSet *types) { - return new(alloc) MMonitorTypes(def, types); + static MMonitorTypes *New(TempAllocator &alloc, MDefinition *def, const types::TemporaryTypeSet *types, + BarrierKind kind) { + return new(alloc) MMonitorTypes(def, types, kind); } TypePolicy *typePolicy() { @@ -9145,6 +9181,10 @@ class MMonitorTypes : public MUnaryInstruction, public BoxInputsPolicy const types::TemporaryTypeSet *typeSet() const { return typeSet_; } + BarrierKind barrierKind() const { + return barrierKind_; + } + AliasSet getAliasSet() const { return AliasSet::None(); } @@ -10094,17 +10134,17 @@ bool ElementAccessIsPacked(types::CompilerConstraintList *constraints, MDefiniti bool ElementAccessHasExtraIndexedProperty(types::CompilerConstraintList *constraints, MDefinition *obj); MIRType DenseNativeElementType(types::CompilerConstraintList *constraints, MDefinition *obj); -bool PropertyReadNeedsTypeBarrier(JSContext *propertycx, - types::CompilerConstraintList *constraints, - types::TypeObjectKey *object, PropertyName *name, - types::TemporaryTypeSet *observed, bool updateObserved); -bool PropertyReadNeedsTypeBarrier(JSContext *propertycx, - types::CompilerConstraintList *constraints, - MDefinition *obj, PropertyName *name, - types::TemporaryTypeSet *observed); -bool PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *constraints, - MDefinition *obj, PropertyName *name, - types::TemporaryTypeSet *observed); +BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx, + types::CompilerConstraintList *constraints, + types::TypeObjectKey *object, PropertyName *name, + types::TemporaryTypeSet *observed, bool updateObserved); +BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx, + types::CompilerConstraintList *constraints, + MDefinition *obj, PropertyName *name, + types::TemporaryTypeSet *observed); +BarrierKind PropertyReadOnPrototypeNeedsTypeBarrier(types::CompilerConstraintList *constraints, + MDefinition *obj, PropertyName *name, + types::TemporaryTypeSet *observed); bool PropertyReadIsIdempotent(types::CompilerConstraintList *constraints, MDefinition *obj, PropertyName *name); void AddObjectsForPropertyRead(MDefinition *obj, PropertyName *name, diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 3745c442b92a..0a5c0bbcfc12 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -6,21 +6,42 @@ #include "jit/Recover.h" +#include "jscntxt.h" +#include "jsmath.h" + #include "jit/IonSpewer.h" +#include "jit/JitFrameIterator.h" #include "jit/MIR.h" #include "jit/MIRGraph.h" +#include "vm/Interpreter.h" + using namespace js; using namespace js::jit; +bool +MNode::writeRecoverData(CompactBufferWriter &writer) const +{ + MOZ_ASSUME_UNREACHABLE("This instruction is not serializable"); + return false; +} + void RInstruction::readRecoverData(CompactBufferReader &reader, RInstructionStorage *raw) { uint32_t op = reader.readUnsigned(); switch (Opcode(op)) { - case Recover_ResumePoint: - new (raw->addr()) RResumePoint(reader); +# define MATCH_OPCODES_(op) \ + case Recover_##op: \ + static_assert(sizeof(R##op) <= sizeof(RInstructionStorage), \ + "Storage space is too small to decode R" #op " instructions."); \ + new (raw->addr()) R##op(reader); \ break; + + RECOVER_OPCODE_LIST(MATCH_OPCODES_) +# undef DEFINE_OPCODES_ + + case Recover_Invalid: default: MOZ_ASSUME_UNREACHABLE("Bad decoding of the previous instruction?"); break; @@ -101,10 +122,48 @@ MResumePoint::writeRecoverData(CompactBufferWriter &writer) const RResumePoint::RResumePoint(CompactBufferReader &reader) { - static_assert(sizeof(*this) <= sizeof(RInstructionStorage), - "Storage space is too small to decode this recover instruction."); pcOffset_ = reader.readUnsigned(); numOperands_ = reader.readUnsigned(); IonSpew(IonSpew_Snapshots, "Read RResumePoint (pc offset %u, nslots %u)", pcOffset_, numOperands_); } + +bool +RResumePoint::recover(JSContext *cx, SnapshotIterator &iter) const +{ + MOZ_ASSUME_UNREACHABLE("This instruction is not recoverable."); +} + +bool +MAdd::writeRecoverData(CompactBufferWriter &writer) const +{ + MOZ_ASSERT(canRecoverOnBailout()); + writer.writeUnsigned(uint32_t(RInstruction::Recover_Add)); + writer.writeByte(specialization_ == MIRType_Float32); + return true; +} + +RAdd::RAdd(CompactBufferReader &reader) +{ + isFloatOperation_ = reader.readByte(); +} + +bool +RAdd::recover(JSContext *cx, SnapshotIterator &iter) const +{ + RootedValue lhs(cx, iter.read()); + RootedValue rhs(cx, iter.read()); + RootedValue result(cx); + + MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); + if (!js::AddValues(cx, &lhs, &rhs, &result)) + return false; + + // MIRType_Float32 is a specialization embedding the fact that the result is + // rounded to a Float32. + if (isFloatOperation_ && !RoundFloat32(cx, result, &result)) + return false; + + iter.storeInstructionResult(result); + return true; +} diff --git a/js/src/jit/Recover.h b/js/src/jit/Recover.h index 95fe77ea7dde..135e1b701887 100644 --- a/js/src/jit/Recover.h +++ b/js/src/jit/Recover.h @@ -11,44 +11,71 @@ #include "jit/Snapshots.h" +class JSContext; + namespace js { namespace jit { +#define RECOVER_OPCODE_LIST(_) \ + _(ResumePoint) \ + _(Add) + class RResumePoint; +class SnapshotIterator; class RInstruction { public: enum Opcode { - Recover_ResumePoint = 0 +# define DEFINE_OPCODES_(op) Recover_##op, + RECOVER_OPCODE_LIST(DEFINE_OPCODES_) +# undef DEFINE_OPCODES_ + Recover_Invalid }; virtual Opcode opcode() const = 0; + // As opposed to the MIR, there is no need to add more methods as every + // other instruction is well abstracted under the "recover" method. bool isResumePoint() const { return opcode() == Recover_ResumePoint; } inline const RResumePoint *toResumePoint() const; + // Number of allocations which are encoded in the Snapshot for recovering + // the current instruction. virtual uint32_t numOperands() const = 0; + // Function used to recover the value computed by this instruction. This + // function reads its arguments from the allocations listed on the snapshot + // iterator and stores its returned value on the snapshot iterator too. + virtual bool recover(JSContext *cx, SnapshotIterator &iter) const = 0; + + // Decode an RInstruction on top of the reserved storage space, based on the + // tag written by the writeRecoverData function of the corresponding MIR + // instruction. static void readRecoverData(CompactBufferReader &reader, RInstructionStorage *raw); }; +#define RINSTRUCTION_HEADER_(op) \ + private: \ + friend class RInstruction; \ + R##op(CompactBufferReader &reader); \ + \ + public: \ + Opcode opcode() const { \ + return RInstruction::Recover_##op; \ + } + class RResumePoint MOZ_FINAL : public RInstruction { private: uint32_t pcOffset_; // Offset from script->code. uint32_t numOperands_; // Number of slots. - friend class RInstruction; - RResumePoint(CompactBufferReader &reader); - public: - virtual Opcode opcode() const { - return Recover_ResumePoint; - } + RINSTRUCTION_HEADER_(ResumePoint) uint32_t pcOffset() const { return pcOffset_; @@ -56,8 +83,26 @@ class RResumePoint MOZ_FINAL : public RInstruction virtual uint32_t numOperands() const { return numOperands_; } + bool recover(JSContext *cx, SnapshotIterator &iter) const; }; +class RAdd MOZ_FINAL : public RInstruction +{ + private: + bool isFloatOperation_; + + public: + RINSTRUCTION_HEADER_(Add) + + virtual uint32_t numOperands() const { + return 2; + } + + bool recover(JSContext *cx, SnapshotIterator &iter) const; +}; + +#undef RINSTRUCTION_HEADER_ + const RResumePoint * RInstruction::toResumePoint() const { diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp index 6948f6056773..a45b232551f9 100644 --- a/js/src/jit/Snapshots.cpp +++ b/js/src/jit/Snapshots.cpp @@ -76,6 +76,9 @@ using namespace js::jit; // first register/stack-offset correspond to the holder of the type, // and the second correspond to the payload of the JS Value. // +// RECOVER_INSTRUCTION [INDEX] +// Index into the list of recovered instruction results. +// // TYPED_REG [PACKED_TAG, GPR_REG]: // Value with statically known type, which payload is stored in a // register. @@ -219,6 +222,15 @@ RValueAllocation::layoutFromMode(Mode mode) return layout; } #endif + case RECOVER_INSTRUCTION: { + static const RValueAllocation::Layout layout = { + PAYLOAD_INDEX, + PAYLOAD_NONE, + "instruction" + }; + return layout; + } + default: { static const RValueAllocation::Layout regLayout = { PAYLOAD_PACKED_TAG, @@ -662,20 +674,20 @@ SnapshotWriter::endSnapshot() } RecoverOffset -RecoverWriter::startRecover(uint32_t frameCount, bool resumeAfter) +RecoverWriter::startRecover(uint32_t instructionCount, bool resumeAfter) { - MOZ_ASSERT(frameCount); - nframes_ = frameCount; - framesWritten_ = 0; + MOZ_ASSERT(instructionCount); + instructionCount_ = instructionCount; + instructionsWritten_ = 0; - IonSpew(IonSpew_Snapshots, "starting recover with frameCount %u", - frameCount); + IonSpew(IonSpew_Snapshots, "starting recover with %u instruction(s)", + instructionCount); MOZ_ASSERT(!(uint32_t(resumeAfter) &~ RECOVER_RESUMEAFTER_MASK)); - MOZ_ASSERT(frameCount < uint32_t(1 << RECOVER_RINSCOUNT_BITS)); + MOZ_ASSERT(instructionCount < uint32_t(1 << RECOVER_RINSCOUNT_BITS)); uint32_t bits = (uint32_t(resumeAfter) << RECOVER_RESUMEAFTER_SHIFT) | - (frameCount << RECOVER_RINSCOUNT_SHIFT); + (instructionCount << RECOVER_RINSCOUNT_SHIFT); RecoverOffset recoverOffset = writer_.length(); writer_.writeUnsigned(bits); @@ -683,16 +695,16 @@ RecoverWriter::startRecover(uint32_t frameCount, bool resumeAfter) } bool -RecoverWriter::writeFrame(const MResumePoint *rp) +RecoverWriter::writeInstruction(const MNode *rp) { if (!rp->writeRecoverData(writer_)) return false; - framesWritten_++; + instructionsWritten_++; return true; } void RecoverWriter::endRecover() { - JS_ASSERT(nframes_ == framesWritten_); + MOZ_ASSERT(instructionCount_ == instructionsWritten_); } diff --git a/js/src/jit/Snapshots.h b/js/src/jit/Snapshots.h index bdfdb748430c..bf92aef1b339 100644 --- a/js/src/jit/Snapshots.h +++ b/js/src/jit/Snapshots.h @@ -54,6 +54,8 @@ class RValueAllocation UNTYPED_REG = 0x06, UNTYPED_STACK = 0x07, #endif + RECOVER_INSTRUCTION = 0x0a, + // The JSValueType is packed in the Mode. TYPED_REG_MIN = 0x10, TYPED_REG_MAX = 0x17, @@ -236,6 +238,11 @@ class RValueAllocation return RValueAllocation(CONSTANT, payloadOfIndex(index)); } + // Recover instruction's index + static RValueAllocation RecoverInstruction(uint32_t index) { + return RValueAllocation(RECOVER_INSTRUCTION, payloadOfIndex(index)); + } + void writeHeader(CompactBufferWriter &writer, JSValueType type, uint32_t regCode) const; public: static RValueAllocation read(CompactBufferReader &reader); @@ -360,19 +367,19 @@ class SnapshotWriter } }; -class MResumePoint; +class MNode; class RecoverWriter { CompactBufferWriter writer_; - uint32_t nframes_; - uint32_t framesWritten_; + uint32_t instructionCount_; + uint32_t instructionsWritten_; public: - SnapshotOffset startRecover(uint32_t frameCount, bool resumeAfter); + SnapshotOffset startRecover(uint32_t instructionCount, bool resumeAfter); - bool writeFrame(const MResumePoint *rp); + bool writeInstruction(const MNode *rp); void endRecover(); @@ -473,6 +480,13 @@ class RecoverReader public: RecoverReader(SnapshotReader &snapshot, const uint8_t *recovers, uint32_t size); + uint32_t numInstructions() const { + return numInstructions_; + } + uint32_t numInstructionsRead() const { + return numInstructionsRead_; + } + bool moreInstructions() const { return numInstructionsRead_ < numInstructions_; } diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 33291318db19..6e26f1971dba 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1402,6 +1402,10 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM ma_mov(Imm32(0), reg, NoSetCond, Signed); } + void incrementInt32Value(const Address &addr) { + add32(Imm32(1), ToPayload(addr)); + } + void cmp32(const Register &lhs, const Imm32 &rhs); void cmp32(const Register &lhs, const Register &rhs); void cmp32(const Operand &lhs, const Imm32 &rhs); diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index d3ed35bbcbb9..3b7b4e6c4b54 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -137,106 +137,116 @@ ToStackIndex(LAllocation *a) } bool -CodeGeneratorShared::encodeAllocations(LSnapshot *snapshot, MResumePoint *resumePoint, - uint32_t *startIndex) +CodeGeneratorShared::encodeAllocation(LSnapshot *snapshot, MDefinition *mir, + uint32_t *allocIndex) { - IonSpew(IonSpew_Codegen, "Encoding %u of resume point %p's operands starting from %u", - resumePoint->numOperands(), (void *) resumePoint, *startIndex); - for (uint32_t allocno = 0, e = resumePoint->numOperands(); allocno < e; allocno++) { - uint32_t i = allocno + *startIndex; - MDefinition *mir = resumePoint->getOperand(allocno); + if (mir->isBox()) + mir = mir->toBox()->getOperand(0); - if (mir->isBox()) - mir = mir->toBox()->getOperand(0); + MIRType type = + mir->isRecoveredOnBailout() ? MIRType_None : + mir->isUnused() ? MIRType_MagicOptimizedOut : + mir->type(); - MIRType type = mir->isUnused() - ? MIRType_MagicOptimizedOut - : mir->type(); + RValueAllocation alloc; - RValueAllocation alloc; - - switch (type) { - case MIRType_Undefined: - alloc = RValueAllocation::Undefined(); - break; - case MIRType_Null: - alloc = RValueAllocation::Null(); - break; - case MIRType_Int32: - case MIRType_String: - case MIRType_Object: - case MIRType_Boolean: - case MIRType_Double: - case MIRType_Float32: - { - LAllocation *payload = snapshot->payloadOfSlot(i); - JSValueType valueType = ValueTypeFromMIRType(type); - if (payload->isMemory()) { - if (type == MIRType_Float32) - alloc = RValueAllocation::Float32(ToStackIndex(payload)); - else - alloc = RValueAllocation::Typed(valueType, ToStackIndex(payload)); - } else if (payload->isGeneralReg()) { - alloc = RValueAllocation::Typed(valueType, ToRegister(payload)); - } else if (payload->isFloatReg()) { - FloatRegister reg = ToFloatRegister(payload); - if (type == MIRType_Float32) - alloc = RValueAllocation::Float32(reg); - else - alloc = RValueAllocation::Double(reg); - } else { - MConstant *constant = mir->toConstant(); - uint32_t index; - if (!graph.addConstantToPool(constant->value(), &index)) - return false; - alloc = RValueAllocation::ConstantPool(index); - } - break; - } - case MIRType_MagicOptimizedArguments: - case MIRType_MagicOptimizedOut: - { - uint32_t index; - JSWhyMagic why = (type == MIRType_MagicOptimizedArguments - ? JS_OPTIMIZED_ARGUMENTS - : JS_OPTIMIZED_OUT); - Value v = MagicValue(why); - if (!graph.addConstantToPool(v, &index)) - return false; - alloc = RValueAllocation::ConstantPool(index); - break; - } - default: - { - JS_ASSERT(mir->type() == MIRType_Value); - LAllocation *payload = snapshot->payloadOfSlot(i); -#ifdef JS_NUNBOX32 - LAllocation *type = snapshot->typeOfSlot(i); - if (type->isRegister()) { - if (payload->isRegister()) - alloc = RValueAllocation::Untyped(ToRegister(type), ToRegister(payload)); - else - alloc = RValueAllocation::Untyped(ToRegister(type), ToStackIndex(payload)); - } else { - if (payload->isRegister()) - alloc = RValueAllocation::Untyped(ToStackIndex(type), ToRegister(payload)); - else - alloc = RValueAllocation::Untyped(ToStackIndex(type), ToStackIndex(payload)); - } -#elif JS_PUNBOX64 - if (payload->isRegister()) - alloc = RValueAllocation::Untyped(ToRegister(payload)); - else - alloc = RValueAllocation::Untyped(ToStackIndex(payload)); -#endif - break; - } + switch (type) { + case MIRType_None: + { + MOZ_ASSERT(mir->isRecoveredOnBailout()); + uint32_t index = 0; + LRecoverInfo *recoverInfo = snapshot->recoverInfo(); + MNode **it = recoverInfo->begin(), **end = recoverInfo->end(); + while (it != end && mir != *it) { + ++it; + ++index; } - snapshots_.add(alloc); + // This MDefinition is recovered, thus it should be listed in the + // LRecoverInfo. + MOZ_ASSERT(it != end && mir == *it); + alloc = RValueAllocation::RecoverInstruction(index); + break; + } + case MIRType_Undefined: + alloc = RValueAllocation::Undefined(); + break; + case MIRType_Null: + alloc = RValueAllocation::Null(); + break; + case MIRType_Int32: + case MIRType_String: + case MIRType_Object: + case MIRType_Boolean: + case MIRType_Double: + case MIRType_Float32: + { + LAllocation *payload = snapshot->payloadOfSlot(*allocIndex); + JSValueType valueType = ValueTypeFromMIRType(type); + if (payload->isMemory()) { + if (type == MIRType_Float32) + alloc = RValueAllocation::Float32(ToStackIndex(payload)); + else + alloc = RValueAllocation::Typed(valueType, ToStackIndex(payload)); + } else if (payload->isGeneralReg()) { + alloc = RValueAllocation::Typed(valueType, ToRegister(payload)); + } else if (payload->isFloatReg()) { + FloatRegister reg = ToFloatRegister(payload); + if (type == MIRType_Float32) + alloc = RValueAllocation::Float32(reg); + else + alloc = RValueAllocation::Double(reg); + } else { + MConstant *constant = mir->toConstant(); + uint32_t index; + if (!graph.addConstantToPool(constant->value(), &index)) + return false; + alloc = RValueAllocation::ConstantPool(index); + } + break; + } + case MIRType_MagicOptimizedArguments: + case MIRType_MagicOptimizedOut: + { + uint32_t index; + JSWhyMagic why = (type == MIRType_MagicOptimizedArguments + ? JS_OPTIMIZED_ARGUMENTS + : JS_OPTIMIZED_OUT); + Value v = MagicValue(why); + if (!graph.addConstantToPool(v, &index)) + return false; + alloc = RValueAllocation::ConstantPool(index); + break; + } + default: + { + JS_ASSERT(mir->type() == MIRType_Value); + LAllocation *payload = snapshot->payloadOfSlot(*allocIndex); +#ifdef JS_NUNBOX32 + LAllocation *type = snapshot->typeOfSlot(*allocIndex); + if (type->isRegister()) { + if (payload->isRegister()) + alloc = RValueAllocation::Untyped(ToRegister(type), ToRegister(payload)); + else + alloc = RValueAllocation::Untyped(ToRegister(type), ToStackIndex(payload)); + } else { + if (payload->isRegister()) + alloc = RValueAllocation::Untyped(ToStackIndex(type), ToRegister(payload)); + else + alloc = RValueAllocation::Untyped(ToStackIndex(type), ToStackIndex(payload)); + } +#elif JS_PUNBOX64 + if (payload->isRegister()) + alloc = RValueAllocation::Untyped(ToRegister(payload)); + else + alloc = RValueAllocation::Untyped(ToStackIndex(payload)); +#endif + break; + } } - *startIndex += resumePoint->numOperands(); + snapshots_.add(alloc); + *allocIndex += mir->isRecoveredOnBailout() ? 0 : 1; return true; } @@ -246,21 +256,18 @@ CodeGeneratorShared::encode(LRecoverInfo *recover) if (recover->recoverOffset() != INVALID_RECOVER_OFFSET) return true; - uint32_t frameCount = recover->mir()->frameCount(); - IonSpew(IonSpew_Snapshots, "Encoding LRecoverInfo %p (frameCount %u)", - (void *)recover, frameCount); + uint32_t numInstructions = recover->numInstructions(); + IonSpew(IonSpew_Snapshots, "Encoding LRecoverInfo %p (frameCount %u, instructions %u)", + (void *)recover, recover->mir()->frameCount(), numInstructions); MResumePoint::Mode mode = recover->mir()->mode(); JS_ASSERT(mode != MResumePoint::Outer); bool resumeAfter = (mode == MResumePoint::ResumeAfter); - RecoverOffset offset = recovers_.startRecover(frameCount, resumeAfter); + RecoverOffset offset = recovers_.startRecover(numInstructions, resumeAfter); - for (MResumePoint **it = recover->begin(), **end = recover->end(); - it != end; - ++it) - { - if (!recovers_.writeFrame(*it)) + for (MNode **it = recover->begin(), **end = recover->end(); it != end; ++it) { + if (!recovers_.writeInstruction(*it)) return false; } @@ -307,17 +314,17 @@ CodeGeneratorShared::encode(LSnapshot *snapshot) snapshots_.trackSnapshot(pcOpcode, mirOpcode, mirId, lirOpcode, lirId); #endif - uint32_t startIndex = 0; - for (MResumePoint **it = recoverInfo->begin(), **end = recoverInfo->end(); - it != end; - ++it) - { - MResumePoint *mir = *it; - if (!encodeAllocations(snapshot, mir, &startIndex)) + uint32_t allocIndex = 0; + LRecoverInfo::OperandIter it(recoverInfo->begin()); + LRecoverInfo::OperandIter end(recoverInfo->end()); + for (; it != end; ++it) { + DebugOnly allocWritten = snapshots_.allocWritten(); + if (!encodeAllocation(snapshot, *it, &allocIndex)) return false; + MOZ_ASSERT(allocWritten + 1 == snapshots_.allocWritten()); } - MOZ_ASSERT(snapshots_.allocWritten() == snapshot->numSlots()); + MOZ_ASSERT(allocIndex == snapshot->numSlots()); snapshots_.endSnapshot(); snapshot->setSnapshotOffset(offset); return !snapshots_.oom(); diff --git a/js/src/jit/shared/CodeGenerator-shared.h b/js/src/jit/shared/CodeGenerator-shared.h index 4fe73994339c..c58bf86888c5 100644 --- a/js/src/jit/shared/CodeGenerator-shared.h +++ b/js/src/jit/shared/CodeGenerator-shared.h @@ -272,7 +272,7 @@ class CodeGeneratorShared : public LInstructionVisitor // false on failure. bool encode(LRecoverInfo *recover); bool encode(LSnapshot *snapshot); - bool encodeAllocations(LSnapshot *snapshot, MResumePoint *resumePoint, uint32_t *startIndex); + bool encodeAllocation(LSnapshot *snapshot, MDefinition *def, uint32_t *startIndex); // Attempts to assign a BailoutId to a snapshot, if one isn't already set. // If the bailout table is full, this returns false, which is not a fatal diff --git a/js/src/jit/shared/Lowering-shared.cpp b/js/src/jit/shared/Lowering-shared.cpp index eee7abae83ae..768cbc0bed5e 100644 --- a/js/src/jit/shared/Lowering-shared.cpp +++ b/js/src/jit/shared/Lowering-shared.cpp @@ -74,49 +74,51 @@ LIRGeneratorShared::getRecoverInfo(MResumePoint *rp) LSnapshot * LIRGeneratorShared::buildSnapshot(LInstruction *ins, MResumePoint *rp, BailoutKind kind) { - LRecoverInfo *recover = getRecoverInfo(rp); - if (!recover) + LRecoverInfo *recoverInfo = getRecoverInfo(rp); + if (!recoverInfo) return nullptr; - LSnapshot *snapshot = LSnapshot::New(gen, recover, kind); + LSnapshot *snapshot = LSnapshot::New(gen, recoverInfo, kind); if (!snapshot) return nullptr; - size_t i = 0; - for (MResumePoint **it = recover->begin(), **end = recover->end(); it != end; ++it) { - MResumePoint *mir = *it; - for (size_t j = 0, e = mir->numOperands(); j < e; ++i, ++j) { - MDefinition *ins = mir->getOperand(j); + size_t index = 0; + LRecoverInfo::OperandIter it(recoverInfo->begin()); + LRecoverInfo::OperandIter end(recoverInfo->end()); + for (; it != end; ++it) { + MDefinition *ins = *it; + if (ins->isRecoveredOnBailout()) + continue; - LAllocation *type = snapshot->typeOfSlot(i); - LAllocation *payload = snapshot->payloadOfSlot(i); + LAllocation *type = snapshot->typeOfSlot(index); + LAllocation *payload = snapshot->payloadOfSlot(index); + ++index; - if (ins->isBox()) - ins = ins->toBox()->getOperand(0); + if (ins->isBox()) + ins = ins->toBox()->getOperand(0); - // Guards should never be eliminated. - JS_ASSERT_IF(ins->isUnused(), !ins->isGuard()); + // Guards should never be eliminated. + JS_ASSERT_IF(ins->isUnused(), !ins->isGuard()); - // Snapshot operands other than constants should never be - // emitted-at-uses. Try-catch support depends on there being no - // code between an instruction and the LOsiPoint that follows it. - JS_ASSERT_IF(!ins->isConstant(), !ins->isEmittedAtUses()); + // Snapshot operands other than constants should never be + // emitted-at-uses. Try-catch support depends on there being no + // code between an instruction and the LOsiPoint that follows it. + JS_ASSERT_IF(!ins->isConstant(), !ins->isEmittedAtUses()); - // The register allocation will fill these fields in with actual - // register/stack assignments. During code generation, we can restore - // interpreter state with the given information. Note that for - // constants, including known types, we record a dummy placeholder, - // since we can recover the same information, much cleaner, from MIR. - if (ins->isConstant() || ins->isUnused()) { - *type = LConstantIndex::Bogus(); - *payload = LConstantIndex::Bogus(); - } else if (ins->type() != MIRType_Value) { - *type = LConstantIndex::Bogus(); - *payload = use(ins, LUse::KEEPALIVE); - } else { - *type = useType(ins, LUse::KEEPALIVE); - *payload = usePayload(ins, LUse::KEEPALIVE); - } + // The register allocation will fill these fields in with actual + // register/stack assignments. During code generation, we can restore + // interpreter state with the given information. Note that for + // constants, including known types, we record a dummy placeholder, + // since we can recover the same information, much cleaner, from MIR. + if (ins->isConstant() || ins->isUnused()) { + *type = LConstantIndex::Bogus(); + *payload = LConstantIndex::Bogus(); + } else if (ins->type() != MIRType_Value) { + *type = LConstantIndex::Bogus(); + *payload = use(ins, LUse::KEEPALIVE); + } else { + *type = useType(ins, LUse::KEEPALIVE); + *payload = usePayload(ins, LUse::KEEPALIVE); } } @@ -128,40 +130,42 @@ LIRGeneratorShared::buildSnapshot(LInstruction *ins, MResumePoint *rp, BailoutKi LSnapshot * LIRGeneratorShared::buildSnapshot(LInstruction *ins, MResumePoint *rp, BailoutKind kind) { - LRecoverInfo *recover = getRecoverInfo(rp); - if (!recover) + LRecoverInfo *recoverInfo = getRecoverInfo(rp); + if (!recoverInfo) return nullptr; - LSnapshot *snapshot = LSnapshot::New(gen, recover, kind); + LSnapshot *snapshot = LSnapshot::New(gen, recoverInfo, kind); if (!snapshot) return nullptr; - size_t i = 0; - for (MResumePoint **it = recover->begin(), **end = recover->end(); it != end; ++it) { - MResumePoint *mir = *it; - for (size_t j = 0, e = mir->numOperands(); j < e; ++i, ++j) { - MDefinition *def = mir->getOperand(j); + size_t index = 0; + LRecoverInfo::OperandIter it(recoverInfo->begin()); + LRecoverInfo::OperandIter end(recoverInfo->end()); + for (; it != end; ++it) { + MDefinition *def = *it; - if (def->isBox()) - def = def->toBox()->getOperand(0); + if (def->isRecoveredOnBailout()) + continue; - // Guards should never be eliminated. - JS_ASSERT_IF(def->isUnused(), !def->isGuard()); + if (def->isBox()) + def = def->toBox()->getOperand(0); - // Snapshot operands other than constants should never be - // emitted-at-uses. Try-catch support depends on there being no - // code between an instruction and the LOsiPoint that follows it. - JS_ASSERT_IF(!def->isConstant(), !def->isEmittedAtUses()); + // Guards should never be eliminated. + JS_ASSERT_IF(def->isUnused(), !def->isGuard()); - LAllocation *a = snapshot->getEntry(i); + // Snapshot operands other than constants should never be + // emitted-at-uses. Try-catch support depends on there being no + // code between an instruction and the LOsiPoint that follows it. + JS_ASSERT_IF(!def->isConstant(), !def->isEmittedAtUses()); - if (def->isUnused()) { - *a = LConstantIndex::Bogus(); - continue; - } + LAllocation *a = snapshot->getEntry(index++); - *a = useKeepaliveOrConstant(def); + if (def->isUnused()) { + *a = LConstantIndex::Bogus(); + continue; } + + *a = useKeepaliveOrConstant(def); } return snapshot; diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index 2c11e9a829ad..e495210d3e7c 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -1237,6 +1237,10 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared } } + void incrementInt32Value(const Address &addr) { + addPtr(Imm32(1), addr); + } + // If source is a double, load it into dest. If source is int32, // convert it to double. Else, branch to failure. void ensureDouble(const ValueOperand &source, FloatRegister dest, Label *failure) { diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index 17de7a7d0719..d7131e73c8ac 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -1035,6 +1035,9 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared bind(&noOverflow); } + void incrementInt32Value(const Address &addr) { + addl(Imm32(1), payloadOf(addr)); + } // If source is a double, load it into dest. If source is int32, // convert it to double. Else, branch to failure. diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 1ddb86dff637..a28285c8db6e 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -235,13 +235,10 @@ class AutoVectorRooter : protected AutoGCRooter return vector.reserve(newLength); } - T &operator[](size_t i) { return vector[i]; } - const T &operator[](size_t i) const { return vector[i]; } - - JS::MutableHandle handleAt(size_t i) { + JS::MutableHandle operator[](size_t i) { return JS::MutableHandle::fromMarkedLocation(&vector[i]); } - JS::Handle handleAt(size_t i) const { + JS::Handle operator[](size_t i) const { return JS::Handle::fromMarkedLocation(&vector[i]); } diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 24da1769c7b7..460ffd36fe65 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -1744,14 +1744,14 @@ MergeSortByKey(K keys, size_t len, K scratch, C comparator, AutoValueVector *vec do { size_t k = keys[j].elementIndex; keys[j].elementIndex = j; - (*vec)[j] = (*vec)[k]; + (*vec)[j].set((*vec)[k]); j = k; } while (j != i); // We could assert the loop invariant that |i == keys[i].elementIndex| // here if we synced |keys[i].elementIndex|. But doing so would render // the assertion vacuous, so don't bother, even in debug builds. - (*vec)[i] = tv; + (*vec)[i].set(tv); } return true; @@ -1820,7 +1820,7 @@ SortNumerically(JSContext *cx, AutoValueVector *vec, size_t len, ComparatorMatch return false; double dv; - if (!ToNumber(cx, vec->handleAt(i), &dv)) + if (!ToNumber(cx, (*vec)[i], &dv)) return false; NumericElement el = { dv, i }; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 682651e93591..5693476fb3c8 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -2771,8 +2771,10 @@ BeginMarkPhase(JSRuntime *rt) } if (!rt->gcShouldCleanUpEverything) { +#ifdef JS_ION if (JSCompartment *comp = jit::TopmostJitActivationCompartment(rt)) comp->zone()->setPreservingCode(true); +#endif } /* diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 9cfa5f0e7e26..b59193d0f987 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -351,6 +351,26 @@ TypeSet::mightBeMIRType(jit::MIRType type) } } +bool +TypeSet::objectsAreSubset(TypeSet *other) +{ + if (other->unknownObject()) + return true; + + if (unknownObject()) + return false; + + for (unsigned i = 0; i < getObjectCount(); i++) { + TypeObjectKey *obj = getObject(i); + if (!obj) + continue; + if (!other->hasType(Type::ObjectType(obj))) + return false; + } + + return true; +} + bool TypeSet::isSubset(TypeSet *other) { diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index 6efeb4116c36..89fa7a2d2045 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -584,6 +584,12 @@ class TypeSet */ bool isSubset(TypeSet *other); + /* + * Get whether the objects in this TypeSet are a subset of the objects + * in other. + */ + bool objectsAreSubset(TypeSet *other); + /* Forward all types in this set to the specified constraint. */ bool addTypesToConstraint(JSContext *cx, TypeConstraint *constraint); diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 1c6e320b841b..e907a98416fe 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -463,7 +463,7 @@ js::math_imul(JSContext *cx, unsigned argc, Value *vp) // Implements Math.fround (20.2.2.16) up to step 3 bool -js::RoundFloat32(JSContext *cx, Handle v, float *out) +js::RoundFloat32(JSContext *cx, HandleValue v, float *out) { double d; bool success = ToNumber(cx, v, &d); @@ -471,6 +471,17 @@ js::RoundFloat32(JSContext *cx, Handle v, float *out) return success; } +bool +js::RoundFloat32(JSContext *cx, HandleValue arg, MutableHandleValue res) +{ + float f; + if (!RoundFloat32(cx, arg, &f)) + return false; + + res.setDouble(static_cast(f)); + return true; +} + bool js::math_fround(JSContext *cx, unsigned argc, Value *vp) { diff --git a/js/src/jsmath.h b/js/src/jsmath.h index 2e0c5746d63b..674d4277065f 100644 --- a/js/src/jsmath.h +++ b/js/src/jsmath.h @@ -110,7 +110,10 @@ extern bool math_imul(JSContext *cx, unsigned argc, js::Value *vp); extern bool -RoundFloat32(JSContext *cx, Handle v, float *out); +RoundFloat32(JSContext *cx, HandleValue v, float *out); + +extern bool +RoundFloat32(JSContext *cx, HandleValue arg, MutableHandleValue res); extern bool math_fround(JSContext *cx, unsigned argc, js::Value *vp); diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 274550954caf..c9f52631478b 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1003,7 +1003,7 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props) bool dummy; Rooted arr(cx, &obj->as()); for (size_t i = 0, len = ids.length(); i < len; i++) { - if (!DefinePropertyOnArray(cx, arr, ids.handleAt(i), descs[i], true, &dummy)) + if (!DefinePropertyOnArray(cx, arr, ids[i], descs[i], true, &dummy)) return false; } return true; @@ -1017,7 +1017,7 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props) if (obj->is()) { for (size_t i = 0, len = ids.length(); i < len; i++) { RootedValue pd(cx, descs[i].pd()); - if (!Proxy::defineProperty(cx, obj, ids.handleAt(i), pd)) + if (!Proxy::defineProperty(cx, obj, ids[i], pd)) return false; } return true; @@ -1028,7 +1028,7 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props) bool dummy; for (size_t i = 0, len = ids.length(); i < len; i++) { - if (!DefinePropertyOnObject(cx, obj, ids.handleAt(i), descs[i], true, &dummy)) + if (!DefinePropertyOnObject(cx, obj, ids[i], descs[i], true, &dummy)) return false; } @@ -1743,7 +1743,7 @@ JS_CopyPropertiesFrom(JSContext *cx, HandleObject target, HandleObject obj) return false; for (size_t i = 0; i < props.length(); ++i) { - if (!JS_CopyPropertyFrom(cx, props.handleAt(i), target, obj)) + if (!JS_CopyPropertyFrom(cx, props[i], target, obj)) return false; } @@ -2013,7 +2013,7 @@ js::XDRObjectLiteral(XDRState *xdr, MutableHandleObject obj) } JS_ASSERT(it.front().hasDefaultGetter()); - ids[it.front().slot()] = it.front().propid(); + ids[it.front().slot()].set(it.front().propid()); } } diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 6deabd884898..9760edcc4ea5 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -243,7 +243,7 @@ BaseProxyHandler::keys(JSContext *cx, HandleObject proxy, AutoIdVector &props) if (!getOwnPropertyDescriptor(cx, proxy, id, &desc)) return false; if (desc.object() && desc.isEnumerable()) - props[i++] = id; + props[i++].set(id); } JS_ASSERT(i <= props.length()); @@ -1582,7 +1582,7 @@ ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleVa // step iii for (uint32_t j = 0; j < i; ++j) { - if (props[j] == id) { + if (props[j].get() == id) { ReportInvalidTrapResult(cx, proxy, trapName); return false; } @@ -1618,7 +1618,7 @@ ArrayToIdVector(JSContext *cx, HandleObject proxy, HandleObject target, HandleVa bool found = false; for (size_t j = 0; j < props.length(); ++j) { - if (props[j] == id) { + if (props[j].get() == id) { found = true; break; } @@ -2453,7 +2453,7 @@ js::AppendUnique(JSContext *cx, AutoIdVector &base, AutoIdVector &others) for (size_t i = 0; i < others.length(); ++i) { bool unique = true; for (size_t j = 0; j < base.length(); ++j) { - if (others[i] == base[j]) { + if (others[i].get() == base[j]) { unique = false; break; } diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 9e92c92c9c40..4e66c5a113bb 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -235,7 +235,7 @@ XDRScriptBindings(XDRState *xdr, LifoAllocScope &las, unsigned numArgs, ui RootedAtom atom(cx); if (!XDRAtom(xdr, &atom)) return false; - atoms[i] = StringValue(atom); + atoms[i].setString(atom); } Binding *bindingArray = las.alloc().newArrayUninitialized(nameCount); diff --git a/js/src/jswrapper.cpp b/js/src/jswrapper.cpp index 2348316245d2..e2d22205a319 100644 --- a/js/src/jswrapper.cpp +++ b/js/src/jswrapper.cpp @@ -410,7 +410,7 @@ Reify(JSContext *cx, JSCompartment *origin, MutableHandleValue vp) if (!ValueToId(cx, v, &id)) return false; keys.infallibleAppend(id); - if (!origin->wrapId(cx, &keys[i])) + if (!origin->wrapId(cx, keys[i].address())) return false; } } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 4c182c067ad2..3f5e3760c148 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -2802,7 +2802,7 @@ Debugger::findScripts(JSContext *cx, unsigned argc, Value *vp) result->ensureDenseInitializedLength(cx, 0, scripts.length()); for (size_t i = 0; i < scripts.length(); i++) { - JSObject *scriptObject = dbg->wrapScript(cx, scripts.handleAt(i)); + JSObject *scriptObject = dbg->wrapScript(cx, scripts[i]); if (!scriptObject) return false; result->setDenseElement(i, ObjectValue(*scriptObject)); @@ -4709,8 +4709,8 @@ DebuggerGenericEval(JSContext *cx, const char *fullMethodName, const Value &code return false; } for (size_t i = 0; i < keys.length(); i++) { - MutableHandleValue valp = values.handleAt(i); - if (!JSObject::getGeneric(cx, bindingsobj, bindingsobj, keys.handleAt(i), valp) || + MutableHandleValue valp = values[i]; + if (!JSObject::getGeneric(cx, bindingsobj, bindingsobj, keys[i], valp) || !dbg->unwrapDebuggeeValue(cx, valp)) { return false; @@ -4786,7 +4786,7 @@ DebuggerGenericEval(JSContext *cx, const char *fullMethodName, const Value &code RootedId id(cx); for (size_t i = 0; i < keys.length(); i++) { id = keys[i]; - MutableHandleValue val = values.handleAt(i); + MutableHandleValue val = values[i]; if (!cx->compartment()->wrap(cx, val) || !DefineNativeProperty(cx, env, id, val, nullptr, nullptr, 0)) { @@ -5223,11 +5223,11 @@ DebuggerObject_getOwnPropertyNames(JSContext *cx, unsigned argc, Value *vp) vals[i].setString(str); } else if (JSID_IS_ATOM(id)) { vals[i].setString(JSID_TO_STRING(id)); - if (!cx->compartment()->wrap(cx, vals.handleAt(i))) + if (!cx->compartment()->wrap(cx, vals[i])) return false; } else { vals[i].setObject(*JSID_TO_OBJECT(id)); - if (!dbg->wrapDebuggeeValue(cx, vals.handleAt(i))) + if (!dbg->wrapDebuggeeValue(cx, vals[i])) return false; } } @@ -5322,14 +5322,14 @@ DebuggerObject_defineProperties(JSContext *cx, unsigned argc, Value *vp) if (!rewrappedIds.append(JSID_VOID) || !rewrappedDescs.append()) return false; id = ids[i]; - if (!unwrappedDescs[i].wrapInto(cx, obj, id, &rewrappedIds[i], &rewrappedDescs[i])) + if (!unwrappedDescs[i].wrapInto(cx, obj, id, rewrappedIds[i].address(), &rewrappedDescs[i])) return false; } ErrorCopier ec(ac, dbg->toJSObject()); for (size_t i = 0; i < n; i++) { bool dummy; - if (!DefineProperty(cx, obj, rewrappedIds.handleAt(i), + if (!DefineProperty(cx, obj, rewrappedIds[i], rewrappedDescs[i], true, &dummy)) { return false; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 79712ed341fb..054286fc6a85 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -3230,31 +3230,15 @@ END_CASE(JSOP_INITELEM_INC) CASE(JSOP_SPREAD) { - int32_t count = REGS.sp[-2].toInt32(); + HandleValue countVal = REGS.stackHandleAt(-2); RootedObject &arr = rootObject0; arr = ®S.sp[-3].toObject(); - const Value iterable = REGS.sp[-1]; - ForOfIterator iter(cx); - RootedValue &iterVal = rootValue0; - iterVal.set(iterable); - if (!iter.init(iterVal)) + HandleValue iterable = REGS.stackHandleAt(-1); + MutableHandleValue resultCountVal = REGS.stackHandleAt(-2); + + if (!SpreadOperation(cx, arr, countVal, iterable, resultCountVal)) goto error; - while (true) { - bool done; - if (!iter.next(&iterVal, &done)) - goto error; - if (done) - break; - if (count == INT32_MAX) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, - JSMSG_SPREAD_TOO_LARGE); - goto error; - } - if (!JSObject::defineElement(cx, arr, count++, iterVal, nullptr, nullptr, - JSPROP_ENUMERATE)) - goto error; - } - REGS.sp[-2].setInt32(count); + REGS.sp--; } END_CASE(JSOP_SPREAD) @@ -3963,3 +3947,31 @@ js::InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, H return InitGetterSetterOperation(cx, pc, obj, id, val); } + +bool +js::SpreadOperation(JSContext *cx, HandleObject arr, HandleValue countVal, + HandleValue iterable, MutableHandleValue resultCountVal) +{ + int32_t count = countVal.toInt32(); + ForOfIterator iter(cx); + RootedValue iterVal(cx, iterable); + if (!iter.init(iterVal)) + return false; + while (true) { + bool done; + if (!iter.next(&iterVal, &done)) + return false; + if (done) + break; + if (count == INT32_MAX) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, + JSMSG_SPREAD_TOO_LARGE); + return false; + } + if (!JSObject::defineElement(cx, arr, count++, iterVal, nullptr, nullptr, + JSPROP_ENUMERATE)) + return false; + } + resultCountVal.setInt32(count); + return true; +} diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 5a0d5da41f15..1c4c1a51eed4 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -462,6 +462,10 @@ bool InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, HandleValue idval, HandleObject val); +bool +SpreadOperation(JSContext *cx, HandleObject arr, HandleValue countVal, + HandleValue iterable, MutableHandleValue resultCountVal); + inline bool SetConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName name, HandleValue rval) { diff --git a/js/src/vm/OldDebugAPI.cpp b/js/src/vm/OldDebugAPI.cpp index b67323c7aab8..536268b989b3 100644 --- a/js/src/vm/OldDebugAPI.cpp +++ b/js/src/vm/OldDebugAPI.cpp @@ -677,7 +677,7 @@ JS_GetPropertyDescArray(JSContext *cx, JS::HandleObject obj, JSPropertyDescArray pd[i].id = IdToValue(props[i]); if (!AddValueRoot(cx, &pd[i].value, nullptr)) goto bad; - if (!Proxy::get(cx, obj, obj, props.handleAt(i), MutableHandleValue::fromMarkedLocation(&pd[i].value))) + if (!Proxy::get(cx, obj, obj, props[i], MutableHandleValue::fromMarkedLocation(&pd[i].value))) goto bad; } diff --git a/js/src/vm/SPSProfiler.h b/js/src/vm/SPSProfiler.h index 0dd8e7e072fd..5d4fa64ff9df 100644 --- a/js/src/vm/SPSProfiler.h +++ b/js/src/vm/SPSProfiler.h @@ -415,16 +415,12 @@ class SPSInstrumentation bool push(JSScript *script, Assembler &masm, Register scratch, bool inlinedFunction = false) { if (!enabled()) return true; -#ifdef JS_ION - if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) { -#endif + if (!inlinedFunction) { const char *string = profiler_->profileString(script, script->functionNonDelazifying()); if (string == nullptr) return false; masm.spsPushFrame(profiler_, string, script, scratch); -#ifdef JS_ION } -#endif setPushed(script); return true; } @@ -440,9 +436,7 @@ class SPSInstrumentation if (!enabled()) return; -#ifdef JS_ION - if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) -#endif + if (!inlinedFunction) masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch); setPushed(script); @@ -459,22 +453,17 @@ class SPSInstrumentation if (enabled() && frame->script && frame->left++ == 0) { jsbytecode *updatePC = pc; JSScript *script = frame->script; -#ifdef JS_ION if (!inlinedFunction) { - // We may be leaving an inlined frame for entry into a C++ - // frame. If profileInlineFrames is turned off, use the top - // script's pc offset instead of the innermost script's. - if (!jit::js_JitOptions.profileInlineFrames && inliningDepth() > 0) { + // We may be leaving an inlined frame for entry into a C++ frame. + // Use the top script's pc offset instead of the innermost script's. + if (inliningDepth() > 0) { JS_ASSERT(frames[0].pc); updatePC = frames[0].pc; script = frames[0].script; } } -#endif -#ifdef JS_ION - if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) -#endif + if (!inlinedFunction) masm.spsUpdatePCIdx(profiler_, script->pcToOffset(updatePC), scratch); } } @@ -489,9 +478,7 @@ class SPSInstrumentation if (frame->skipNext) { frame->skipNext = false; } else { -#ifdef JS_ION - if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) -#endif + if (!inlinedFunction) masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch); } } @@ -505,9 +492,7 @@ class SPSInstrumentation if (enabled()) { JS_ASSERT(frame->left == 0); JS_ASSERT(frame->script); -#ifdef JS_ION - if (!inlinedFunction || jit::js_JitOptions.profileInlineFrames) -#endif + if (!inlinedFunction) masm.spsPopFrame(profiler_, scratch); } } diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index 91e73c79c82a..7136d5dcb59e 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -802,7 +802,7 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, return false; for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) - shapes[obj->shapeToIndex(r.front())] = &r.front(); + shapes[obj->shapeToIndex(r.front())].set(&r.front()); RootedShape shape(cx); RootedId propid(cx); @@ -853,7 +853,7 @@ CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, Handle r(srcBlock->lastProperty()); !r.empty(); r.popFront()) - shapes[srcBlock->shapeToIndex(r.front())] = &r.front(); + shapes[srcBlock->shapeToIndex(r.front())].set(&r.front()); for (Shape **p = shapes.begin(); p != shapes.end(); ++p) { RootedId id(cx, (*p)->propid()); @@ -1910,7 +1910,7 @@ DebugScopes::onPopCall(AbstractFramePtr frame, JSContext *cx) if (script->analyzedArgsUsage() && script->needsArgsObj() && frame.hasArgsObj()) { for (unsigned i = 0; i < frame.numFormalArgs(); ++i) { if (script->formalLivesInArgumentsObject(i)) - vec[i] = frame.argsObj().arg(i); + vec[i].set(frame.argsObj().arg(i)); } } diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index 4e09cd9db3e8..52a8e214c071 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -690,7 +690,7 @@ js::NewReshapedObject(JSContext *cx, HandleTypeObject type, JSObject *parent, } Shape *nshape = shape; while (!nshape->isEmptyShape()) { - ids[nshape->slot()] = nshape->propid(); + ids[nshape->slot()].set(nshape->propid()); nshape = nshape->previous(); } } diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index f83d6d1aa18f..9be647107d1b 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -1250,7 +1250,7 @@ JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, Val return false; vp->setObject(*obj); - allObjs[placeholderIndex] = *vp; + allObjs[placeholderIndex].set(*vp); return true; } diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index 6ccbe373d46b..640747228f4a 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -391,7 +391,7 @@ CloneNonReflectorsRead(JSContext *cx, JSStructuredCloneReader *reader, uint32_t size_t idx; if (JS_ReadBytes(reader, &idx, sizeof(size_t))) { - RootedObject reflector(cx, reflectors->handleAt(idx)); + RootedObject reflector(cx, (*reflectors)[idx]); MOZ_ASSERT(reflector, "No object pointer?"); MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!"); diff --git a/js/xpconnect/wrappers/FilteringWrapper.cpp b/js/xpconnect/wrappers/FilteringWrapper.cpp index 1f2dcc1bcd13..849d78191188 100644 --- a/js/xpconnect/wrappers/FilteringWrapper.cpp +++ b/js/xpconnect/wrappers/FilteringWrapper.cpp @@ -35,7 +35,7 @@ Filter(JSContext *cx, HandleObject wrapper, AutoIdVector &props) for (size_t n = 0; n < props.length(); ++n) { id = props[n]; if (Policy::check(cx, wrapper, id, Wrapper::GET)) - props[w++] = id; + props[w++].set(id); else if (JS_IsExceptionPending(cx)) return false; } diff --git a/media/libopus/moz.build b/media/libopus/moz.build index 4a0bb5927de5..2761e9e1afb9 100644 --- a/media/libopus/moz.build +++ b/media/libopus/moz.build @@ -19,6 +19,11 @@ DEFINES['OPUS_BUILD'] = True DEFINES['OPUS_VERSION'] = '"v1.1-mozilla"' DEFINES['USE_ALLOCA'] = True +# We only need to export symbols if we're built into libgkmedias +# instead of libxul. +if not CONFIG['GKMEDIAS_SHARED_LIBRARY']: + DEFINES['OPUS_EXPORT'] = '' + if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['GNU_AS']: DEFINES['OPUS_ARM_ASM'] = True DEFINES['OPUS_ARM_EXTERNAL_ASM'] = True diff --git a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp index 46f5c5590544..affca7bceceb 100644 --- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp +++ b/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp @@ -45,6 +45,12 @@ using namespace android; namespace mozilla { +static uint8_t kNALStartCode[] = { 0x00, 0x00, 0x00, 0x01 }; +enum { + kNALTypeSPS = 7, + kNALTypePPS = 8, +}; + // NS_INLINE_DECL_THREADSAFE_REFCOUNTING() cannot be used directly in // ImageNativeHandle below because the return type of webrtc::NativeHandle // AddRef()/Release() conflicts with those defined in macro. To avoid another @@ -151,13 +157,11 @@ public: MonitorAutoLock lock(mMonitor); while (true) { if (mInputFrames.empty()) { - ALOGE("Waiting OMXOutputDrain"); // Wait for new input. lock.Wait(); } if (mEnding) { - ALOGE("Ending OMXOutputDrain"); // Stop draining. break; } @@ -239,34 +243,35 @@ public: mLooper.clear(); } - // Parse SPS/PPS NALUs. - static sp ParseParamSets(sp& aParamSets) + // Find SPS in input data and extract picture width and height if found. + static status_t ExtractPicDimensions(uint8_t* aData, size_t aSize, + int32_t* aWidth, int32_t* aHeight) { - return MakeAVCCodecSpecificData(aParamSets); + MOZ_ASSERT(aData && aSize > 1); + if ((aData[0] & 0x1f) != kNALTypeSPS) { + return ERROR_MALFORMED; + } + sp sps = new ABuffer(aData, aSize); + FindAVCDimensions(sps, aWidth, aHeight); + return OK; } - // Configure decoder using data returned by ParseParamSets(). - status_t ConfigureWithParamSets(const sp& aParamSets) + // Configure decoder using image width/height. + status_t ConfigureWithPicDimensions(int32_t aWidth, int32_t aHeight) { MOZ_ASSERT(mCodec != nullptr); if (mCodec == nullptr) { return INVALID_OPERATION; } - int32_t width = 0; - bool ok = aParamSets->findInt32(kKeyWidth, &width); - MOZ_ASSERT(ok && width > 0); - int32_t height = 0; - ok = aParamSets->findInt32(kKeyHeight, &height); - MOZ_ASSERT(ok && height > 0); - CODEC_LOGD("OMX:%p decoder config width:%d height:%d", this, width, height); + CODEC_LOGD("OMX:%p decoder width:%d height:%d", this, aWidth, aHeight); sp config = new AMessage(); config->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC); - config->setInt32("width", width); - config->setInt32("height", height); - mWidth = width; - mHeight = height; + config->setInt32("width", aWidth); + config->setInt32("height", aHeight); + mWidth = aWidth; + mHeight = aHeight; sp surface = nullptr; mNativeWindow = new GonkNativeWindow(); @@ -286,8 +291,8 @@ public: FillInput(const webrtc::EncodedImage& aEncoded, bool aIsFirstFrame, int64_t& aRenderTimeMs) { - MOZ_ASSERT(mCodec != nullptr); - if (mCodec == nullptr) { + MOZ_ASSERT(mCodec != nullptr && aEncoded._buffer && aEncoded._length > 0); + if (mCodec == nullptr || !aEncoded._buffer || aEncoded._length == 0) { return INVALID_OPERATION; } @@ -299,19 +304,24 @@ public: return err; } - uint32_t flags = 0; - if (aEncoded._frameType == webrtc::kKeyFrame) { - flags = aIsFirstFrame ? MediaCodec::BUFFER_FLAG_CODECCONFIG : MediaCodec::BUFFER_FLAG_SYNCFRAME; - } - size_t size = aEncoded._length; - MOZ_ASSERT(size); + // Prepend start code to buffer. + size_t size = aEncoded._length + sizeof(kNALStartCode); const sp& omxIn = mInputBuffers.itemAt(index); MOZ_ASSERT(omxIn->capacity() >= size); omxIn->setRange(0, size); // Copying is needed because MediaCodec API doesn't support externallay // allocated buffer as input. - memcpy(omxIn->data(), aEncoded._buffer, size); + uint8_t* dst = omxIn->data(); + memcpy(dst, kNALStartCode, sizeof(kNALStartCode)); + memcpy(dst + sizeof(kNALStartCode), aEncoded._buffer, aEncoded._length); int64_t inputTimeUs = aEncoded._timeStamp * 1000 / 90; // 90kHz -> us. + // Assign input flags according to input buffer NALU and frame types. + uint32_t flags = 0; + if (aEncoded._frameType == webrtc::kKeyFrame) { + int nalType = dst[sizeof(kNALStartCode)] & 0x1f; + flags = (nalType == kNALTypeSPS || nalType == kNALTypePPS) ? + MediaCodec::BUFFER_FLAG_CODECCONFIG : MediaCodec::BUFFER_FLAG_SYNCFRAME; + } err = mCodec->queueInputBuffer(index, 0, size, inputTimeUs, flags); if (err == OK && !(flags & MediaCodec::BUFFER_FLAG_CODECCONFIG)) { if (mOutputDrain == nullptr) { @@ -563,8 +573,6 @@ protected: encoded.capture_time_ms_ = aInputFrame.mRenderTimeMs; encoded._completeFrame = true; - ALOGE("OMX:%p encode frame type:%d size:%u", mOMX, encoded._frameType, encoded._length); - // Prepend SPS/PPS to I-frames unless they were sent last time. SendEncodedDataToCallback(encoded, isIFrame && !mIsPrevOutputParamSets); mIsPrevOutputParamSets = isParamSets; @@ -800,20 +808,22 @@ WebrtcOMXH264VideoDecoder::Decode(const webrtc::EncodedImage& aInputImage, return WEBRTC_VIDEO_CODEC_ERROR; } - ALOGE("WebrtcOMXH264VideoDecoder:%p will decode", this); - bool configured = !!mOMX; if (!configured) { - // Search for SPS/PPS NALUs in input to get decoder config. - sp input = new ABuffer(aInputImage._buffer, aInputImage._length); - sp paramSets = WebrtcOMXDecoder::ParseParamSets(input); - if (NS_WARN_IF(paramSets == nullptr)) { - // Cannot config decoder because SPS/PPS NALUs haven't been seen. + // Search for SPS NALU in input to get width/height config. + int32_t width; + int32_t height; + status_t result = WebrtcOMXDecoder::ExtractPicDimensions(aInputImage._buffer, + aInputImage._length, + &width, &height); + if (result != OK) { + // Cannot config decoder because SPS haven't been seen. + CODEC_LOGI("WebrtcOMXH264VideoDecoder:%p missing SPS in input"); return WEBRTC_VIDEO_CODEC_UNINITIALIZED; } RefPtr omx = new WebrtcOMXDecoder(MEDIA_MIMETYPE_VIDEO_AVC, mCallback); - status_t result = omx->ConfigureWithParamSets(paramSets); + result = omx->ConfigureWithPicDimensions(width, height); if (NS_WARN_IF(result != OK)) { return WEBRTC_VIDEO_CODEC_UNINITIALIZED; } diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp index 5776d2d9a60a..a7be911232c1 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -1896,6 +1896,7 @@ PeerConnectionImpl::IceGatheringStateChange( NS_DISPATCH_NORMAL); } +#ifdef MOZILLA_INTERNAL_API static bool isDone(PCImplIceConnectionState state) { return state != PCImplIceConnectionState::Checking && state != PCImplIceConnectionState::New; @@ -1910,6 +1911,7 @@ static bool isFailed(PCImplIceConnectionState state) { return state == PCImplIceConnectionState::Failed || state == PCImplIceConnectionState::Disconnected; } +#endif nsresult PeerConnectionImpl::IceConnectionStateChange_m(PCImplIceConnectionState aState) @@ -2185,11 +2187,6 @@ PeerConnectionImpl::ExecuteStatsQuery_s(RTCStatsQuery *query) { ASSERT_ON_THREAD(query->iceCtx->thread()); - // NrIceCtx must be destroyed on STS, so it is not safe to dispatch it back - // to main. - RefPtr iceCtxTmp(query->iceCtx); - query->iceCtx = nullptr; - // Gather stats from pipelines provided (can't touch mMedia + stream on STS) for (size_t p = 0; p < query->pipelines.Length(); ++p) { @@ -2333,6 +2330,11 @@ PeerConnectionImpl::ExecuteStatsQuery_s(RTCStatsQuery *query) { &(query->report)); } + // NrIceCtx and NrIceMediaStream must be destroyed on STS, so it is not safe + // to dispatch them back to main. + // We clear streams first to maintain destruction order + query->streams.Clear(); + query->iceCtx = nullptr; return NS_OK; } diff --git a/mfbt/MacroArgs.h b/mfbt/MacroArgs.h index f838b0a9edcb..c8b733821035 100644 --- a/mfbt/MacroArgs.h +++ b/mfbt/MacroArgs.h @@ -75,16 +75,16 @@ * With a prefix of 0.0, it expands to e.g. 0.04. If there are too many * arguments, it expands to the first argument over the limit. If this * exceeding argument is a number, the assertion will fail as there is no - * number than can simultaneously be both > 10 and < 0.1. If the exceeding - * argument is not a number, a compile-time error will still occur because the - * exceeding argument is compared to an int and a double. + * number than can simultaneously be both > 10 and == 0. If the exceeding + * argument is not a number, a compile-time error should still occur due to + * the operations performed on it. */ #define MOZ_MACROARGS_STRINGIFY_HELPER(x) #x #define MOZ_STATIC_ASSERT_VALID_ARG_COUNT(...) \ static_assert( \ sizeof(MOZ_MACROARGS_STRINGIFY_HELPER((__VA_ARGS__))) != sizeof("()") && \ (MOZ_PASTE_PREFIX_AND_ARG_COUNT(1, __VA_ARGS__)) > 10 && \ - (MOZ_PASTE_PREFIX_AND_ARG_COUNT(0.0, __VA_ARGS__)) < 0.1, \ + (int)(MOZ_PASTE_PREFIX_AND_ARG_COUNT(0.0, __VA_ARGS__)) == 0, \ "MOZ_STATIC_ASSERT_VALID_ARG_COUNT requires 1 to 50 arguments") /* ; */ /* diff --git a/mfbt/tests/TestMacroArgs.cpp b/mfbt/tests/TestMacroArgs.cpp index ac87b86a8084..7b4b0a164d22 100644 --- a/mfbt/tests/TestMacroArgs.cpp +++ b/mfbt/tests/TestMacroArgs.cpp @@ -6,6 +6,9 @@ #include "mozilla/MacroArgs.h" +MOZ_STATIC_ASSERT_VALID_ARG_COUNT(1); +MOZ_STATIC_ASSERT_VALID_ARG_COUNT(1, 2); + static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(100, a, b, c) == 1003, ""); static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(, a, b, c) == 3, ""); diff --git a/python/lldbutils/lldbutils/gfx.py b/python/lldbutils/lldbutils/gfx.py index 91f91dd13623..8211a62ada72 100644 --- a/python/lldbutils/lldbutils/gfx.py +++ b/python/lldbutils/lldbutils/gfx.py @@ -30,5 +30,102 @@ def summarize_nscolor(valobj, internal_dict): return colors[color] return color +class RegionSyntheticChildrenProvider: + + def __init__(self, valobj, internal_dict, rect_type = "nsRect"): + self.rect_type = rect_type + self.valobj = valobj + self.pixman_region = self.valobj.GetChildMemberWithName("mImpl") + self.pixman_data = self.pixman_region.GetChildMemberWithName("data") + self.pixman_extents = self.pixman_region.GetChildMemberWithName("extents") + self.num_rects = self.pixman_region_num_rects() + self.box_type = self.pixman_extents.GetType() + self.box_type_size = self.box_type.GetByteSize() + self.box_list_base_ptr = self.pixman_data.GetValueAsUnsigned(0) + self.pixman_data.GetType().GetPointeeType().GetByteSize() + + def pixman_region_num_rects(self): + if self.pixman_data.GetValueAsUnsigned(0): + return self.pixman_data.Dereference().GetChildMemberWithName("numRects").GetValueAsUnsigned(0) + return 1 + + def num_children(self): + return 2 + self.num_rects + + def get_child_index(self, name): + if name == "numRects": + return 0 + if name == "bounds": + return 1 + return None + + def convert_pixman_box_to_rect(self, valobj, name): + x1 = valobj.GetChildMemberWithName("x1").GetValueAsSigned() + x2 = valobj.GetChildMemberWithName("x2").GetValueAsSigned() + y1 = valobj.GetChildMemberWithName("y1").GetValueAsSigned() + y2 = valobj.GetChildMemberWithName("y2").GetValueAsSigned() + return valobj.CreateValueFromExpression(name, + '%s(%d, %d, %d, %d)' % (self.rect_type, x1, y1, x2 - x1, y2 - y1)) + + def get_child_at_index(self, index): + if index == 0: + return self.pixman_data.CreateValueFromExpression('numRects', '(uint32_t)%d' % self.num_rects) + if index == 1: + return self.convert_pixman_box_to_rect(self.pixman_extents, 'bounds') + + rect_index = index - 2 + if rect_index >= self.num_rects: + return None + if self.num_rects == 1: + return self.convert_pixman_box_to_rect(self.pixman_extents, 'bounds') + box_address = self.box_list_base_ptr + rect_index * self.box_type_size + box = self.pixman_data.CreateValueFromAddress('', box_address, self.box_type) + return self.convert_pixman_box_to_rect(box, "[%d]" % rect_index) + +class IntRegionSyntheticChildrenProvider: + def __init__(self, valobj, internal_dict): + wrapped_region = valobj.GetChildMemberWithName("mImpl") + self.wrapped_provider = RegionSyntheticChildrenProvider(wrapped_region, internal_dict, "nsIntRect") + + def num_children(self): + return self.wrapped_provider.num_children() + + def get_child_index(self, name): + return self.wrapped_provider.get_child_index(name) + + def get_child_at_index(self, index): + return self.wrapped_provider.get_child_at_index(index) + +def summarize_rect(valobj, internal_dict): + x = valobj.GetChildMemberWithName("x").GetValue() + y = valobj.GetChildMemberWithName("y").GetValue() + width = valobj.GetChildMemberWithName("width").GetValue() + height = valobj.GetChildMemberWithName("height").GetValue() + return "%s, %s, %s, %s" % (x, y, width, height) + +def rect_is_empty(valobj): + width = valobj.GetChildMemberWithName("width").GetValueAsSigned() + height = valobj.GetChildMemberWithName("height").GetValueAsSigned() + return width <= 0 or height <= 0 + +def summarize_region(valobj, internal_dict): + # This function makes use of the synthetic children generated for ns(Int)Regions. + bounds = valobj.GetChildMemberWithName("bounds") + num_rects = valobj.GetChildMemberWithName("numRects").GetValueAsUnsigned(0) + if num_rects == 0: + return "empty" + bounds_summary = summarize_rect(bounds, internal_dict) + if num_rects == 1: + if rect_is_empty(bounds): + return "empty" + return "one rect: " + bounds_summary + return str(num_rects) + " rects, bounds: " + bounds_summary + def init(debugger): debugger.HandleCommand("type summary add nscolor -v -F lldbutils.gfx.summarize_nscolor") + debugger.HandleCommand("type summary add nsRect -v -F lldbutils.gfx.summarize_rect") + debugger.HandleCommand("type summary add nsIntRect -v -F lldbutils.gfx.summarize_rect") + debugger.HandleCommand("type summary add gfxRect -v -F lldbutils.gfx.summarize_rect") + debugger.HandleCommand("type synthetic add nsRegion -l lldbutils.gfx.RegionSyntheticChildrenProvider") + debugger.HandleCommand("type synthetic add nsIntRegion -l lldbutils.gfx.IntRegionSyntheticChildrenProvider") + debugger.HandleCommand("type summary add nsRegion -v -F lldbutils.gfx.summarize_region") + debugger.HandleCommand("type summary add nsIntRegion -v -F lldbutils.gfx.summarize_region") diff --git a/security/certverifier/NSSCertDBTrustDomain.cpp b/security/certverifier/NSSCertDBTrustDomain.cpp index 989216a55f28..443f6d34ea96 100644 --- a/security/certverifier/NSSCertDBTrustDomain.cpp +++ b/security/certverifier/NSSCertDBTrustDomain.cpp @@ -166,7 +166,8 @@ NSSCertDBTrustDomain::CheckRevocation( PR_ASSERT(endEntityOrCA == MustBeEndEntity); SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time, - stapledOCSPResponse); + stapledOCSPResponse, + ResponseWasStapled); if (rv == SECSuccess) { // stapled OCSP response present and good Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1); @@ -223,6 +224,15 @@ NSSCertDBTrustDomain::CheckRevocation( if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) { cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE; } + // We may have a cached indication of server failure. Ignore it if + // it has expired. + if (cachedResponseErrorCode != 0 && + cachedResponseErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT && + cachedResponseErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE && + cachedResponseValidThrough < time) { + cachedResponseErrorCode = 0; + cachedResponsePresent = false; + } } else { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: no cached OCSP response")); @@ -290,15 +300,31 @@ NSSCertDBTrustDomain::CheckRevocation( return SECFailure; } - const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert, - issuerCert)); - if (!request) { - return SECFailure; + // Only request a response if we didn't have a cached indication of failure + // (don't keep requesting responses from a failing server). + const SECItem* response = nullptr; + if (cachedResponseErrorCode == 0 || + cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT || + cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) { + const SECItem* request(CreateEncodedOCSPRequest(arena.get(), cert, + issuerCert)); + if (!request) { + return SECFailure; + } + + response = CERT_PostOCSPRequest(arena.get(), url.get(), request); } - const SECItem* response(CERT_PostOCSPRequest(arena.get(), url.get(), - request)); if (!response) { + PRErrorCode error = PR_GetError(); + if (error == 0) { + error = cachedResponseErrorCode; + } + PRTime timeout = time + ServerFailureDelay; + if (mOCSPCache.Put(cert, issuerCert, error, time, timeout) != SECSuccess) { + return SECFailure; + } + PR_SetError(error, 0); if (mOCSPFetching != FetchOCSPForDVSoftFail) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning SECFailure after " @@ -320,7 +346,8 @@ NSSCertDBTrustDomain::CheckRevocation( } SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time, - response); + response, + ResponseIsFromNetwork); if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse")); @@ -342,7 +369,7 @@ NSSCertDBTrustDomain::CheckRevocation( SECStatus NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time, - const SECItem* encodedResponse) + const SECItem* encodedResponse, EncodedResponseSource responseSource) { PRTime thisUpdate = 0; PRTime validThrough = 0; @@ -350,7 +377,17 @@ NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( encodedResponse, &thisUpdate, &validThrough); PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError()); - if (rv == SECSuccess || error == SEC_ERROR_REVOKED_CERTIFICATE || + // validThrough is only trustworthy if the response successfully verifies + // or it indicates a revoked or unknown certificate. + // If this isn't the case, store an indication of failure (to prevent + // repeatedly requesting a response from a failing server). + if (rv != SECSuccess && error != SEC_ERROR_REVOKED_CERTIFICATE && + error != SEC_ERROR_OCSP_UNKNOWN_CERT) { + validThrough = time + ServerFailureDelay; + } + if (responseSource == ResponseIsFromNetwork || + rv == SECSuccess || + error == SEC_ERROR_REVOKED_CERTIFICATE || error == SEC_ERROR_OCSP_UNKNOWN_CERT) { PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("NSSCertDBTrustDomain: caching OCSP response")); @@ -358,7 +395,11 @@ NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse( != SECSuccess) { return SECFailure; } - // The call to Put may have un-set the error. Re-set it. + } + + // If the verification failed, re-set to that original error + // (the call to Put may have un-set it). + if (rv != SECSuccess) { PR_SetError(error, 0); } return rv; diff --git a/security/certverifier/NSSCertDBTrustDomain.h b/security/certverifier/NSSCertDBTrustDomain.h index ea924119a86d..443860da6388 100644 --- a/security/certverifier/NSSCertDBTrustDomain.h +++ b/security/certverifier/NSSCertDBTrustDomain.h @@ -79,9 +79,14 @@ public: /*optional*/ const SECItem* stapledOCSPResponse); private: + enum EncodedResponseSource { + ResponseIsFromNetwork = 1, + ResponseWasStapled = 2 + }; + static const PRTime ServerFailureDelay = 5 * 60 * PR_USEC_PER_SEC; SECStatus VerifyAndMaybeCacheEncodedOCSPResponse( const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time, - const SECItem* encodedResponse); + const SECItem* encodedResponse, EncodedResponseSource responseSource); const SECTrustType mCertDBTrustType; const OCSPFetching mOCSPFetching; diff --git a/security/certverifier/OCSPCache.cpp b/security/certverifier/OCSPCache.cpp index b9ef6ef3a8e0..7c930205b94b 100644 --- a/security/certverifier/OCSPCache.cpp +++ b/security/certverifier/OCSPCache.cpp @@ -214,6 +214,16 @@ OCSPCache::Put(const CERTCertificate* aCert, return SECSuccess; } + // Only known good responses or responses indicating an unknown + // or revoked certificate should replace previously known responses. + if (aErrorCode != 0 && aErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT && + aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) { + LogWithCerts("OCSPCache::Put(%s, %s) already in cache - not replacing " + "with less important status", aCert, aIssuerCert); + MakeMostRecentlyUsed(index, lock); + return SECSuccess; + } + LogWithCerts("OCSPCache::Put(%s, %s) already in cache - replacing", aCert, aIssuerCert); mEntries[index]->mErrorCode = aErrorCode; diff --git a/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp index f21282d418a5..e42702a017a8 100644 --- a/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp +++ b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp @@ -7,6 +7,7 @@ #include "CertVerifier.h" #include "OCSPCache.h" #include "nss.h" +#include "prerr.h" #include "prprf.h" #include "secerr.h" @@ -248,3 +249,35 @@ TEST_F(OCSPCacheTest, Times) // SEC_ERROR_REVOKED_CERTIFICATE overrides everything PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 50); } + +TEST_F(OCSPCacheTest, NetworkFailure) +{ + SCOPED_TRACE(""); + CERTCertificate subject; + MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001"); + CERTCertificate issuer; + MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000"); + PutAndGet(cache, &subject, &issuer, PR_CONNECT_REFUSED_ERROR, 100); + PutAndGet(cache, &subject, &issuer, 0, 200); + // This should not override the already present entry. + SECStatus rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 300, 350); + ASSERT_TRUE(rv == SECSuccess); + PRErrorCode errorOut; + PRTime timeOut; + ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); + ASSERT_TRUE(errorOut == 0 && timeOut == 200); + + PutAndGet(cache, &subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 400); + // This should not override the already present entry. + rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 500, 550); + ASSERT_TRUE(rv == SECSuccess); + ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); + ASSERT_TRUE(errorOut == SEC_ERROR_OCSP_UNKNOWN_CERT && timeOut == 400); + + PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 600); + // This should not override the already present entry. + rv = cache.Put(&subject, &issuer, PR_CONNECT_REFUSED_ERROR, 700, 750); + ASSERT_TRUE(rv == SECSuccess); + ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut)); + ASSERT_TRUE(errorOut == SEC_ERROR_REVOKED_CERTIFICATE && timeOut == 600); +} diff --git a/security/manager/ssl/tests/unit/test_ocsp_caching.js b/security/manager/ssl/tests/unit/test_ocsp_caching.js index 447c2d489191..844ea8e6d919 100644 --- a/security/manager/ssl/tests/unit/test_ocsp_caching.js +++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js @@ -107,13 +107,10 @@ function add_tests_in_mode(useMozillaPKIX) { clearSessionCache); add_test(function() { do_check_eq(gFetchCount, 1); run_next_test(); }); - // TODO(bug 977865): implement this for mozilla::pkix - if (!useMozillaPKIX) { - // The error entry will prevent a fetch from happening for a while. - add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK, - clearSessionCache); - add_test(function() { do_check_eq(gFetchCount, 1); run_next_test(); }); - } + // The error entry will prevent a fetch from happening for a while. + add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK, + clearSessionCache); + add_test(function() { do_check_eq(gFetchCount, 1); run_next_test(); }); // The error entry must not prevent a stapled OCSP response from being // honored. diff --git a/security/manager/ssl/tests/unit/test_ocsp_required.js b/security/manager/ssl/tests/unit/test_ocsp_required.js index 568444f2684f..24bd0c48d422 100644 --- a/security/manager/ssl/tests/unit/test_ocsp_required.js +++ b/security/manager/ssl/tests/unit/test_ocsp_required.js @@ -53,9 +53,7 @@ function add_tests_in_mode(useMozillaPKIX) add_connection_test("ocsp-stapling-none.example.com", getXPCOMStatusFromNSS(SEC_ERROR_OCSP_BAD_SIGNATURE)); add_test(function () { - // TODO(bug 977865): mozilla::pkix keeps requesting responses from - // failing responders - do_check_eq(gOCSPRequestCount, useMozillaPKIX ? 2 : 1); + do_check_eq(gOCSPRequestCount, 1); gOCSPRequestCount = 0; run_next_test(); }); diff --git a/security/pkix/lib/pkixbuild.cpp b/security/pkix/lib/pkixbuild.cpp index ce8ca63d8c75..1f7693b89334 100644 --- a/security/pkix/lib/pkixbuild.cpp +++ b/security/pkix/lib/pkixbuild.cpp @@ -122,7 +122,6 @@ BuildForwardInner(TrustDomain& trustDomain, SECOidTag requiredEKUIfPresent, SECOidTag requiredPolicy, CERTCertificate* potentialIssuerCertToDup, - /*optional*/ const SECItem* stapledOCSPResponse, unsigned int subCACount, ScopedCERTCertList& results) { @@ -252,14 +251,12 @@ BuildForward(TrustDomain& trustDomain, !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA, requiredEKUIfPresent, requiredPolicy, - n->cert, stapledOCSPResponse, subCACount, - results); + n->cert, subCACount, results); if (rv == Success) { // If we found a valid chain but deferred reporting an error with the // end-entity certificate, report it now. if (deferredEndEntityError != 0) { - PR_SetError(deferredEndEntityError, 0); - return FatalError; + return Fail(FatalError, deferredEndEntityError); } SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA, @@ -281,8 +278,7 @@ BuildForward(TrustDomain& trustDomain, switch (currentError) { case 0: PR_NOT_REACHED("Error code not set!"); - PR_SetError(PR_INVALID_STATE_ERROR, 0); - return FatalError; + return Fail(FatalError, PR_INVALID_STATE_ERROR); case SEC_ERROR_UNTRUSTED_CERT: currentError = SEC_ERROR_UNTRUSTED_ISSUER; break; diff --git a/security/pkix/lib/pkixcheck.cpp b/security/pkix/lib/pkixcheck.cpp index 9e552281f0bb..27818e0450c7 100644 --- a/security/pkix/lib/pkixcheck.cpp +++ b/security/pkix/lib/pkixcheck.cpp @@ -42,15 +42,15 @@ CheckTimes(const CERTCertificate* cert, PRTime time) // Modeled after GetKeyUsage in certdb.c Result CheckKeyUsage(EndEntityOrCA endEntityOrCA, - bool isTrustAnchor, const SECItem* encodedKeyUsage, KeyUsages requiredKeyUsagesIfPresent, PLArenaPool* arena) { if (!encodedKeyUsage) { - // TODO: Reject certificates that are being used to verify certificate - // signatures unless the certificate is a trust anchor, to reduce the - // chances of an end-entity certificate being abused as a CA certificate. + // TODO(bug 970196): Reject certificates that are being used to verify + // certificate signatures unless the certificate is a trust anchor, to + // reduce the chances of an end-entity certificate being abused as a CA + // certificate. // if (endEntityOrCA == MustBeCA && !isTrustAnchor) { // return Fail(RecoverableError, SEC_ERROR_INADEQUATE_KEY_USAGE); // } @@ -122,8 +122,7 @@ CheckCertificatePolicies(BackCert& cert, EndEntityOrCA endEntityOrCA, // inhibitAnyPolicy extension is present and we need to evaluate certificate // policies. if (cert.encodedInhibitAnyPolicy) { - PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); - return RecoverableError; + return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED); } // The root CA certificate may omit the policies that it has been @@ -135,8 +134,7 @@ CheckCertificatePolicies(BackCert& cert, EndEntityOrCA endEntityOrCA, } if (!cert.encodedCertificatePolicies) { - PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); - return RecoverableError; + return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED); } ScopedPtr @@ -158,8 +156,7 @@ CheckCertificatePolicies(BackCert& cert, EndEntityOrCA endEntityOrCA, } } - PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0); - return RecoverableError; + return Fail(RecoverableError, SEC_ERROR_POLICY_VALIDATION_FAILED); } // BasicConstraints ::= SEQUENCE { @@ -354,8 +351,7 @@ CheckNameConstraints(BackCert& cert) // PR_SetError when it fails. We set the error code here, though this // may be papering over some fatal errors. NSS's // cert_VerifyCertChainOld does something similar. - PR_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE, 0); - return RecoverableError; + return Fail(RecoverableError, SEC_ERROR_CERT_NOT_IN_NAME_SPACE); } currentName = CERT_GetNextGeneralName(currentName); } while (currentName != names); @@ -415,8 +411,7 @@ CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, const SECItem* encodedEKUs, // If the EKU extension was included, then the required EKU must be in the // list. if (!found) { - PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0); - return RecoverableError; + return Fail(RecoverableError, SEC_ERROR_INADEQUATE_CERT_TYPE); } } @@ -479,8 +474,7 @@ CheckIssuerIndependentProperties(TrustDomain& trustDomain, return rv; } if (trustLevel == TrustDomain::ActivelyDistrusted) { - PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); - return RecoverableError; + return Fail(RecoverableError, SEC_ERROR_UNTRUSTED_CERT); } if (trustLevel != TrustDomain::TrustAnchor && trustLevel != TrustDomain::InheritsTrust) { @@ -505,7 +499,7 @@ CheckIssuerIndependentProperties(TrustDomain& trustDomain, // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136). // 4.2.1.3. Key Usage - rv = CheckKeyUsage(endEntityOrCA, isTrustAnchor, cert.encodedKeyUsage, + rv = CheckKeyUsage(endEntityOrCA, cert.encodedKeyUsage, requiredKeyUsagesIfPresent, arena); if (rv != Success) { return rv; diff --git a/security/pkix/lib/pkixder.h b/security/pkix/lib/pkixder.h index 980dc09d9e68..dca4a170ffa4 100644 --- a/security/pkix/lib/pkixder.h +++ b/security/pkix/lib/pkixder.h @@ -376,8 +376,7 @@ Boolean(Input& input, /*out*/ bool& value) case 0: value = false; return Success; case 0xFF: value = true; return Success; default: - PR_SetError(SEC_ERROR_BAD_DER, 0); - return Failure; + return Fail(SEC_ERROR_BAD_DER); } } diff --git a/security/pkix/test/gtest/moz.build b/security/pkix/test/gtest/moz.build index 8e2f91d4ba0e..610e01f91c4f 100644 --- a/security/pkix/test/gtest/moz.build +++ b/security/pkix/test/gtest/moz.build @@ -7,6 +7,7 @@ LIBRARY_NAME = 'mozillapkix_gtest' SOURCES += [ + 'nssgtest.cpp', 'pkixder_input_tests.cpp', 'pkixder_pki_types_tests.cpp', 'pkixder_universal_types_tests.cpp', diff --git a/security/pkix/test/gtest/nssgtest.cpp b/security/pkix/test/gtest/nssgtest.cpp new file mode 100644 index 000000000000..2b130149f6f9 --- /dev/null +++ b/security/pkix/test/gtest/nssgtest.cpp @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Copyright 2013 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "nssgtest.h" + +using namespace std; +using namespace testing; + +namespace mozilla { namespace pkix { namespace test { + +ostream& +operator<<(ostream& os, SECStatusWithPRErrorCode const& value) +{ + switch (value.mRv) + { + case SECSuccess: + os << "SECSuccess"; + break; + case SECWouldBlock: + os << "SECWouldBlock"; + break; + case SECFailure: + os << "SECFailure"; + break; + default: + os << "[Invalid SECStatus: " << static_cast(value.mRv) << ']'; + break; + } + + if (value.mRv != SECSuccess) { + os << '('; + const char* name = PR_ErrorToName(value.mErrorCode); + if (name) { + os << name; + } else { + os << value.mErrorCode; + } + os << ')'; + } + + return os; +} + +AssertionResult +Pred_SECFailure(const char* expectedExpr, const char* actualExpr, + PRErrorCode expectedErrorCode, SECStatus actual) +{ + if (SECFailure == actual && expectedErrorCode == PR_GetError()) { + return AssertionSuccess(); + } + + return AssertionFailure() + << "Expected: (" << expectedExpr << ") == (" << actualExpr + << "), actual: " << SECFailure << " != " << actual; +} + +} } } // namespace mozilla::pkix::test diff --git a/security/pkix/test/gtest/nssgtest.h b/security/pkix/test/gtest/nssgtest.h new file mode 100644 index 000000000000..87522c272332 --- /dev/null +++ b/security/pkix/test/gtest/nssgtest.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Copyright 2013 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef mozilla_pkix__nssgtest_h +#define mozilla_pkix__nssgtest_h + +#include "stdint.h" +#include "gtest/gtest.h" +#include "prerror.h" +#include "seccomon.h" + +namespace mozilla { namespace pkix { namespace test { + +class SECStatusWithPRErrorCode +{ +public: + SECStatusWithPRErrorCode(SECStatus rv, PRErrorCode errorCode) + : mRv(rv) + , mErrorCode(errorCode) + { + } + + SECStatusWithPRErrorCode(SECStatus rv) + : mRv(rv) + , mErrorCode(rv == SECSuccess ? 0 : PR_GetError()) + { + } + + bool operator==(const SECStatusWithPRErrorCode& other) const + { + return mRv == other.mRv && mErrorCode == other.mErrorCode; + } + +private: + const SECStatus mRv; + const PRErrorCode mErrorCode; + + friend std::ostream& operator<<(std::ostream& os, + SECStatusWithPRErrorCode const& value); + + void operator=(const SECStatusWithPRErrorCode&) /*delete*/; +}; + +::std::ostream& operator<<(::std::ostream&, + SECStatusWithPRErrorCode const&); + +} } } // namespace mozilla::pkix::test + +#define ASSERT_SECSuccess(rv) \ + ASSERT_EQ(::mozilla::pkix::test::SECStatusWithPRErrorCode(SECSuccess, 0), \ + ::mozilla::pkix::test::SECStatusWithPRErrorCode(rv)) +#define EXPECT_SECSuccess(rv) \ + EXPECT_EQ(::mozilla::pkix::test::SECStatusWithPRErrorCode(SECSuccess, 0), \ + ::mozilla::pkix::test::SECStatusWithPRErrorCode(rv)) + +#define ASSERT_SECFailure(expectedError, rv) \ + ASSERT_EQ(::mozilla::pkix::test::SECStatusWithPRErrorCode(SECFailure, \ + expectedError), \ + ::mozilla::pkix::test::SECStatusWithPRErrorCode(rv)) +#define EXPECT_SECFailure(expectedError, rv) \ + EXPECT_EQ(::mozilla::pkix::test::SECStatusWithPRErrorCode(SECFailure, \ + expectedError), \ + ::mozilla::pkix::test::SECStatusWithPRErrorCode(rv)) + +#endif // mozilla_pkix__nssgtest_h diff --git a/testing/mach_commands.py b/testing/mach_commands.py index da88e09ce7b4..cee3d08a343c 100644 --- a/testing/mach_commands.py +++ b/testing/mach_commands.py @@ -62,10 +62,10 @@ TEST_SUITES = { 'mach_command': 'jetpack-test', 'kwargs': {}, }, - 'jittest': { - 'aliases': ('Jit', 'jit'), - 'mach_command': 'jittest', - 'kwargs': {'test_file': None}, + 'check-spidermonkey': { + 'aliases': ('Sm', 'sm'), + 'mach_command': 'check-spidermonkey', + 'kwargs': {'valgrind': False}, }, 'mochitest-a11y': { 'mach_command': 'mochitest-a11y', @@ -216,22 +216,41 @@ class MachCommands(MachCommandBase): return 0 if result else 1 @CommandProvider -class JittestCommand(MachCommandBase): - @Command('jittest', category='testing', description='Run jit-test tests.') +class CheckSpiderMonkeyCommand(MachCommandBase): + @Command('check-spidermonkey', category='testing', description='Run SpiderMonkey tests.') @CommandArgument('--valgrind', action='store_true', help='Run jit-test suite with valgrind flag') - def run_jittest(self, **params): + def run_checkspidermonkey(self, **params): import subprocess import sys + bin_suffix = '' if sys.platform.startswith('win'): - js = os.path.join(self.bindir, 'js.exe') - else: - js = os.path.join(self.bindir, 'js') - cmd = [os.path.join(self.topsrcdir, 'js', 'src', 'jit-test', 'jit_test.py'), - js, '--no-slow', '--no-progress', '--tinderbox', '--tbpl'] + bin_suffix = '.exe' + js = os.path.join(self.bindir, 'js%s' % bin_suffix) + + print('Running jit-tests') + jittest_cmd = [os.path.join(self.topsrcdir, 'js', 'src', 'jit-test', 'jit_test.py'), + js, '--no-slow', '--tbpl'] if params['valgrind']: - cmd.append('--valgrind') + jittest_cmd.append('--valgrind') - return subprocess.call(cmd) + jittest_result = subprocess.call(jittest_cmd) + + print('running jstests') + jstest_cmd = [os.path.join(self.topsrcdir, 'js', 'src', 'tests', 'jstests.py'), + js, '--tbpl'] + jstest_result = subprocess.call(jstest_cmd) + + print('running jsapi-tests') + jsapi_tests_cmd = [os.path.join(self.bindir, 'jsapi-tests%s' % bin_suffix)] + jsapi_tests_result = subprocess.call(jsapi_tests_cmd) + + print('running check-style') + check_style_cmd = [sys.executable, os.path.join(self.topsrcdir, 'config', 'check_spidermonkey_style.py')] + check_style_result = subprocess.call(check_style_cmd, cwd=os.path.join(self.topsrcdir, 'js', 'src')) + + all_passed = jittest_result and jstest_result and jsapi_tests_result and check_style_result + + return all_passed diff --git a/testing/marionette/client/marionette/scripts/runemu.sh b/testing/marionette/client/marionette/scripts/runemu.sh index 60516ecd69a0..7022d6dd4ae7 100755 --- a/testing/marionette/client/marionette/scripts/runemu.sh +++ b/testing/marionette/client/marionette/scripts/runemu.sh @@ -41,7 +41,7 @@ then . bin/activate else echo "Creating a virtual environment in $VENV_DIR" - curl https://raw.github.com/pypa/virtualenv/develop/virtualenv.py | ${PYTHON} - $VENV_DIR + curl -L https://raw.github.com/pypa/virtualenv/develop/virtualenv.py | ${PYTHON} - $VENV_DIR cd $VENV_DIR . bin/activate # set up mozbase diff --git a/testing/marionette/client/marionette/venv_automation.sh b/testing/marionette/client/marionette/venv_automation.sh index 5bc516d68e39..ae81c246ccdb 100755 --- a/testing/marionette/client/marionette/venv_automation.sh +++ b/testing/marionette/client/marionette/venv_automation.sh @@ -19,7 +19,7 @@ then cd marionette_auto_venv . bin/activate else - curl https://raw.github.com/pypa/virtualenv/${VIRTUAL_ENV_VERSION}/virtualenv.py | ${PYTHON} - marionette_auto_venv + curl -L https://raw.github.com/pypa/virtualenv/${VIRTUAL_ENV_VERSION}/virtualenv.py | ${PYTHON} - marionette_auto_venv cd marionette_auto_venv . bin/activate diff --git a/testing/marionette/client/marionette/venv_b2g_update_test.sh b/testing/marionette/client/marionette/venv_b2g_update_test.sh index 693b709ec383..0ce063f36f9d 100644 --- a/testing/marionette/client/marionette/venv_b2g_update_test.sh +++ b/testing/marionette/client/marionette/venv_b2g_update_test.sh @@ -37,7 +37,7 @@ then echo "Using virtual environment in $VENV_DIR" else echo "Creating a virtual environment (version ${VIRTUAL_ENV_VERSION}) in ${VENV_DIR}" - curl https://raw.github.com/pypa/virtualenv/${VIRTUAL_ENV_VERSION}/virtualenv.py | ${PYTHON} - $VENV_DIR + curl -L https://raw.github.com/pypa/virtualenv/${VIRTUAL_ENV_VERSION}/virtualenv.py | ${PYTHON} - $VENV_DIR fi . $VENV_DIR/bin/activate diff --git a/testing/marionette/client/setup.py b/testing/marionette/client/setup.py index cf107b9f9235..5d6a604672cd 100644 --- a/testing/marionette/client/setup.py +++ b/testing/marionette/client/setup.py @@ -2,7 +2,7 @@ import os from setuptools import setup, find_packages import sys -version = '0.7.6' +version = '0.7.7' # get documentation from the README try: @@ -27,7 +27,7 @@ if os.path.exists(transport_dir) and method: try: subprocess.check_call(cmd, cwd=transport_dir) except subprocess.CalledProcessError: - print "Error running setup.py in %s" % directory + print "Error running setup.py in %s" % transport_dir raise else: deps += ['marionette-transport == 0.1'] diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py index 964830e47260..259e1e3a3c56 100644 --- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py +++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py @@ -457,7 +457,10 @@ class DeviceManagerADB(DeviceManager): raise return - paths = [('/mnt/sdcard', 'tests'), + paths = [('/storage/sdcard0', 'tests'), + ('/storage/sdcard1', 'tests'), + ('/sdcard', 'tests'), + ('/mnt/sdcard', 'tests'), ('/data/local', 'tests')] for (basePath, subPath) in paths: if self.dirExists(basePath): diff --git a/testing/mozbase/mozdevice/setup.py b/testing/mozbase/mozdevice/setup.py index b7e6ee9e0a43..751285652508 100644 --- a/testing/mozbase/mozdevice/setup.py +++ b/testing/mozbase/mozdevice/setup.py @@ -5,7 +5,7 @@ from setuptools import setup PACKAGE_NAME = 'mozdevice' -PACKAGE_VERSION = '0.33' +PACKAGE_VERSION = '0.34' deps = ['mozfile >= 1.0', 'mozlog', diff --git a/testing/mozbase/mozlog/tests/test_structured.py b/testing/mozbase/mozlog/tests/test_structured.py index cad9548f9541..f2f190d3fead 100644 --- a/testing/mozbase/mozlog/tests/test_structured.py +++ b/testing/mozbase/mozlog/tests/test_structured.py @@ -43,7 +43,6 @@ class BaseStructuredTest(unittest.TestCase): for key, value in all_expected.iteritems(): self.assertEqual(actual[key], value) - self.assertAlmostEqual(actual["time"], time.time()*1000, delta=100) self.assertEquals(set(all_expected.keys()) | specials, set(actual.keys())) diff --git a/testing/talos/talos.json b/testing/talos/talos.json index 8231aca889e3..090ecb8f8697 100644 --- a/testing/talos/talos.json +++ b/testing/talos/talos.json @@ -1,11 +1,11 @@ { "talos.zip": { - "url": "http://talos-bundles.pvt.build.mozilla.org/zips/talos.7674ffd4b494.zip", + "url": "http://talos-bundles.pvt.build.mozilla.org/zips/talos.8d6fb3704417.zip", "path": "" }, "global": { "talos_repo": "https://hg.mozilla.org/build/talos", - "talos_revision": "7674ffd4b494" + "talos_revision": "8d6fb3704417" }, "suites": { "chromez": { diff --git a/toolkit/library/libxul.mk b/toolkit/library/libxul.mk index 67f0db9581d7..edc5d1d38b77 100644 --- a/toolkit/library/libxul.mk +++ b/toolkit/library/libxul.mk @@ -214,13 +214,6 @@ endif ifdef MOZ_METRO OS_LIBS += $(call EXPAND_LIBNAME,uiautomationcore runtimeobject) endif -ifdef MOZ_GAMEPAD -ifdef MOZ_HAS_WINSDK_WITH_D3D -OS_LIBS += $(call EXPAND_LIBNAME,dxguid dinput8) -else -OS_LIBS += $(call EXPAND_LIBNAME_PATH,dxguid dinput8, '$(subst \,/,$(MOZ_DIRECTX_SDK_PATH))/Lib/$(MOZ_DIRECTX_SDK_CPU_SUFFIX)') -endif -endif endif # WINNT ifdef MOZ_JPROF