From 8cdf8ee29c66dd559fad2104d7a96fe1e3377e1e Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 20 Oct 2022 11:04:02 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1170958 - Feed a SourceMediaStream-backed dom stream instead of a raw SourceMediaStream in MediaManager. r=jesup (8670ff2711) - Bug 1103188 - Remove identical override nsDOMUserMediaStream::Stop(). r=jib (54831f9b18) - Bug 1103188 - Deprecate DOMMediaStream::Stop(). r=jib (36112afe82) - Bug 1186813 - Replace nsBaseHashtable::EnumerateRead() calls in dom/media/ with iterators r=cpearce (cd0c4a34e8) - Bug 1190337 - Log GPS status and SVs status if the 'gDebug_isLoggingEnabled' is true. r=garvank (c269f6f31d) - Bug 1154435 - [Stumbler] FxOS Geo Stumbling for Mozilla Location Service. r=jdm (1a86f4dda5) - Bug 1199395 - FxOS Stumbling gzip the stumbles to store more data. r=jdm (4d108665d9) - Bug 1175860 - Add some documentation to UploadLastDir to make its workings clearer. r=baku (cdac9a7849) - Bug 1210517 - Create nsVariant directly rather than via do_CreateInstance(). r=froydnj (df420cba8e) - Bug 953265: make getUserMedia fake audio tones configurable in frequency via pref r=jib (67793ee005) - Bug 1166293 - Use AsyncShutdown API to shut down media thread in non-e10s. r= jesup (1245d20b7e) - Bug 1103188 - MediaStream WebIDL update with addTrack/removeTrack. r=smaug,jib (697791fd6f) - Bug 1103188 - MediaStream::AddTrack/RemoveTrack implementation. r=roc (c8b02beb45) - Bug 1170958 - Improve logging of MediaStreams and playback. r=roc (5fcb40437e) - Bug 1170958 - Add DOMMediaStream::OwnedStreamListener. r=roc (afff077f93) - Bug 1103188 - Break out MediaTrackListListener to an interface. r=roc (298b665f27) - Bug 1198435 - Call RemoveMediaElementFromURITable before modifying mLoadingSrc, so that a future LookupMediaElementURITable won't access this element anymore. r=rillian (f2805c8dba) - Bug 1141875 - Add flag to init gl_Position. - r=kamidphish (eeb333c02b) - Bug 1128044 - Enforce packing restrictions for varyings. - r=kamidphish (17b9596a3d) - Bug 1128044 - Only pack varyings that have static use in both shaders. - r=warnings-as-errors (f41708642a) - Bug 1128044 - Use nsTArray since android doesn't support std::vector::data(). - r=bustage (be88a80844) - Bug 1128044 - nsTArray::AppendElement doesn't accept init lists. - r=bustage (cdeafa867b) - bit of Bug 1019209 - Allow GL initialization without Android bridge (3dba5dffa2) - some reporter (3049ad6f6d) - Bug 1206030 - Remove nsIDOMHTMLCanvasElement::MozFetchAsStream() f=Ms2ger r=jst (95e773b79f) - Bug 1187174 - Use 'webgl2' not 'experimental-webgl2'. - r=kamidphish (a6c21752fc) - Bug 1190777 - Add null checks to prevent bad dereferences. r=kamidphish (f67f0125ce) - Bug 709490 - Part 1: Let ImageBridge transfer CanvasClient async. r=nical (a46ac7e71c) - Bug 1150762 - Add pref for activating all ANGLE options. - r=kamidphish (6ab4d39827) - Bug 1195401 - Use gfxPrefs (threadsafe) rather than crashing on debug builds for off-main-thread pref access. r=snorp (0d29cea59c) - Bug 709490 - Part 2: Introduce OffscreenCanvas and let WebGL context work on workers. r=nical, r=jgilbert, r=jrmuizel, sr=ehsan (842aaa8328) - Bug 709490 - Part 3: Transfer OffscreenCanvas from mainthread to workers. r=baku, r=sfink (91c24b0e08) - Bug 709490 - Part 4: Mochitests for offscreencanvas. r=baku, r=jgilbert (4c439fd376) - Bug 1173544 - Add tests for Canvas CSS/SVG Filters. r=mstange (04c01f1c11) - fix (9c7ab9d870) - Bug 709490 - Part 5: Add interfaces test. r=ehsan (2993581c89) - Bug 709490 - Part 6: Add frame ID to CanvasClient so compositor could update frame correctly. r=roc (3e6554af1e) - Bug 709490 - Part 7: If layer is not available, fallback to BasicCanvasLayer. r=roc (c0c0d04468) - Bug 709490 - Part 8: Copy to a temp texture when readback from IOSurface. r=jgilbert (d1a4879a39) - Bug 709490 - Part 9: Readback without blocking main thread. r=jgilbert (2430c6e2a5) - Bug 709490 - Part 10: Using mechanism in RuntimeService to get pref in worker thread instead of gfxPref. r=baku (85d6dc2744) - Bug 709490 - Part 11: Diabled test_offscreencanvas_many.html on gonk, android, windows and linux. r=jgilbert (5cd8f28063) - Bug 1212663 - Use doxygen style comments in jsapi, r=Waldo (0e67283edf) - Bug 1000922 - Use nsMainThreadPtrHandle instead of already_AddRefed and forget for callbacks in NativeOSFileInternals.cpp r=jdm (4a128db7a6) - Bug 1169740 - Implement a TDZ-like behavior for |this| in derived class constructors. (r=jandem, r=jorendorff, inputs on nit resoulution from Waldo) (6d7df317e3) - Bug 1211949 - check for allocation failure. r=nbp (94b8aac5e3) - Bug 1209497 - OOM-crash if a consistent object table is impossible. r=jandem (e8ded0c3cb) - Bug 1141863 - Part 1: Make |this| object creation account for new.target. (r=jandem, r=jorendorff) (9b4ec25d47) - Bug 1141863 - Part 2: Implement ES6 SuperCall. (r=jandem, r=jorendorff) (1bbd2ba712) - Bug 1141863 - Followup: Clean up proxy get traps to handle new |this| creation semantics. (rs=Waldo) CLOSED TREE (e7cd48b43c) - Bug 1141863 - Last followup fix for a couple jstest failures. r=orange in a CLOSED TREE (8a9cff881a) - Bug 1141863 - Followfollowfollowup: Remove redundant assert causing rooting hazards. (r=Waldo over IRC) CLOSED TREE (338b64ca87) - Bug 1141863 - Tests. (r=jorendorff) (3957511169) - Bug 1105463 - Implement default constructors for ES6 class definitions. (r=jorendorff) (8ead7f33a5) - Bug 1105463 - Follow up: Fix erroneous syntax test. (r=theSheriffMadeMeDoIt) (425e678cf2) - Bug 1212794 - Remove decompile-body functionality. r=till (9b87e5c0e4) - Bug 1214970 - Don't emit nullptr atoms for class expressions with default constructors. (r=Waldo) (80ae19d6dc) - Bug 1215744 - Unnamed class expressions shouldn't get a name property. (r=arai) (0ce0a96be4) - Bug 1208747 - Move most of Stopwatch-related code to XPCOM-land (JSAPI-level);r=jandem (e28fa2f859) - Bug 1184486 - Let PerformanceStats.jsm play nicer with process-per-tab. r=mconley (f0cf0d0eae) - Bug 1198167 - nsPerformanceStatsService should wait for profile-before-change, not profile-before-shutdown. r=yoric (5ba3c98109) - Bug 1199603 - Don't wait for shutdown to update nsPerformanceStats Telemetry. r=Mossop (110813977b) - Bug 1205154 - Use channel->Open2() in js/xpconnect/src/XPCJSRuntime.cpp (r=sicking) (8efd629889) - Bug 1208747 - Move most of Stopwatch-related code to XPCOM-land (XPCOM-level + XPConnect-level);r=froydnj (a1b1e83549) - with some fixes --- b2g/app/b2g.js | 3 + dom/base/ImageEncoder.cpp | 13 +- dom/base/ImageEncoder.h | 5 +- dom/base/StructuredCloneHolder.cpp | 50 + dom/base/StructuredCloneTags.h | 3 + dom/base/nsContentAreaDragDrop.cpp | 29 +- dom/canvas/CanvasRenderingContextHelper.cpp | 265 ++++ dom/canvas/CanvasRenderingContextHelper.h | 71 ++ dom/canvas/CanvasUtils.cpp | 35 + dom/canvas/CanvasUtils.h | 1 + dom/canvas/OffscreenCanvas.cpp | 242 ++++ dom/canvas/OffscreenCanvas.h | 179 +++ dom/canvas/WebGLContext.cpp | 396 +++--- dom/canvas/WebGLContext.h | 43 +- dom/canvas/WebGLContextExtensions.cpp | 14 +- dom/canvas/WebGLContextGL.cpp | 5 +- dom/canvas/WebGLContextLossHandler.cpp | 119 ++ dom/canvas/WebGLContextLossHandler.h | 5 +- dom/canvas/WebGLContextReporter.cpp | 2 - dom/canvas/WebGLContextValidate.cpp | 19 +- dom/canvas/WebGLMemoryTracker.cpp | 2 - dom/canvas/WebGLShaderValidator.cpp | 80 +- dom/canvas/WebGLShaderValidator.h | 4 +- dom/canvas/moz.build | 5 + .../nsICanvasRenderingContextInternal.h | 15 +- dom/canvas/test/mochitest.ini | 24 + dom/canvas/test/offscreencanvas.js | 299 +++++ dom/canvas/test/offscreencanvas_mask.svg | 11 + dom/canvas/test/offscreencanvas_neuter.js | 1 + .../offscreencanvas_serviceworker_inner.html | 32 + .../test/reftest/filters/default-color.html | 16 + .../filters/drop-shadow-transformed.html | 17 + .../test/reftest/filters/drop-shadow.html | 16 + .../reftest/filters/global-alpha-ref.html | 18 + .../test/reftest/filters/global-alpha.html | 17 + .../global-composite-operation-ref.html | 26 + .../filters/global-composite-operation.html | 21 + dom/canvas/test/reftest/filters/liveness.html | 18 + .../filters/multiple-drop-shadows.html | 16 + dom/canvas/test/reftest/filters/ref.html | 17 + dom/canvas/test/reftest/filters/reftest.list | 20 + .../test/reftest/filters/shadow-ref.html | 19 + dom/canvas/test/reftest/filters/shadow.html | 18 + .../reftest/filters/subregion-fill-paint.html | 27 + .../test/reftest/filters/subregion-ref.html | 15 + .../filters/subregion-stroke-paint.html | 27 + .../test/reftest/filters/svg-bbox-ref.html | 15 + dom/canvas/test/reftest/filters/svg-bbox.html | 27 + .../test/reftest/filters/svg-inline.html | 30 + .../test/reftest/filters/svg-liveness.html | 64 + .../test/reftest/filters/svg-off-screen.html | 33 + dom/canvas/test/reftest/filters/units-em.html | 17 + dom/canvas/test/reftest/filters/units-ex.html | 17 + .../reftest/filters/units-off-screen.html | 21 + dom/canvas/test/reftest/filters/units-pt.html | 16 + dom/canvas/test/reftest/filters/units.html | 16 + dom/canvas/test/test_filter.html | 45 + .../test_offscreencanvas_basic_webgl.html | 62 + ...test_offscreencanvas_dynamic_fallback.html | 80 ++ .../test/test_offscreencanvas_many.html | 67 + .../test/test_offscreencanvas_neuter.html | 78 ++ .../test_offscreencanvas_serviceworker.html | 46 + .../test_offscreencanvas_sharedworker.html | 47 + .../test/test_offscreencanvas_sizechange.html | 41 + .../test/test_offscreencanvas_subworker.html | 90 ++ dom/canvas/test/webgl-mochitest/webgl-util.js | 6 - dom/events/DataTransfer.cpp | 13 +- dom/html/HTMLCanvasElement.cpp | 661 +++++----- dom/html/HTMLCanvasElement.h | 98 +- dom/html/HTMLInputElement.cpp | 8 +- dom/html/HTMLInputElement.h | 13 +- dom/html/HTMLMediaElement.cpp | 182 ++- dom/html/HTMLMediaElement.h | 22 + dom/html/TextTrackManager.cpp | 4 +- .../html/nsIDOMHTMLCanvasElement.idl | 7 +- dom/ipc/ContentChild.cpp | 5 +- dom/ipc/TabParent.cpp | 9 +- dom/locales/en-US/chrome/dom/dom.properties | 2 + dom/media/DOMMediaStream.cpp | 479 ++++--- dom/media/DOMMediaStream.h | 77 +- dom/media/MediaManager.cpp | 373 +++--- dom/media/MediaManager.h | 2 + dom/media/MediaStreamGraph.cpp | 18 +- dom/media/MediaStreamTrack.h | 1 + dom/media/MediaTrackList.cpp | 27 - dom/media/MediaTrackList.h | 36 - dom/media/TrackUnionStream.cpp | 3 + dom/media/gmp/GMPServiceChild.cpp | 12 +- dom/media/systemservices/MediaParent.cpp | 47 +- dom/media/systemservices/MediaUtils.cpp | 6 +- dom/media/systemservices/MediaUtils.h | 30 + .../tests/mochitest/mediaStreamPlayback.js | 6 +- .../test_getUserMedia_basicScreenshare.html | 2 +- .../test_getUserMedia_basicWindowshare.html | 2 +- .../test_getUserMedia_stopAudioStream.html | 2 +- ...edia_stopAudioStreamWithFollowupAudio.html | 2 +- ...est_getUserMedia_stopVideoAudioStream.html | 2 +- ...ideoAudioStreamWithFollowupVideoAudio.html | 2 +- .../test_getUserMedia_stopVideoStream.html | 2 +- ...edia_stopVideoStreamWithFollowupVideo.html | 2 +- dom/media/webrtc/MediaEngine.h | 1 + dom/media/webrtc/MediaEngineDefault.cpp | 18 +- dom/media/webrtc/MediaEngineWebRTC.cpp | 35 +- dom/media/webspeech/synth/SpeechSynthesis.cpp | 13 +- dom/mobilemessage/DeletedMessageInfo.cpp | 10 +- dom/storage/DOMStorageDBThread.cpp | 7 +- .../gonk/GonkGPSGeolocationProvider.cpp | 191 ++- dom/system/gonk/moz.build | 5 + dom/system/gonk/mozstumbler/MozStumbler.cpp | 323 +++++ dom/system/gonk/mozstumbler/MozStumbler.h | 45 + .../gonk/mozstumbler/StumblerLogging.cpp | 13 + dom/system/gonk/mozstumbler/StumblerLogging.h | 18 + .../mozstumbler/UploadStumbleRunnable.cpp | 151 +++ .../gonk/mozstumbler/UploadStumbleRunnable.h | 46 + .../gonk/mozstumbler/WriteStumbleOnThread.cpp | 299 +++++ .../gonk/mozstumbler/WriteStumbleOnThread.h | 84 ++ .../mochitest/general/test_interfaces.html | 2 + dom/webidl/HTMLCanvasElement.webidl | 9 +- dom/webidl/MediaStream.webidl | 30 +- dom/webidl/OffscreenCanvas.webidl | 28 + dom/webidl/WebGLRenderingContext.webidl | 30 +- dom/webidl/moz.build | 1 + dom/workers/RuntimeService.cpp | 10 + dom/workers/WorkerPrivate.h | 7 + dom/workers/Workers.h | 1 + dom/workers/XMLHttpRequest.cpp | 6 +- dom/workers/test/test_worker_interfaces.js | 22 + dom/xslt/xslt/txMozillaXSLTProcessor.cpp | 5 +- .../nsXULTemplateQueryProcessorStorage.cpp | 4 +- editor/composer/nsEditorSpellCheck.cpp | 5 +- gfx/gl/AndroidSurfaceTexture.cpp | 5 +- gfx/gl/GLContext.cpp | 23 + gfx/gl/GLLibraryEGL.cpp | 41 +- gfx/gl/GLLibraryEGL.h | 12 + gfx/gl/GLReadTexImageHelper.cpp | 48 +- gfx/gl/GLReadTexImageHelper.h | 21 +- gfx/gl/GLScreenBuffer.cpp | 69 +- gfx/gl/GLScreenBuffer.h | 7 + gfx/gl/SharedSurface.cpp | 6 +- gfx/gl/SharedSurface.h | 7 + gfx/gl/SharedSurfaceANGLE.cpp | 132 ++ gfx/gl/SharedSurfaceANGLE.h | 2 + gfx/gl/SharedSurfaceEGL.cpp | 9 +- gfx/gl/SharedSurfaceEGL.h | 2 + gfx/gl/SharedSurfaceGLX.cpp | 33 + gfx/gl/SharedSurfaceGLX.h | 2 + gfx/gl/SharedSurfaceGralloc.cpp | 53 + gfx/gl/SharedSurfaceGralloc.h | 2 + gfx/gl/SharedSurfaceIO.cpp | 25 + gfx/gl/SharedSurfaceIO.h | 2 + gfx/layers/AsyncCanvasRenderer.cpp | 279 ++++ gfx/layers/AsyncCanvasRenderer.h | 169 +++ gfx/layers/CopyableCanvasLayer.cpp | 9 +- gfx/layers/LayerTreeInvalidation.cpp | 31 +- gfx/layers/Layers.cpp | 14 + gfx/layers/Layers.h | 22 +- gfx/layers/apz/src/AsyncPanZoomAnimation.h | 1 + gfx/layers/apz/src/WheelScrollAnimation.h | 2 + gfx/layers/basic/BasicCanvasLayer.cpp | 1 + gfx/layers/client/CanvasClient.cpp | 94 +- gfx/layers/client/CanvasClient.h | 67 +- gfx/layers/client/ClientCanvasLayer.cpp | 86 +- gfx/layers/client/ClientCanvasLayer.h | 1 - gfx/layers/client/TextureClient.cpp | 1 + gfx/layers/composite/TextureHost.cpp | 1 + gfx/layers/ipc/ImageBridgeChild.cpp | 135 +- gfx/layers/ipc/ImageBridgeChild.h | 10 + gfx/layers/ipc/PImageBridge.ipdl | 2 +- gfx/layers/moz.build | 2 + gfx/src/gfxCrashReporterUtils.cpp | 19 +- gfx/thebes/gfxPrefs.h | 25 +- gfx/thebes/gfxUtils.cpp | 123 ++ gfx/thebes/gfxUtils.h | 17 + gfx/thebes/moz.build | 1 + js/public/Class.h | 229 ++-- js/public/Conversions.h | 4 +- js/public/GCAPI.h | 58 +- js/public/HeapAPI.h | 14 +- js/public/Id.h | 2 +- js/public/MemoryMetrics.h | 95 +- js/public/RootingAPI.h | 26 +- js/public/StructuredClone.h | 162 ++- js/public/TracingAPI.h | 36 +- js/public/Value.h | 79 +- js/src/builtin/ReflectParse.cpp | 12 +- js/src/ds/InlineMap.h | 12 + js/src/frontend/BytecodeEmitter.cpp | 63 +- js/src/frontend/FoldConstants.cpp | 5 +- js/src/frontend/NameFunctions.cpp | 3 +- js/src/frontend/ParseNode.cpp | 1 + js/src/frontend/ParseNode.h | 1 + js/src/frontend/Parser.cpp | 34 +- js/src/frontend/SharedContext.h | 1 + .../jit-test/tests/auto-regress/bug759306.js | 2 +- .../jit-test/tests/auto-regress/bug776484.js | 4 - .../basic/function-tosource-constructor.js | 2 - .../tests/basic/function-tosource-exprbody.js | 1 - .../tests/basic/function-tosource-genexpr.js | 2 +- .../tests/basic/function-tosource-lambda.js | 3 - .../basic/function-tosource-statement.js | 2 - .../tests/basic/testProxyConstructors.js | 8 +- js/src/jit-test/tests/ion/bug825705.js | 15 - js/src/jit/BaselineBailouts.cpp | 4 +- js/src/jit/BaselineCompiler.cpp | 75 +- js/src/jit/BaselineCompiler.h | 5 +- js/src/jit/BaselineIC.cpp | 61 +- js/src/jit/BaselineJIT.cpp | 30 +- js/src/jit/CodeGenerator.cpp | 35 +- js/src/jit/CodeGenerator.h | 1 + js/src/jit/Ion.cpp | 18 +- js/src/jit/IonBuilder.cpp | 51 +- js/src/jit/IonBuilder.h | 4 +- js/src/jit/IonTypes.h | 13 +- js/src/jit/Lowering.cpp | 22 +- js/src/jit/Lowering.h | 1 + js/src/jit/MIR.h | 74 +- js/src/jit/MOpcodes.h | 3 +- js/src/jit/TypePolicy.cpp | 1 + js/src/jit/VMFunctions.cpp | 27 +- js/src/jit/VMFunctions.h | 4 +- js/src/jit/shared/LIR-shared.h | 30 +- js/src/jit/shared/LOpcodes-shared.h | 3 +- js/src/js.msg | 4 + js/src/jsapi.cpp | 100 +- js/src/jsapi.h | 818 +++++------- js/src/jsfriendapi.h | 304 +++-- js/src/jsfun.cpp | 92 +- js/src/jsfun.h | 14 +- js/src/jsobj.cpp | 15 +- js/src/jsobj.h | 7 +- js/src/jsopcode.cpp | 1 + js/src/moz.build | 1 + js/src/shell/js.cpp | 21 +- .../extensions/strict-function-toSource.js | 4 +- js/src/tests/ecma_6/Class/classHeritage.js | 4 +- js/src/tests/ecma_6/Class/className.js | 12 +- .../ecma_6/Class/defaultConstructorBase.js | 25 + .../Class/defaultConstructorNotCallable.js | 15 + .../Class/derivedConstructorDisabled.js | 3 +- .../Class/derivedConstructorInlining.js | 19 + .../derivedConstructorReturnPrimitive.js | 15 + .../derivedConstructorTDZExplicitThis.js | 18 + .../Class/derivedConstructorTDZOffEdge.js | 11 + .../derivedConstructorTDZReturnObject.js | 13 + .../derivedConstructorTDZReturnUndefined.js | 13 + .../Class/superCallBadDynamicSuperClass.js | 19 + .../Class/superCallBadNewTargetPrototype.js | 32 + .../ecma_6/Class/superCallBaseInvoked.js | 64 + js/src/tests/ecma_6/Class/superCallIllegal.js | 7 + .../ecma_6/Class/superCallInvalidBase.js | 17 + js/src/tests/ecma_6/Class/superCallOrder.js | 33 + .../tests/ecma_6/Class/superCallProperBase.js | 41 + .../tests/ecma_6/Class/superCallSpreadCall.js | 34 + .../tests/ecma_6/Class/superCallThisInit.js | 52 + .../ecma_6/Class/superPropBasicGetter.js | 2 +- .../tests/ecma_6/Class/superPropBasicNew.js | 5 +- js/src/tests/ecma_6/Class/superPropChains.js | 10 +- js/src/tests/ecma_6/Class/superPropDelete.js | 2 +- .../ecma_6/Class/superPropDerivedCalls.js | 2 +- .../ecma_6/Class/superPropDestructuring.js | 2 +- .../tests/ecma_6/Class/superPropHomeObject.js | 4 +- .../tests/ecma_6/Class/superPropOrdering.js | 10 +- .../ecma_6/Class/superPropProtoChanges.js | 2 +- js/src/tests/ecma_6/Class/superPropProxies.js | 5 +- js/src/tests/ecma_6/Class/superPropSkips.js | 3 +- .../ecma_6/Proxy/revoke-as-side-effect.js | 9 +- js/src/tests/ecma_6/Reflect/construct.js | 17 +- .../js1_8_5/reflect-parse/PatternBuilders.js | 3 + js/src/tests/js1_8_5/reflect-parse/classes.js | 52 +- js/src/vm/Interpreter.cpp | 562 ++++---- js/src/vm/Interpreter.h | 8 + js/src/vm/ObjectGroup.cpp | 40 +- js/src/vm/Opcodes.h | 64 +- js/src/vm/Runtime.cpp | 455 +------ js/src/vm/Runtime.h | 298 +---- js/src/vm/Stack.cpp | 57 +- js/src/vm/Stack.h | 11 + js/src/vm/Stopwatch.cpp | 621 +++++++++ js/src/vm/Stopwatch.h | 391 ++++++ js/src/vm/StructuredClone.cpp | 26 +- js/src/vm/Xdr.h | 4 +- js/xpconnect/src/XPCJSRuntime.cpp | 47 +- layout/xul/tree/nsTreeBodyFrame.cpp | 11 +- modules/libpref/init/all.js | 3 + .../components/build/nsToolkitCompsModule.cpp | 2 +- .../osfile/NativeOSFileInternals.cpp | 60 +- .../perfmonitoring/PerformanceStats.jsm | 2 +- .../perfmonitoring/nsIPerformanceStats.idl | 33 +- .../perfmonitoring/nsPerformanceStats.cpp | 1130 ++++++++++++----- .../perfmonitoring/nsPerformanceStats.h | 463 ++++++- .../tests/browser/browser_compartments.js | 26 +- toolkit/components/places/Database.cpp | 1 + toolkit/components/places/SQLFunctions.cpp | 13 +- .../components/places/nsNavHistoryQuery.cpp | 7 +- toolkit/components/telemetry/Histograms.json | 2 +- toolkit/xre/nsUpdateDriver.cpp | 9 +- uriloader/exthandler/win/nsMIMEInfoWin.cpp | 8 +- 297 files changed, 11679 insertions(+), 4256 deletions(-) create mode 100644 dom/canvas/CanvasRenderingContextHelper.cpp create mode 100644 dom/canvas/CanvasRenderingContextHelper.h create mode 100644 dom/canvas/OffscreenCanvas.cpp create mode 100644 dom/canvas/OffscreenCanvas.h create mode 100644 dom/canvas/test/offscreencanvas.js create mode 100644 dom/canvas/test/offscreencanvas_mask.svg create mode 100644 dom/canvas/test/offscreencanvas_neuter.js create mode 100644 dom/canvas/test/offscreencanvas_serviceworker_inner.html create mode 100644 dom/canvas/test/reftest/filters/default-color.html create mode 100644 dom/canvas/test/reftest/filters/drop-shadow-transformed.html create mode 100644 dom/canvas/test/reftest/filters/drop-shadow.html create mode 100644 dom/canvas/test/reftest/filters/global-alpha-ref.html create mode 100644 dom/canvas/test/reftest/filters/global-alpha.html create mode 100644 dom/canvas/test/reftest/filters/global-composite-operation-ref.html create mode 100644 dom/canvas/test/reftest/filters/global-composite-operation.html create mode 100644 dom/canvas/test/reftest/filters/liveness.html create mode 100644 dom/canvas/test/reftest/filters/multiple-drop-shadows.html create mode 100644 dom/canvas/test/reftest/filters/ref.html create mode 100644 dom/canvas/test/reftest/filters/reftest.list create mode 100644 dom/canvas/test/reftest/filters/shadow-ref.html create mode 100644 dom/canvas/test/reftest/filters/shadow.html create mode 100644 dom/canvas/test/reftest/filters/subregion-fill-paint.html create mode 100644 dom/canvas/test/reftest/filters/subregion-ref.html create mode 100644 dom/canvas/test/reftest/filters/subregion-stroke-paint.html create mode 100644 dom/canvas/test/reftest/filters/svg-bbox-ref.html create mode 100644 dom/canvas/test/reftest/filters/svg-bbox.html create mode 100644 dom/canvas/test/reftest/filters/svg-inline.html create mode 100644 dom/canvas/test/reftest/filters/svg-liveness.html create mode 100644 dom/canvas/test/reftest/filters/svg-off-screen.html create mode 100644 dom/canvas/test/reftest/filters/units-em.html create mode 100644 dom/canvas/test/reftest/filters/units-ex.html create mode 100644 dom/canvas/test/reftest/filters/units-off-screen.html create mode 100644 dom/canvas/test/reftest/filters/units-pt.html create mode 100644 dom/canvas/test/reftest/filters/units.html create mode 100644 dom/canvas/test/test_filter.html create mode 100644 dom/canvas/test/test_offscreencanvas_basic_webgl.html create mode 100644 dom/canvas/test/test_offscreencanvas_dynamic_fallback.html create mode 100644 dom/canvas/test/test_offscreencanvas_many.html create mode 100644 dom/canvas/test/test_offscreencanvas_neuter.html create mode 100644 dom/canvas/test/test_offscreencanvas_serviceworker.html create mode 100644 dom/canvas/test/test_offscreencanvas_sharedworker.html create mode 100644 dom/canvas/test/test_offscreencanvas_sizechange.html create mode 100644 dom/canvas/test/test_offscreencanvas_subworker.html create mode 100644 dom/system/gonk/mozstumbler/MozStumbler.cpp create mode 100644 dom/system/gonk/mozstumbler/MozStumbler.h create mode 100644 dom/system/gonk/mozstumbler/StumblerLogging.cpp create mode 100644 dom/system/gonk/mozstumbler/StumblerLogging.h create mode 100644 dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp create mode 100644 dom/system/gonk/mozstumbler/UploadStumbleRunnable.h create mode 100644 dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp create mode 100644 dom/system/gonk/mozstumbler/WriteStumbleOnThread.h create mode 100644 dom/webidl/OffscreenCanvas.webidl create mode 100644 gfx/layers/AsyncCanvasRenderer.cpp create mode 100644 gfx/layers/AsyncCanvasRenderer.h delete mode 100644 js/src/jit-test/tests/auto-regress/bug776484.js create mode 100644 js/src/tests/ecma_6/Class/defaultConstructorBase.js create mode 100644 js/src/tests/ecma_6/Class/defaultConstructorNotCallable.js create mode 100644 js/src/tests/ecma_6/Class/derivedConstructorInlining.js create mode 100644 js/src/tests/ecma_6/Class/derivedConstructorReturnPrimitive.js create mode 100644 js/src/tests/ecma_6/Class/derivedConstructorTDZExplicitThis.js create mode 100644 js/src/tests/ecma_6/Class/derivedConstructorTDZOffEdge.js create mode 100644 js/src/tests/ecma_6/Class/derivedConstructorTDZReturnObject.js create mode 100644 js/src/tests/ecma_6/Class/derivedConstructorTDZReturnUndefined.js create mode 100644 js/src/tests/ecma_6/Class/superCallBadDynamicSuperClass.js create mode 100644 js/src/tests/ecma_6/Class/superCallBadNewTargetPrototype.js create mode 100644 js/src/tests/ecma_6/Class/superCallBaseInvoked.js create mode 100644 js/src/tests/ecma_6/Class/superCallIllegal.js create mode 100644 js/src/tests/ecma_6/Class/superCallInvalidBase.js create mode 100644 js/src/tests/ecma_6/Class/superCallOrder.js create mode 100644 js/src/tests/ecma_6/Class/superCallProperBase.js create mode 100644 js/src/tests/ecma_6/Class/superCallSpreadCall.js create mode 100644 js/src/tests/ecma_6/Class/superCallThisInit.js create mode 100644 js/src/vm/Stopwatch.cpp create mode 100644 js/src/vm/Stopwatch.h diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index e1556c4d32..3d8689226c 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -203,6 +203,9 @@ pref("geo.provider.use_mls", false); pref("geo.cell.scan", true); pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%"); +// base url for the stumbler +pref("geo.stumbler.url", "https://location.services.mozilla.com/v1/geosubmit?key=%MOZILLA_API_KEY%"); + // enable geo pref("geo.enabled", true); diff --git a/dom/base/ImageEncoder.cpp b/dom/base/ImageEncoder.cpp index c96ef24501..e5a751c5cf 100644 --- a/dom/base/ImageEncoder.cpp +++ b/dom/base/ImageEncoder.cpp @@ -8,6 +8,7 @@ #include "mozilla/dom/CanvasRenderingContext2D.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/DataSurfaceHelpers.h" +#include "mozilla/layers/AsyncCanvasRenderer.h" #include "mozilla/RefPtr.h" #include "mozilla/SyncRunnable.h" #include "mozilla/unused.h" @@ -165,6 +166,7 @@ public: mSize, mImage, nullptr, + nullptr, getter_AddRefs(stream), mEncoder); @@ -178,6 +180,7 @@ public: mSize, mImage, nullptr, + nullptr, getter_AddRefs(stream), mEncoder); } @@ -234,6 +237,7 @@ ImageEncoder::ExtractData(nsAString& aType, const nsAString& aOptions, const nsIntSize aSize, nsICanvasRenderingContextInternal* aContext, + layers::AsyncCanvasRenderer* aRenderer, nsIInputStream** aStream) { nsCOMPtr encoder = ImageEncoder::GetImageEncoder(aType); @@ -242,10 +246,9 @@ ImageEncoder::ExtractData(nsAString& aType, } return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, nullptr, - aContext, aStream, encoder); + aContext, aRenderer, aStream, encoder); } - /* static */ nsresult ImageEncoder::ExtractDataFromLayersImageAsync(nsAString& aType, @@ -341,6 +344,7 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType, const nsIntSize aSize, layers::Image* aImage, nsICanvasRenderingContextInternal* aContext, + layers::AsyncCanvasRenderer* aRenderer, nsIInputStream** aStream, imgIEncoder* aEncoder) { @@ -366,6 +370,11 @@ ImageEncoder::ExtractDataInternal(const nsAString& aType, rv = aContext->GetInputStream(encoderType.get(), nsPromiseFlatString(aOptions).get(), getter_AddRefs(imgStream)); + } else if (aRenderer) { + NS_ConvertUTF16toUTF8 encoderType(aType); + rv = aRenderer->GetInputStream(encoderType.get(), + nsPromiseFlatString(aOptions).get(), + getter_AddRefs(imgStream)); } else if (aImage) { // It is safe to convert PlanarYCbCr format from YUV to RGB off-main-thread. // Other image formats could have problem to convert format off-main-thread. diff --git a/dom/base/ImageEncoder.h b/dom/base/ImageEncoder.h index fe3522daff..a38bbd2091 100644 --- a/dom/base/ImageEncoder.h +++ b/dom/base/ImageEncoder.h @@ -19,6 +19,7 @@ class nsICanvasRenderingContextInternal; namespace mozilla { namespace layers { +class AsyncCanvasRenderer; class Image; } // namespace layers @@ -40,6 +41,7 @@ public: const nsAString& aOptions, const nsIntSize aSize, nsICanvasRenderingContextInternal* aContext, + layers::AsyncCanvasRenderer* aRenderer, nsIInputStream** aStream); // Extracts data asynchronously. aType may change to "image/png" if we had to @@ -84,7 +86,7 @@ public: nsIInputStream** aStream); private: - // When called asynchronously, aContext is null. + // When called asynchronously, aContext and aRenderer are null. static nsresult ExtractDataInternal(const nsAString& aType, const nsAString& aOptions, @@ -93,6 +95,7 @@ private: const nsIntSize aSize, layers::Image* aImage, nsICanvasRenderingContextInternal* aContext, + layers::AsyncCanvasRenderer* aRenderer, nsIInputStream** aStream, imgIEncoder* aEncoder); diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp index a9b41c9e5a..253a919aa7 100644 --- a/dom/base/StructuredCloneHolder.cpp +++ b/dom/base/StructuredCloneHolder.cpp @@ -21,6 +21,8 @@ #include "mozilla/dom/StructuredClone.h" #include "mozilla/dom/MessagePort.h" #include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/OffscreenCanvas.h" +#include "mozilla/dom/OffscreenCanvasBinding.h" #include "mozilla/dom/PMessagePort.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/SubtleCryptoBinding.h" @@ -1015,6 +1017,25 @@ StructuredCloneHolder::CustomReadTransferHandler(JSContext* aCx, return true; } + if (aTag == SCTAG_DOM_CANVAS) { + MOZ_ASSERT(mSupportedContext == SameProcessSameThread || + mSupportedContext == SameProcessDifferentThread); + MOZ_ASSERT(aContent); + OffscreenCanvasCloneData* data = + static_cast(aContent); + RefPtr canvas = OffscreenCanvas::CreateFromCloneData(data); + delete data; + + JS::Rooted value(aCx); + if (!GetOrCreateDOMReflector(aCx, canvas, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + aReturnObject.set(&value.toObject()); + return true; + } + return false; } @@ -1046,6 +1067,24 @@ StructuredCloneHolder::CustomWriteTransferHandler(JSContext* aCx, return true; } + + if (mSupportedContext == SameProcessSameThread || + mSupportedContext == SameProcessDifferentThread) { + OffscreenCanvas* canvas = nullptr; + rv = UNWRAP_OBJECT(OffscreenCanvas, aObj, canvas); + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(canvas); + + *aExtraData = 0; + *aTag = SCTAG_DOM_CANVAS; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = canvas->ToCloneData(); + MOZ_ASSERT(*aContent); + canvas->SetNeutered(); + + return true; + } + } } return false; @@ -1063,6 +1102,17 @@ StructuredCloneHolder::CustomFreeTransferHandler(uint32_t aTag, MOZ_ASSERT(!aContent); MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); MessagePort::ForceClose(mPortIdentifiers[aExtraData]); + return; + } + + if (aTag == SCTAG_DOM_CANVAS) { + MOZ_ASSERT(mSupportedContext == SameProcessSameThread || + mSupportedContext == SameProcessDifferentThread); + MOZ_ASSERT(aContent); + OffscreenCanvasCloneData* data = + static_cast(aContent); + delete data; + return; } } diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index fe6ccdf371..f1fec533d4 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -48,6 +48,9 @@ enum StructuredCloneTags { SCTAG_DOM_FORMDATA, + // This tag is for OffscreenCanvas. + SCTAG_DOM_CANVAS, + SCTAG_DOM_MAX }; diff --git a/dom/base/nsContentAreaDragDrop.cpp b/dom/base/nsContentAreaDragDrop.cpp index b32c254fa0..240ec7b970 100644 --- a/dom/base/nsContentAreaDragDrop.cpp +++ b/dom/base/nsContentAreaDragDrop.cpp @@ -57,6 +57,7 @@ #include "TabParent.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLAreaElement.h" +#include "nsVariant.h" using namespace mozilla::dom; @@ -726,11 +727,9 @@ DragDataProducer::AddString(DataTransfer* aDataTransfer, const nsAString& aData, nsIPrincipal* aPrincipal) { - nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); - if (variant) { - variant->SetAsAString(aData); - aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal); - } + RefPtr variant = new nsVariant(); + variant->SetAsAString(aData); + aDataTransfer->SetDataWithPrincipal(aFlavor, variant, 0, aPrincipal); } nsresult @@ -784,12 +783,10 @@ DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode, // a new flavor so as not to confuse anyone who is really registered // for image/gif or image/jpg. if (mImage) { - nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); - if (variant) { - variant->SetAsISupports(mImage); - aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kNativeImageMime), - variant, 0, principal); - } + RefPtr variant = new nsVariant(); + variant->SetAsISupports(mImage); + aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kNativeImageMime), + variant, 0, principal); // assume the image comes from a file, and add a file promise. We // register ourselves as a nsIFlavorDataProvider, and will use the @@ -798,12 +795,10 @@ DragDataProducer::AddStringsToDataTransfer(nsIContent* aDragNode, nsCOMPtr dataProvider = new nsContentAreaDragDropDataProvider(); if (dataProvider) { - nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); - if (variant) { - variant->SetAsISupports(dataProvider); - aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime), - variant, 0, principal); - } + RefPtr variant = new nsVariant(); + variant->SetAsISupports(dataProvider); + aDataTransfer->SetDataWithPrincipal(NS_LITERAL_STRING(kFilePromiseMime), + variant, 0, principal); } AddString(aDataTransfer, NS_LITERAL_STRING(kFilePromiseURLMime), diff --git a/dom/canvas/CanvasRenderingContextHelper.cpp b/dom/canvas/CanvasRenderingContextHelper.cpp new file mode 100644 index 0000000000..e47c1aef80 --- /dev/null +++ b/dom/canvas/CanvasRenderingContextHelper.cpp @@ -0,0 +1,265 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CanvasRenderingContextHelper.h" +#include "ImageEncoder.h" +#include "mozilla/dom/CanvasRenderingContext2D.h" +#include "mozilla/Telemetry.h" +#include "nsContentUtils.h" +#include "nsDOMJSUtils.h" +#include "nsIScriptContext.h" +#include "nsJSUtils.h" +#include "WebGL1Context.h" +#include "WebGL2Context.h" + +namespace mozilla { +namespace dom { + +void +CanvasRenderingContextHelper::ToBlob(JSContext* aCx, + nsIGlobalObject* aGlobal, + FileCallback& aCallback, + const nsAString& aType, + JS::Handle aParams, + ErrorResult& aRv) +{ + nsAutoString type; + nsContentUtils::ASCIIToLower(aType, type); + + nsAutoString params; + bool usingCustomParseOptions; + aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions); + if (aRv.Failed()) { + return; + } + + if (mCurrentContext) { + // We disallow canvases of width or height zero, and set them to 1, so + // we will have a discrepancy with the sizes of the canvas and the context. + // That discrepancy is OK, the rest are not. + nsIntSize elementSize = GetWidthHeight(); + if ((elementSize.width != mCurrentContext->GetWidth() && + (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) || + (elementSize.height != mCurrentContext->GetHeight() && + (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + } + + uint8_t* imageBuffer = nullptr; + int32_t format = 0; + if (mCurrentContext) { + mCurrentContext->GetImageBuffer(&imageBuffer, &format); + } + + // Encoder callback when encoding is complete. + class EncodeCallback : public EncodeCompleteCallback + { + public: + EncodeCallback(nsIGlobalObject* aGlobal, FileCallback* aCallback) + : mGlobal(aGlobal) + , mFileCallback(aCallback) {} + + // This is called on main thread. + nsresult ReceiveBlob(already_AddRefed aBlob) + { + RefPtr blob = aBlob; + + ErrorResult rv; + uint64_t size = blob->GetSize(rv); + if (rv.Failed()) { + rv.SuppressException(); + } else { + AutoJSAPI jsapi; + if (jsapi.Init(mGlobal)) { + JS_updateMallocCounter(jsapi.cx(), size); + } + } + + RefPtr newBlob = Blob::Create(mGlobal, blob->Impl()); + + mFileCallback->Call(*newBlob, rv); + + mGlobal = nullptr; + mFileCallback = nullptr; + + return rv.StealNSResult(); + } + + nsCOMPtr mGlobal; + RefPtr mFileCallback; + }; + + RefPtr callback = + new EncodeCallback(aGlobal, &aCallback); + + aRv = ImageEncoder::ExtractDataAsync(type, + params, + usingCustomParseOptions, + imageBuffer, + format, + GetWidthHeight(), + callback); +} + +already_AddRefed +CanvasRenderingContextHelper::CreateContext(CanvasContextType aContextType) +{ + MOZ_ASSERT(aContextType != CanvasContextType::NoContext); + RefPtr ret; + + switch (aContextType) { + case CanvasContextType::NoContext: + break; + + case CanvasContextType::Canvas2D: + Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); + ret = new CanvasRenderingContext2D(); + break; + + case CanvasContextType::WebGL1: + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1); + + ret = WebGL1Context::Create(); + if (!ret) + return nullptr; + + break; + + case CanvasContextType::WebGL2: + Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1); + + ret = WebGL2Context::Create(); + if (!ret) + return nullptr; + + break; + } + MOZ_ASSERT(ret); + + return ret.forget(); +} + +already_AddRefed +CanvasRenderingContextHelper::GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv) +{ + CanvasContextType contextType; + if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) + return nullptr; + + if (!mCurrentContext) { + // This canvas doesn't have a context yet. + RefPtr context; + context = CreateContext(contextType); + if (!context) { + return nullptr; + } + + // Ensure that the context participates in CC. Note that returning a + // CC participant from QI doesn't addref. + nsXPCOMCycleCollectionParticipant* cp = nullptr; + CallQueryInterface(context, &cp); + if (!cp) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + mCurrentContext = context.forget(); + mCurrentContextType = contextType; + + aRv = UpdateContext(aCx, aContextOptions); + if (aRv.Failed()) { + aRv = NS_OK; // See bug 645792 + return nullptr; + } + } else { + // We already have a context of some type. + if (contextType != mCurrentContextType) + return nullptr; + } + + nsCOMPtr context = mCurrentContext; + return context.forget(); +} + +nsresult +CanvasRenderingContextHelper::UpdateContext(JSContext* aCx, + JS::Handle aNewContextOptions) +{ + if (!mCurrentContext) + return NS_OK; + + nsIntSize sz = GetWidthHeight(); + + nsCOMPtr currentContext = mCurrentContext; + + nsresult rv = currentContext->SetIsOpaque(GetOpaqueAttr()); + if (NS_FAILED(rv)) { + mCurrentContext = nullptr; + return rv; + } + + rv = currentContext->SetContextOptions(aCx, aNewContextOptions); + if (NS_FAILED(rv)) { + mCurrentContext = nullptr; + return rv; + } + + rv = currentContext->SetDimensions(sz.width, sz.height); + if (NS_FAILED(rv)) { + mCurrentContext = nullptr; + } + + return rv; +} + +nsresult +CanvasRenderingContextHelper::ParseParams(JSContext* aCx, + const nsAString& aType, + const JS::Value& aEncoderOptions, + nsAString& outParams, + bool* const outUsingCustomParseOptions) +{ + // Quality parameter is only valid for the image/jpeg MIME type + if (aType.EqualsLiteral("image/jpeg")) { + if (aEncoderOptions.isNumber()) { + double quality = aEncoderOptions.toNumber(); + // Quality must be between 0.0 and 1.0, inclusive + if (quality >= 0.0 && quality <= 1.0) { + outParams.AppendLiteral("quality="); + outParams.AppendInt(NS_lround(quality * 100.0)); + } + } + } + + // If we haven't parsed the aParams check for proprietary options. + // The proprietary option -moz-parse-options will take a image lib encoder + // parse options string as is and pass it to the encoder. + *outUsingCustomParseOptions = false; + if (outParams.Length() == 0 && aEncoderOptions.isString()) { + NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:"); + nsAutoJSString paramString; + if (!paramString.init(aCx, aEncoderOptions.toString())) { + return NS_ERROR_FAILURE; + } + if (StringBeginsWith(paramString, mozParseOptions)) { + nsDependentSubstring parseOptions = Substring(paramString, + mozParseOptions.Length(), + paramString.Length() - + mozParseOptions.Length()); + outParams.Append(parseOptions); + *outUsingCustomParseOptions = true; + } + } + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/canvas/CanvasRenderingContextHelper.h b/dom/canvas/CanvasRenderingContextHelper.h new file mode 100644 index 0000000000..89cf9e8b3c --- /dev/null +++ b/dom/canvas/CanvasRenderingContextHelper.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_ +#define MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_ + +#include "mozilla/dom/BindingDeclarations.h" +#include "nsSize.h" + +class nsICanvasRenderingContextInternal; +class nsIGlobalObject; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class FileCallback; + +enum class CanvasContextType : uint8_t { + NoContext, + Canvas2D, + WebGL1, + WebGL2 +}; + +/** + * Povides common RenderingContext functionality used by both OffscreenCanvas + * and HTMLCanvasElement. + */ +class CanvasRenderingContextHelper +{ +public: + virtual already_AddRefed + GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv); + + virtual bool GetOpaqueAttr() = 0; + +protected: + virtual nsresult UpdateContext(JSContext* aCx, + JS::Handle aNewContextOptions); + + virtual nsresult ParseParams(JSContext* aCx, + const nsAString& aType, + const JS::Value& aEncoderOptions, + nsAString& outParams, + bool* const outCustomParseOptions); + + void ToBlob(JSContext* aCx, nsIGlobalObject* global, FileCallback& aCallback, + const nsAString& aType, JS::Handle aParams, + ErrorResult& aRv); + + virtual already_AddRefed + CreateContext(CanvasContextType aContextType); + + virtual nsIntSize GetWidthHeight() = 0; + + CanvasContextType mCurrentContextType; + nsCOMPtr mCurrentContext; +}; + +} // namespace dom +} // namespace mozilla + +#endif // MOZILLA_DOM_CANVASRENDERINGCONTEXTHELPER_H_ diff --git a/dom/canvas/CanvasUtils.cpp b/dom/canvas/CanvasUtils.cpp index 04fec40e8c..9d258c01f7 100644 --- a/dom/canvas/CanvasUtils.cpp +++ b/dom/canvas/CanvasUtils.cpp @@ -23,12 +23,47 @@ #include "CanvasUtils.h" #include "mozilla/gfx/Matrix.h" +#include "WebGL2Context.h" using namespace mozilla::gfx; namespace mozilla { namespace CanvasUtils { +bool +GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type) +{ + if (str.EqualsLiteral("2d")) { + *out_type = dom::CanvasContextType::Canvas2D; + return true; + } + + if (str.EqualsLiteral("experimental-webgl")) { + *out_type = dom::CanvasContextType::WebGL1; + return true; + } + +#ifdef MOZ_WEBGL_CONFORMANT + if (str.EqualsLiteral("webgl")) { + /* WebGL 1.0, $2.1 "Context Creation": + * If the user agent supports both the webgl and experimental-webgl + * canvas context types, they shall be treated as aliases. + */ + *out_type = dom::CanvasContextType::WebGL1; + return true; + } +#endif + + if (WebGL2Context::IsSupported()) { + if (str.EqualsLiteral("webgl2")) { + *out_type = dom::CanvasContextType::WebGL2; + return true; + } + } + + return false; +} + /** * This security check utility might be called from an source that never taints * others. For example, while painting a CanvasPattern, which is created from an diff --git a/dom/canvas/CanvasUtils.h b/dom/canvas/CanvasUtils.h index 044b847053..b1237b69e6 100644 --- a/dom/canvas/CanvasUtils.h +++ b/dom/canvas/CanvasUtils.h @@ -21,6 +21,7 @@ class HTMLCanvasElement; namespace CanvasUtils { +bool GetCanvasContextType(const nsAString& str, dom::CanvasContextType* const out_type); // Check that the rectangle [x,y,w,h] is a subrectangle of [0,0,realWidth,realHeight] diff --git a/dom/canvas/OffscreenCanvas.cpp b/dom/canvas/OffscreenCanvas.cpp new file mode 100644 index 0000000000..adb666018f --- /dev/null +++ b/dom/canvas/OffscreenCanvas.cpp @@ -0,0 +1,242 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "OffscreenCanvas.h" + +#include "mozilla/dom/OffscreenCanvasBinding.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/layers/AsyncCanvasRenderer.h" +#include "mozilla/layers/CanvasClient.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/Telemetry.h" +#include "CanvasRenderingContext2D.h" +#include "CanvasUtils.h" +#include "GLScreenBuffer.h" +#include "WebGL1Context.h" +#include "WebGL2Context.h" + +namespace mozilla { +namespace dom { + +OffscreenCanvasCloneData::OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer, + uint32_t aWidth, uint32_t aHeight, + layers::LayersBackend aCompositorBackend, + bool aNeutered) + : mRenderer(aRenderer) + , mWidth(aWidth) + , mHeight(aHeight) + , mCompositorBackendType(aCompositorBackend) + , mNeutered(aNeutered) +{ +} + +OffscreenCanvasCloneData::~OffscreenCanvasCloneData() +{ +} + +OffscreenCanvas::OffscreenCanvas(uint32_t aWidth, + uint32_t aHeight, + layers::LayersBackend aCompositorBackend, + layers::AsyncCanvasRenderer* aRenderer) + : mAttrDirty(false) + , mNeutered(false) + , mWidth(aWidth) + , mHeight(aHeight) + , mCompositorBackendType(aCompositorBackend) + , mCanvasClient(nullptr) + , mCanvasRenderer(aRenderer) +{} + +OffscreenCanvas::~OffscreenCanvas() +{ + ClearResources(); +} + +OffscreenCanvas* +OffscreenCanvas::GetParentObject() const +{ + return nullptr; +} + +JSObject* +OffscreenCanvas::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) +{ + return OffscreenCanvasBinding::Wrap(aCx, this, aGivenProto); +} + +void +OffscreenCanvas::ClearResources() +{ + if (mCanvasClient) { + mCanvasClient->Clear(); + ImageBridgeChild::DispatchReleaseCanvasClient(mCanvasClient); + mCanvasClient = nullptr; + + if (mCanvasRenderer) { + nsCOMPtr activeThread = mCanvasRenderer->GetActiveThread(); + MOZ_RELEASE_ASSERT(activeThread); + MOZ_RELEASE_ASSERT(activeThread == NS_GetCurrentThread()); + mCanvasRenderer->SetCanvasClient(nullptr); + mCanvasRenderer->mContext = nullptr; + mCanvasRenderer->mGLContext = nullptr; + mCanvasRenderer->ResetActiveThread(); + } + } +} + +already_AddRefed +OffscreenCanvas::GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv) +{ + if (mNeutered) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + // We only support WebGL in workers for now + CanvasContextType contextType; + if (!CanvasUtils::GetCanvasContextType(aContextId, &contextType)) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; + } + + if (!(contextType == CanvasContextType::WebGL1 || + contextType == CanvasContextType::WebGL2)) + { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; + } + + already_AddRefed result = + CanvasRenderingContextHelper::GetContext(aCx, + aContextId, + aContextOptions, + aRv); + + if (!mCurrentContext) { + return nullptr; + } + + if (mCanvasRenderer) { + WebGLContext* webGL = static_cast(mCurrentContext.get()); + gl::GLContext* gl = webGL->GL(); + mCanvasRenderer->mContext = mCurrentContext; + mCanvasRenderer->SetActiveThread(); + mCanvasRenderer->mGLContext = gl; + mCanvasRenderer->SetIsAlphaPremultiplied(webGL->IsPremultAlpha() || !gl->Caps().alpha); + + if (ImageBridgeChild::IsCreated()) { + TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT; + mCanvasClient = ImageBridgeChild::GetSingleton()-> + CreateCanvasClient(CanvasClient::CanvasClientTypeShSurf, flags).take(); + mCanvasRenderer->SetCanvasClient(mCanvasClient); + + gl::GLScreenBuffer* screen = gl->Screen(); + gl::SurfaceCaps caps = screen->mCaps; + auto forwarder = mCanvasClient->GetForwarder(); + + UniquePtr factory = + gl::GLScreenBuffer::CreateFactory(gl, caps, forwarder, flags); + + if (factory) + screen->Morph(Move(factory)); + } + } + + return result; +} + +already_AddRefed +OffscreenCanvas::CreateContext(CanvasContextType aContextType) +{ + RefPtr ret = + CanvasRenderingContextHelper::CreateContext(aContextType); + + ret->SetOffscreenCanvas(this); + return ret.forget(); +} + +void +OffscreenCanvas::CommitFrameToCompositor() +{ + // The attributes has changed, we have to notify main + // thread to change canvas size. + if (mAttrDirty) { + if (mCanvasRenderer) { + mCanvasRenderer->SetWidth(mWidth); + mCanvasRenderer->SetHeight(mHeight); + mCanvasRenderer->NotifyElementAboutAttributesChanged(); + } + mAttrDirty = false; + } + + if (mCurrentContext) { + static_cast(mCurrentContext.get())->PresentScreenBuffer(); + } + + if (mCanvasRenderer && mCanvasRenderer->mGLContext) { + mCanvasRenderer->NotifyElementAboutInvalidation(); + ImageBridgeChild::GetSingleton()-> + UpdateAsyncCanvasRenderer(mCanvasRenderer); + } +} + +OffscreenCanvasCloneData* +OffscreenCanvas::ToCloneData() +{ + return new OffscreenCanvasCloneData(mCanvasRenderer, mWidth, mHeight, + mCompositorBackendType, mNeutered); +} + +/* static */ already_AddRefed +OffscreenCanvas::CreateFromCloneData(OffscreenCanvasCloneData* aData) +{ + MOZ_ASSERT(aData); + RefPtr wc = + new OffscreenCanvas(aData->mWidth, aData->mHeight, + aData->mCompositorBackendType, aData->mRenderer); + if (aData->mNeutered) { + wc->SetNeutered(); + } + return wc.forget(); +} + +/* static */ bool +OffscreenCanvas::PrefEnabled(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("gfx.offscreencanvas.enabled"); + } else { + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + MOZ_ASSERT(workerPrivate); + return workerPrivate->OffscreenCanvasEnabled(); + } +} + +/* static */ bool +OffscreenCanvas::PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return true; + } + + return PrefEnabled(aCx, aObj); +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(OffscreenCanvas, DOMEventTargetHelper, mCurrentContext) + +NS_IMPL_ADDREF_INHERITED(OffscreenCanvas, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(OffscreenCanvas, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OffscreenCanvas) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +} // namespace dom +} // namespace mozilla diff --git a/dom/canvas/OffscreenCanvas.h b/dom/canvas/OffscreenCanvas.h new file mode 100644 index 0000000000..cdad6bcce8 --- /dev/null +++ b/dom/canvas/OffscreenCanvas.h @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_DOM_OFFSCREENCANVAS_H_ +#define MOZILLA_DOM_OFFSCREENCANVAS_H_ + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/RefPtr.h" +#include "CanvasRenderingContextHelper.h" +#include "nsCycleCollectionParticipant.h" + +struct JSContext; + +namespace mozilla { + +class ErrorResult; + +namespace layers { +class AsyncCanvasRenderer; +class CanvasClient; +} // namespace layers + +namespace dom { + +// This is helper class for transferring OffscreenCanvas to worker thread. +// Because OffscreenCanvas is not thread-safe. So we cannot pass Offscreen- +// Canvas to worker thread directly. Thus, we create this helper class and +// store necessary data in it then pass it to worker thread. +struct OffscreenCanvasCloneData final +{ + OffscreenCanvasCloneData(layers::AsyncCanvasRenderer* aRenderer, + uint32_t aWidth, uint32_t aHeight, + layers::LayersBackend aCompositorBackend, + bool aNeutered); + ~OffscreenCanvasCloneData(); + + RefPtr mRenderer; + uint32_t mWidth; + uint32_t mHeight; + layers::LayersBackend mCompositorBackendType; + bool mNeutered; +}; + +class OffscreenCanvas final : public DOMEventTargetHelper + , public CanvasRenderingContextHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OffscreenCanvas, DOMEventTargetHelper) + + OffscreenCanvas(uint32_t aWidth, + uint32_t aHeight, + layers::LayersBackend aCompositorBackend, + layers::AsyncCanvasRenderer* aRenderer); + + OffscreenCanvas* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + void ClearResources(); + + uint32_t Width() const + { + return mWidth; + } + + uint32_t Height() const + { + return mHeight; + } + + void SetWidth(uint32_t aWidth, ErrorResult& aRv) + { + if (mNeutered) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (mWidth != aWidth) { + mWidth = aWidth; + CanvasAttrChanged(); + } + } + + void SetHeight(uint32_t aHeight, ErrorResult& aRv) + { + if (mNeutered) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + if (mHeight != aHeight) { + mHeight = aHeight; + CanvasAttrChanged(); + } + } + + nsICanvasRenderingContextInternal* GetContext() const + { + return mCurrentContext; + } + + static already_AddRefed + CreateFromCloneData(OffscreenCanvasCloneData* aData); + + static bool PrefEnabled(JSContext* aCx, JSObject* aObj); + + // Return true on main-thread, and return gfx.offscreencanvas.enabled + // on worker thread. + static bool PrefEnabledOnWorkerThread(JSContext* aCx, JSObject* aObj); + + OffscreenCanvasCloneData* ToCloneData(); + + void CommitFrameToCompositor(); + + virtual bool GetOpaqueAttr() override + { + return false; + } + + virtual nsIntSize GetWidthHeight() override + { + return nsIntSize(mWidth, mHeight); + } + + virtual already_AddRefed + CreateContext(CanvasContextType aContextType) override; + + virtual already_AddRefed + GetContext(JSContext* aCx, + const nsAString& aContextId, + JS::Handle aContextOptions, + ErrorResult& aRv) override; + + void SetNeutered() + { + mNeutered = true; + } + + bool IsNeutered() const + { + return mNeutered; + } + + layers::LayersBackend GetCompositorBackendType() const + { + return mCompositorBackendType; + } + +private: + ~OffscreenCanvas(); + + void CanvasAttrChanged() + { + mAttrDirty = true; + UpdateContext(nullptr, JS::NullHandleValue); + } + + bool mAttrDirty; + bool mNeutered; + + uint32_t mWidth; + uint32_t mHeight; + + layers::LayersBackend mCompositorBackendType; + + layers::CanvasClient* mCanvasClient; + RefPtr mCanvasRenderer; +}; + +} // namespace dom +} // namespace mozilla + +#endif // MOZILLA_DOM_OFFSCREENCANVAS_H_ diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index afdb7f342e..ab1db1b407 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -22,6 +22,7 @@ #include "ImageEncoder.h" #include "Layers.h" #include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Event.h" #include "mozilla/dom/HTMLVideoElement.h" #include "mozilla/dom/ImageData.h" #include "mozilla/EnumeratedArrayCycleCollection.h" @@ -79,125 +80,6 @@ using namespace mozilla::gfx; using namespace mozilla::gl; using namespace mozilla::layers; -WebGLObserver::WebGLObserver(WebGLContext* webgl) - : mWebGL(webgl) -{ -} - -WebGLObserver::~WebGLObserver() -{ -} - -void -WebGLObserver::Destroy() -{ - UnregisterMemoryPressureEvent(); - UnregisterVisibilityChangeEvent(); - mWebGL = nullptr; -} - -void -WebGLObserver::RegisterVisibilityChangeEvent() -{ - if (!mWebGL) - return; - - HTMLCanvasElement* canvas = mWebGL->GetCanvas(); - MOZ_ASSERT(canvas); - - if (canvas) { - nsIDocument* document = canvas->OwnerDoc(); - - document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), - this, true, false); - } -} - -void -WebGLObserver::UnregisterVisibilityChangeEvent() -{ - if (!mWebGL) - return; - - HTMLCanvasElement* canvas = mWebGL->GetCanvas(); - - if (canvas) { - nsIDocument* document = canvas->OwnerDoc(); - - document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), - this, true); - } -} - -void -WebGLObserver::RegisterMemoryPressureEvent() -{ - if (!mWebGL) - return; - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - - MOZ_ASSERT(observerService); - - if (observerService) - observerService->AddObserver(this, "memory-pressure", false); -} - -void -WebGLObserver::UnregisterMemoryPressureEvent() -{ - if (!mWebGL) - return; - - nsCOMPtr observerService = - mozilla::services::GetObserverService(); - - // Do not assert on observerService here. This might be triggered by - // the cycle collector at a late enough time, that XPCOM services are - // no longer available. See bug 1029504. - if (observerService) - observerService->RemoveObserver(this, "memory-pressure"); -} - -NS_IMETHODIMP -WebGLObserver::Observe(nsISupports*, const char* topic, const char16_t*) -{ - if (!mWebGL || strcmp(topic, "memory-pressure")) { - return NS_OK; - } - - bool wantToLoseContext = mWebGL->mLoseContextOnMemoryPressure; - - if (!mWebGL->mCanLoseContextInForeground && - ProcessPriorityManager::CurrentProcessIsForeground()) - { - wantToLoseContext = false; - } - - if (wantToLoseContext) - mWebGL->ForceLoseContext(); - - return NS_OK; -} - -NS_IMETHODIMP -WebGLObserver::HandleEvent(nsIDOMEvent* event) -{ - nsAutoString type; - event->GetType(type); - if (!mWebGL || !type.EqualsLiteral("visibilitychange")) - return NS_OK; - - HTMLCanvasElement* canvas = mWebGL->GetCanvas(); - MOZ_ASSERT(canvas); - - if (canvas && !canvas->OwnerDoc()->Hidden()) - mWebGL->ForceRestoreContext(); - - return NS_OK; -} - WebGLContextOptions::WebGLContextOptions() : alpha(true) , depth(true) @@ -208,7 +90,7 @@ WebGLContextOptions::WebGLContextOptions() , failIfMajorPerformanceCaveat(false) { // Set default alpha state based on preference. - if (Preferences::GetBool("webgl.default-no-alpha", false)) + if (gfxPrefs::WebGLDefaultNoAlpha()) alpha = false; } @@ -280,7 +162,10 @@ WebGLContext::WebGLContext() mPixelStorePackAlignment = 4; mPixelStoreUnpackAlignment = 4; - WebGLMemoryTracker::AddWebGLContext(this); + if (NS_IsMainThread()) { + // XXX mtseng: bug 709490, not thread safe + WebGLMemoryTracker::AddWebGLContext(this); + } mAllowContextRestore = true; mLastLossWasSimulated = false; @@ -294,15 +179,12 @@ WebGLContext::WebGLContext() mAlreadyWarnedAboutFakeVertexAttrib0 = false; mAlreadyWarnedAboutViewportLargerThanDest = false; - mMaxWarnings = Preferences::GetInt("webgl.max-warnings-per-context", 32); + mMaxWarnings = gfxPrefs::WebGLMaxWarningsPerContext(); if (mMaxWarnings < -1) { GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)"); mMaxWarnings = 0; } - mContextObserver = new WebGLObserver(this); - MOZ_RELEASE_ASSERT(mContextObserver, "Can't alloc WebGLContextObserver"); - mLastUseIndex = 0; InvalidateBufferFetching(); @@ -317,10 +199,12 @@ WebGLContext::WebGLContext() WebGLContext::~WebGLContext() { RemovePostRefreshObserver(); - mContextObserver->Destroy(); DestroyResourcesAndContext(); - WebGLMemoryTracker::RemoveWebGLContext(this); + if (NS_IsMainThread()) { + // XXX mtseng: bug 709490, not thread safe + WebGLMemoryTracker::RemoveWebGLContext(this); + } mContextLossHandler->DisableTimer(); mContextLossHandler = nullptr; @@ -329,8 +213,6 @@ WebGLContext::~WebGLContext() void WebGLContext::DestroyResourcesAndContext() { - mContextObserver->UnregisterMemoryPressureEvent(); - if (!gl) return; @@ -429,6 +311,35 @@ WebGLContext::Invalidate() mCanvasElement->InvalidateCanvasContent(nullptr); } +void +WebGLContext::OnVisibilityChange() +{ + if (!IsContextLost()) { + return; + } + + if (!mRestoreWhenVisible || mLastLossWasSimulated) { + return; + } + + ForceRestoreContext(); +} + +void +WebGLContext::OnMemoryPressure() +{ + bool shouldLoseContext = mLoseContextOnMemoryPressure; + + if (!mCanLoseContextInForeground && + ProcessPriorityManager::CurrentProcessIsForeground()) + { + shouldLoseContext = false; + } + + if (shouldLoseContext) + ForceLoseContext(); +} + // // nsICanvasRenderingContextInternal // @@ -511,7 +422,7 @@ static bool IsFeatureInBlacklist(const nsCOMPtr& gfxInfo, int32_t feature) { int32_t status; - if (!NS_SUCCEEDED(gfxInfo->GetFeatureStatus(feature, &status))) + if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, feature, &status))) return false; return status != nsIGfxInfo::FEATURE_STATUS_OK; @@ -522,19 +433,29 @@ HasAcceleratedLayers(const nsCOMPtr& gfxInfo) { int32_t status; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + &status); if (status) return true; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &status); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_OPENGL_LAYERS, + &status); if (status) return true; @@ -606,7 +527,7 @@ CreateHeadlessGL(CreateContextFlags flags, const nsCOMPtr& gfxInfo, WebGLContext* webgl) { bool preferEGL = PR_GetEnv("MOZ_WEBGL_PREFER_EGL"); - bool disableANGLE = Preferences::GetBool("webgl.disable-angle", false); + bool disableANGLE = gfxPrefs::WebGLDisableANGLE(); if (PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL")) disableANGLE = true; @@ -703,7 +624,7 @@ CreateOffscreen(GLContext* gl, const WebGLContextOptions& options, // we should really have this behind a // |gfxPlatform::GetPlatform()->GetScreenDepth() == 16| check, but // for now it's just behind a pref for testing/evaluation. - baseCaps.bpp16 = Preferences::GetBool("webgl.prefer-16bpp", false); + baseCaps.bpp16 = gfxPrefs::WebGLPrefer16bpp(); #ifdef MOZ_WIDGET_GONK baseCaps.surfaceAllocator = surfAllocator; @@ -711,7 +632,7 @@ CreateOffscreen(GLContext* gl, const WebGLContextOptions& options, // Done with baseCaps construction. - bool forceAllowAA = Preferences::GetBool("webgl.msaa-force", false); + bool forceAllowAA = gfxPrefs::WebGLForceMSAA(); if (!forceAllowAA && IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA)) { @@ -824,10 +745,6 @@ WebGLContext::ResizeBackbuffer(uint32_t requestedWidth, NS_IMETHODIMP WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) { - // Early error return cases - if (!GetCanvas()) - return NS_ERROR_FAILURE; - if (signedWidth < 0 || signedHeight < 0) { GenerateWarning("Canvas size is too large (seems like a negative value wrapped)"); return NS_ERROR_OUT_OF_MEMORY; @@ -837,7 +754,10 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) uint32_t height = signedHeight; // Early success return cases - GetCanvas()->InvalidateCanvas(); + + // May have a OffscreenCanvas instead of an HTMLCanvasElement + if (GetCanvas()) + GetCanvas()->InvalidateCanvas(); // Zero-sized surfaces can cause problems. if (width == 0) @@ -905,10 +825,7 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) // pick up the old generation. ++mGeneration; - // Get some prefs for some preferred/overriden things - NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); - - bool disabled = Preferences::GetBool("webgl.disabled", false); + bool disabled = gfxPrefs::WebGLDisabled(); // TODO: When we have software webgl support we should use that instead. disabled |= gfxPlatform::InSafeMode(); @@ -931,7 +848,7 @@ WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) } // Alright, now let's start trying. - bool forceEnabled = Preferences::GetBool("webgl.force-enabled", false); + bool forceEnabled = gfxPrefs::WebGLForceEnabled(); ScopedGfxFeatureReporter reporter("WebGL", forceEnabled); if (!CreateOffscreenGL(forceEnabled)) { @@ -1036,6 +953,11 @@ WebGLContext::LoseOldestWebGLContextIfLimitExceeded() #endif MOZ_ASSERT(kMaxWebGLContextsPerPrincipal < kMaxWebGLContexts); + if (!NS_IsMainThread()) { + // XXX mtseng: bug 709490, WebGLMemoryTracker is not thread safe. + return; + } + // it's important to update the index on a new context before losing old contexts, // otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones // when choosing which one to lose first. @@ -1123,32 +1045,8 @@ WebGLContext::GetImageBuffer(uint8_t** out_imageBuffer, int32_t* out_format) RefPtr dataSurface = snapshot->GetDataSurface(); - DataSourceSurface::MappedSurface map; - if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) - return; - - uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4]; - if (!imageBuffer) { - dataSurface->Unmap(); - return; - } - memcpy(imageBuffer, map.mData, mWidth * mHeight * 4); - - dataSurface->Unmap(); - - int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; - if (!mOptions.premultipliedAlpha) { - // We need to convert to INPUT_FORMAT_RGBA, otherwise - // we are automatically considered premult, and unpremult'd. - // Yes, it is THAT silly. - // Except for different lossy conversions by color, - // we could probably just change the label, and not change the data. - gfxUtils::ConvertBGRAtoRGBA(imageBuffer, mWidth * mHeight * 4); - format = imgIEncoder::INPUT_FORMAT_RGBA; - } - - *out_imageBuffer = imageBuffer; - *out_format = format; + return gfxUtils::GetImageBuffer(dataSurface, mOptions.premultipliedAlpha, + out_imageBuffer, out_format); } NS_IMETHODIMP @@ -1160,20 +1058,18 @@ WebGLContext::GetInputStream(const char* mimeType, if (!gl) return NS_ERROR_FAILURE; - nsCString enccid("@mozilla.org/image/encoder;2?type="); - enccid += mimeType; - nsCOMPtr encoder = do_CreateInstance(enccid.get()); - if (!encoder) + // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied + bool premult; + RefPtr snapshot = + GetSurfaceSnapshot(mOptions.premultipliedAlpha ? nullptr : &premult); + if (!snapshot) return NS_ERROR_FAILURE; - nsAutoArrayPtr imageBuffer; - int32_t format = 0; - GetImageBuffer(getter_Transfers(imageBuffer), &format); - if (!imageBuffer) - return NS_ERROR_FAILURE; + MOZ_ASSERT(mOptions.premultipliedAlpha || !premult, "We must get unpremult when we ask for it!"); - return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format, - encoder, encoderOptions, out_stream); + RefPtr dataSurface = snapshot->GetDataSurface(); + return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType, + encoderOptions, out_stream); } void @@ -1252,25 +1148,26 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder, } WebGLContextUserData* userData = nullptr; - if (builder->IsPaintingToWindow()) { - // Make the layer tell us whenever a transaction finishes (including - // the current transaction), so we can clear our invalidation state and - // start invalidating again. We need to do this for the layer that is - // being painted to a window (there shouldn't be more than one at a time, - // and if there is, flushing the invalidation state more often than - // necessary is harmless). + if (builder->IsPaintingToWindow() && mCanvasElement) { + // Make the layer tell us whenever a transaction finishes (including + // the current transaction), so we can clear our invalidation state and + // start invalidating again. We need to do this for the layer that is + // being painted to a window (there shouldn't be more than one at a time, + // and if there is, flushing the invalidation state more often than + // necessary is harmless). - // The layer will be destroyed when we tear down the presentation - // (at the latest), at which time this userData will be destroyed, - // releasing the reference to the element. - // The userData will receive DidTransactionCallbacks, which flush the - // the invalidation state to indicate that the canvas is up to date. - userData = new WebGLContextUserData(mCanvasElement); - canvasLayer->SetDidTransactionCallback( - WebGLContextUserData::DidTransactionCallback, userData); - canvasLayer->SetPreTransactionCallback( - WebGLContextUserData::PreTransactionCallback, userData); + // The layer will be destroyed when we tear down the presentation + // (at the latest), at which time this userData will be destroyed, + // releasing the reference to the element. + // The userData will receive DidTransactionCallbacks, which flush the + // the invalidation state to indicate that the canvas is up to date. + userData = new WebGLContextUserData(mCanvasElement); + canvasLayer->SetDidTransactionCallback( + WebGLContextUserData::DidTransactionCallback, userData); + canvasLayer->SetPreTransactionCallback( + WebGLContextUserData::PreTransactionCallback, userData); } + canvasLayer->SetUserData(&gWebGLLayerUserData, userData); CanvasLayer::Data data; @@ -1292,14 +1189,36 @@ WebGLContext::GetCanvasLayer(nsDisplayListBuilder* builder, layers::LayersBackend WebGLContext::GetCompositorBackendType() const { - nsIWidget* docWidget = nsContentUtils::WidgetForDocument(mCanvasElement->OwnerDoc()); - if (docWidget) { - layers::LayerManager* layerManager = docWidget->GetLayerManager(); - return layerManager->GetCompositorBackendType(); + if (mCanvasElement) { + return mCanvasElement->GetCompositorBackendType(); + } else if (mOffscreenCanvas) { + return mOffscreenCanvas->GetCompositorBackendType(); } + return LayersBackend::LAYERS_NONE; } +void +WebGLContext::Commit() +{ + if (mOffscreenCanvas) { + mOffscreenCanvas->CommitFrameToCompositor(); + } +} + +void +WebGLContext::GetCanvas(Nullable& retval) +{ + if (mCanvasElement) { + MOZ_RELEASE_ASSERT(!mOffscreenCanvas); + retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement; + } else if (mOffscreenCanvas) { + retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas; + } else { + retval.SetNull(); + } +} + void WebGLContext::GetContextAttributes(dom::Nullable& retval) { @@ -1608,7 +1527,7 @@ WebGLContext::RunContextLossTimer() mContextLossHandler->RunTimer(); } -class UpdateContextLossStatusTask : public nsRunnable +class UpdateContextLossStatusTask : public nsCancelableRunnable { RefPtr mWebGL; @@ -1619,10 +1538,16 @@ public: } NS_IMETHOD Run() { - mWebGL->UpdateContextLossStatus(); + if (mWebGL) + mWebGL->UpdateContextLossStatus(); return NS_OK; } + + NS_IMETHOD Cancel() { + mWebGL = nullptr; + return NS_OK; + } }; void @@ -1649,7 +1574,7 @@ WebGLContext::EnqueueUpdateContextLossStatus() void WebGLContext::UpdateContextLossStatus() { - if (!mCanvasElement) { + if (!mCanvasElement && !mOffscreenCanvas) { // the canvas is gone. That happens when the page was closed before we got // this timer event. In this case, there's nothing to do here, just don't crash. return; @@ -1677,12 +1602,23 @@ WebGLContext::UpdateContextLossStatus() // callback, so do that now. bool useDefaultHandler; - nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), - static_cast(mCanvasElement), - NS_LITERAL_STRING("webglcontextlost"), - true, - true, - &useDefaultHandler); + + if (mCanvasElement) { + nsContentUtils::DispatchTrustedEvent( + mCanvasElement->OwnerDoc(), + static_cast(mCanvasElement), + NS_LITERAL_STRING("webglcontextlost"), + true, + true, + &useDefaultHandler); + } else { + // OffscreenCanvas case + RefPtr event = new Event(mOffscreenCanvas, nullptr, nullptr); + event->InitEvent(NS_LITERAL_STRING("webglcontextlost"), true, true); + event->SetTrusted(true); + mOffscreenCanvas->DispatchEvent(event, &useDefaultHandler); + } + // We sent the callback, so we're just 'regular lost' now. mContextStatus = ContextLost; // If we're told to use the default handler, it means the script @@ -1734,11 +1670,22 @@ WebGLContext::UpdateContextLossStatus() // Revival! mContextStatus = ContextNotLost; - nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), - static_cast(mCanvasElement), - NS_LITERAL_STRING("webglcontextrestored"), - true, - true); + + if (mCanvasElement) { + nsContentUtils::DispatchTrustedEvent( + mCanvasElement->OwnerDoc(), + static_cast(mCanvasElement), + NS_LITERAL_STRING("webglcontextrestored"), + true, + true); + } else { + RefPtr event = new Event(mOffscreenCanvas, nullptr, nullptr); + event->InitEvent(NS_LITERAL_STRING("webglcontextrestored"), true, true); + event->SetTrusted(true); + bool unused; + mOffscreenCanvas->DispatchEvent(event, &unused); + } + mEmitContextLostErrorOnce = true; return; } @@ -1756,12 +1703,6 @@ WebGLContext::ForceLoseContext(bool simulateLosing) DestroyResourcesAndContext(); mLastLossWasSimulated = simulateLosing; - // Register visibility change observer to defer the context restoring. - // Restore the context when the app is visible. - if (mRestoreWhenVisible && !mLastLossWasSimulated) { - mContextObserver->RegisterVisibilityChangeEvent(); - } - // Queue up a task, since we know the status changed. EnqueueUpdateContextLossStatus(); } @@ -1773,8 +1714,6 @@ WebGLContext::ForceRestoreContext() mContextStatus = ContextLostAwaitingRestore; mAllowContextRestore = true; // Hey, you did say 'force'. - mContextObserver->UnregisterVisibilityChangeEvent(); - // Queue up a task, since we know the status changed. EnqueueUpdateContextLossStatus(); } @@ -1902,6 +1841,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLContext, mCanvasElement, + mOffscreenCanvas, mExtensions, mBound2DTextures, mBoundCubeMapTextures, diff --git a/dom/canvas/WebGLContext.h b/dom/canvas/WebGLContext.h index 54aa415bdd..62a9eb5257 100644 --- a/dom/canvas/WebGLContext.h +++ b/dom/canvas/WebGLContext.h @@ -38,7 +38,11 @@ // Generated #include "nsIDOMEventListener.h" #include "nsIDOMWebGLRenderingContext.h" +#include "nsICanvasRenderingContextInternal.h" #include "nsIObserver.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "nsWrapperCache.h" +#include "nsLayoutUtils.h" class nsIDocShell; @@ -78,7 +82,6 @@ class WebGLContextLossHandler; class WebGLBuffer; class WebGLExtensionBase; class WebGLFramebuffer; -class WebGLObserver; class WebGLProgram; class WebGLQuery; class WebGLRenderbuffer; @@ -93,6 +96,7 @@ class WebGLVertexArray; namespace dom { class Element; class ImageData; +class OwningHTMLCanvasElementOrOffscreenCanvas; struct WebGLContextAttributes; template struct Nullable; } // namespace dom @@ -182,7 +186,6 @@ class WebGLContext friend class WebGLExtensionLoseContext; friend class WebGLExtensionVertexArray; friend class WebGLMemoryTracker; - friend class WebGLObserver; enum { UNPACK_FLIP_Y_WEBGL = 0x9240, @@ -212,6 +215,9 @@ public: NS_DECL_NSIDOMWEBGLRENDERINGCONTEXT + virtual void OnVisibilityChange() override; + virtual void OnMemoryPressure() override; + // nsICanvasRenderingContextInternal virtual int32_t GetWidth() const override; virtual int32_t GetHeight() const override; @@ -359,8 +365,11 @@ public: void AssertCachedBindings(); void AssertCachedState(); - // WebIDL WebGLRenderingContext API dom::HTMLCanvasElement* GetCanvas() const { return mCanvasElement; } + + // WebIDL WebGLRenderingContext API + void Commit(); + void GetCanvas(Nullable& retval); GLsizei DrawingBufferWidth() const { return IsContextLost() ? 0 : mWidth; } GLsizei DrawingBufferHeight() const { return IsContextLost() ? 0 : mHeight; @@ -1488,8 +1497,6 @@ protected: ForceDiscreteGPUHelperCGL mForceDiscreteGPUHelper; #endif - RefPtr mContextObserver; - public: // console logging helpers void GenerateWarning(const char* fmt, ...); @@ -1592,32 +1599,6 @@ WebGLContext::ValidateObject(const char* info, ObjectType* object) return ValidateObjectAssumeNonNull(info, object); } -// Listen visibilitychange and memory-pressure event for context lose/restore -class WebGLObserver final - : public nsIObserver - , public nsIDOMEventListener -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - NS_DECL_NSIDOMEVENTLISTENER - - explicit WebGLObserver(WebGLContext* webgl); - - void Destroy(); - - void RegisterVisibilityChangeEvent(); - void UnregisterVisibilityChangeEvent(); - - void RegisterMemoryPressureEvent(); - void UnregisterMemoryPressureEvent(); - -private: - ~WebGLObserver(); - - WebGLContext* mWebGL; -}; - size_t RoundUpToMultipleOf(size_t value, size_t multiple); bool diff --git a/dom/canvas/WebGLContextExtensions.cpp b/dom/canvas/WebGLContextExtensions.cpp index c620e6beef..70a3a423fa 100644 --- a/dom/canvas/WebGLContextExtensions.cpp +++ b/dom/canvas/WebGLContextExtensions.cpp @@ -6,6 +6,7 @@ #include "WebGLContext.h" #include "WebGLContextUtils.h" #include "WebGLExtensions.h" +#include "gfxPrefs.h" #include "GLContext.h" #include "nsString.h" @@ -74,12 +75,15 @@ bool WebGLContext::IsExtensionSupported(JSContext* cx, // Chrome contexts need access to debug information even when // webgl.disable-extensions is set. This is used in the graphics - // section of about:support. - if (xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) + // section of about:support + if (NS_IsMainThread() && + xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) { allowPrivilegedExts = true; + } - if (Preferences::GetBool("webgl.enable-privileged-extensions", false)) + if (gfxPrefs::WebGLPrivilegedExtensionsEnabled()) { allowPrivilegedExts = true; + } if (allowPrivilegedExts) { switch (ext) { @@ -191,9 +195,7 @@ WebGLContext::IsExtensionSupported(WebGLExtensionID ext) const break; } - if (Preferences::GetBool("webgl.enable-draft-extensions", false) || - IsWebGL2()) - { + if (gfxPrefs::WebGLDraftExtensionsEnabled() || IsWebGL2()) { switch (ext) { case WebGLExtensionID::EXT_disjoint_timer_query: return WebGLExtensionDisjointTimerQuery::IsSupported(this); diff --git a/dom/canvas/WebGLContextGL.cpp b/dom/canvas/WebGLContextGL.cpp index c37b0d6cff..8d4e45089a 100644 --- a/dom/canvas/WebGLContextGL.cpp +++ b/dom/canvas/WebGLContextGL.cpp @@ -1378,7 +1378,10 @@ WebGLContext::ReadPixels(GLint x, GLint y, GLsizei width, if (IsContextLost()) return; - if (mCanvasElement->IsWriteOnly() && !nsContentUtils::IsCallerChrome()) { + if (mCanvasElement && + mCanvasElement->IsWriteOnly() && + !nsContentUtils::IsCallerChrome()) + { GenerateWarning("readPixels: Not allowed"); return rv.Throw(NS_ERROR_DOM_SECURITY_ERR); } diff --git a/dom/canvas/WebGLContextLossHandler.cpp b/dom/canvas/WebGLContextLossHandler.cpp index bf19b1ddd2..030bcd5b3b 100644 --- a/dom/canvas/WebGLContextLossHandler.cpp +++ b/dom/canvas/WebGLContextLossHandler.cpp @@ -8,15 +8,103 @@ #include "nsITimer.h" #include "nsThreadUtils.h" #include "WebGLContext.h" +#include "mozilla/dom/WorkerPrivate.h" namespace mozilla { +// ------------------------------------------------------------------- +// Begin worker specific code +// ------------------------------------------------------------------- + +// On workers we can only dispatch CancelableRunnables, so we have to wrap the +// timer's EventTarget to use our own cancelable runnable + +class ContextLossWorkerEventTarget final : public nsIEventTarget +{ +public: + explicit ContextLossWorkerEventTarget(nsIEventTarget* aEventTarget) + : mEventTarget(aEventTarget) + { + MOZ_ASSERT(aEventTarget); + } + + NS_DECL_NSIEVENTTARGET + NS_DECL_THREADSAFE_ISUPPORTS + +protected: + ~ContextLossWorkerEventTarget() {} + +private: + nsCOMPtr mEventTarget; +}; + +class ContextLossWorkerRunnable final : public nsICancelableRunnable +{ +public: + explicit ContextLossWorkerRunnable(nsIRunnable* aRunnable) + : mRunnable(aRunnable) + { + } + + NS_DECL_NSICANCELABLERUNNABLE + NS_DECL_THREADSAFE_ISUPPORTS + + NS_FORWARD_NSIRUNNABLE(mRunnable->) + +protected: + ~ContextLossWorkerRunnable() {} + +private: + nsCOMPtr mRunnable; +}; + +NS_IMPL_ISUPPORTS(ContextLossWorkerEventTarget, nsIEventTarget, + nsISupports) + +NS_IMETHODIMP +ContextLossWorkerEventTarget::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) +{ + nsCOMPtr event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +ContextLossWorkerEventTarget::Dispatch(already_AddRefed&& aEvent, + uint32_t aFlags) +{ + nsCOMPtr eventRef(aEvent); + RefPtr wrappedEvent = + new ContextLossWorkerRunnable(eventRef); + return mEventTarget->Dispatch(wrappedEvent, aFlags); +} + +NS_IMETHODIMP +ContextLossWorkerEventTarget::IsOnCurrentThread(bool* aResult) +{ + return mEventTarget->IsOnCurrentThread(aResult); +} + +NS_IMPL_ISUPPORTS(ContextLossWorkerRunnable, nsICancelableRunnable, + nsIRunnable) + +NS_IMETHODIMP +ContextLossWorkerRunnable::Cancel() +{ + mRunnable = nullptr; + return NS_OK; +} + +// ------------------------------------------------------------------- +// End worker-specific code +// ------------------------------------------------------------------- + WebGLContextLossHandler::WebGLContextLossHandler(WebGLContext* webgl) : mWeakWebGL(webgl) , mTimer(do_CreateInstance(NS_TIMER_CONTRACTID)) , mIsTimerRunning(false) , mShouldRunTimerAgain(false) , mIsDisabled(false) + , mFeatureAdded(false) #ifdef DEBUG , mThread(NS_GetCurrentThread()) #endif @@ -91,6 +179,17 @@ WebGLContextLossHandler::RunTimer() return; } + if (!NS_IsMainThread()) { + dom::workers::WorkerPrivate* workerPrivate = + dom::workers::GetCurrentThreadWorkerPrivate(); + nsCOMPtr target = workerPrivate->GetEventTarget(); + mTimer->SetTarget(new ContextLossWorkerEventTarget(target)); + if (!mFeatureAdded) { + workerPrivate->AddFeature(workerPrivate->GetJSContext(), this); + mFeatureAdded = true; + } + } + StartTimer(1000); mIsTimerRunning = true; @@ -105,6 +204,14 @@ WebGLContextLossHandler::DisableTimer() mIsDisabled = true; + if (mFeatureAdded) { + dom::workers::WorkerPrivate* workerPrivate = + dom::workers::GetCurrentThreadWorkerPrivate(); + MOZ_RELEASE_ASSERT(workerPrivate); + workerPrivate->RemoveFeature(workerPrivate->GetJSContext(), this); + mFeatureAdded = false; + } + // We can't just Cancel() the timer, as sometimes we end up // receiving a callback after calling Cancel(). This could cause us // to receive the callback after object destruction. @@ -117,4 +224,16 @@ WebGLContextLossHandler::DisableTimer() mTimer->SetDelay(0); } +bool +WebGLContextLossHandler::Notify(JSContext* aCx, dom::workers::Status aStatus) +{ + bool isWorkerRunning = aStatus < dom::workers::Closing; + if (!isWorkerRunning && mIsTimerRunning) { + mIsTimerRunning = false; + this->Release(); + } + + return true; +} + } // namespace mozilla diff --git a/dom/canvas/WebGLContextLossHandler.h b/dom/canvas/WebGLContextLossHandler.h index 0769de9105..70d8c671fa 100644 --- a/dom/canvas/WebGLContextLossHandler.h +++ b/dom/canvas/WebGLContextLossHandler.h @@ -10,6 +10,7 @@ #include "mozilla/WeakPtr.h" #include "nsCOMPtr.h" #include "nsISupportsImpl.h" +#include "WorkerFeature.h" class nsIThread; class nsITimer; @@ -17,13 +18,14 @@ class nsITimer; namespace mozilla { class WebGLContext; -class WebGLContextLossHandler +class WebGLContextLossHandler : public dom::workers::WorkerFeature { WeakPtr mWeakWebGL; nsCOMPtr mTimer; bool mIsTimerRunning; bool mShouldRunTimerAgain; bool mIsDisabled; + bool mFeatureAdded; DebugOnly mThread; public: @@ -33,6 +35,7 @@ public: void RunTimer(); void DisableTimer(); + bool Notify(JSContext* aCx, dom::workers::Status aStatus) override; protected: ~WebGLContextLossHandler(); diff --git a/dom/canvas/WebGLContextReporter.cpp b/dom/canvas/WebGLContextReporter.cpp index d257d9662b..4aab34a079 100644 --- a/dom/canvas/WebGLContextReporter.cpp +++ b/dom/canvas/WebGLContextReporter.cpp @@ -9,8 +9,6 @@ namespace mozilla { -NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver) - NS_IMETHODIMP WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport, nsISupports* data, bool) diff --git a/dom/canvas/WebGLContextValidate.cpp b/dom/canvas/WebGLContextValidate.cpp index bc18b8f70e..0e8691e298 100644 --- a/dom/canvas/WebGLContextValidate.cpp +++ b/dom/canvas/WebGLContextValidate.cpp @@ -8,6 +8,7 @@ #include #include "angle/ShaderLang.h" #include "CanvasUtils.h" +#include "gfxPrefs.h" #include "GLContext.h" #include "jsfriendapi.h" #include "mozilla/CheckedInt.h" @@ -1660,11 +1661,11 @@ WebGLContext::InitAndValidateGL() return false; } - mMinCapability = Preferences::GetBool("webgl.min_capability_mode", false); - mDisableExtensions = Preferences::GetBool("webgl.disable-extensions", false); - mLoseContextOnMemoryPressure = Preferences::GetBool("webgl.lose-context-on-memory-pressure", false); - mCanLoseContextInForeground = Preferences::GetBool("webgl.can-lose-context-in-foreground", true); - mRestoreWhenVisible = Preferences::GetBool("webgl.restore-context-when-visible", true); + mMinCapability = gfxPrefs::WebGLMinCapabilityMode(); + mDisableExtensions = gfxPrefs::WebGLDisableExtensions(); + mLoseContextOnMemoryPressure = gfxPrefs::WebGLLoseContextOnMemoryPressure(); + mCanLoseContextInForeground = gfxPrefs::WebGLCanLoseContextInForeground(); + mRestoreWhenVisible = gfxPrefs::WebGLRestoreWhenVisible(); if (MinCapabilityMode()) mDisableFragHighP = true; @@ -1873,10 +1874,7 @@ WebGLContext::InitAndValidateGL() #endif // Check the shader validator pref - NS_ENSURE_TRUE(Preferences::GetRootBranch(), false); - - mBypassShaderValidation = Preferences::GetBool("webgl.bypass-shader-validation", - mBypassShaderValidation); + mBypassShaderValidation = gfxPrefs::WebGLBypassShaderValidator(); // initialize shader translator if (!ShInitialize()) { @@ -1932,9 +1930,6 @@ WebGLContext::InitAndValidateGL() mDefaultVertexArray->BindVertexArray(); } - if (mLoseContextOnMemoryPressure) - mContextObserver->RegisterMemoryPressureEvent(); - return true; } diff --git a/dom/canvas/WebGLMemoryTracker.cpp b/dom/canvas/WebGLMemoryTracker.cpp index b13d9d7c43..5bc4b31fb5 100644 --- a/dom/canvas/WebGLMemoryTracker.cpp +++ b/dom/canvas/WebGLMemoryTracker.cpp @@ -16,8 +16,6 @@ namespace mozilla { -NS_IMPL_ISUPPORTS(WebGLObserver, nsIObserver) - NS_IMETHODIMP WebGLMemoryTracker::CollectReports(nsIHandleReportCallback* handleReport, nsISupports* data, bool) diff --git a/dom/canvas/WebGLShaderValidator.cpp b/dom/canvas/WebGLShaderValidator.cpp index 3964f8ad95..3d93b0e150 100644 --- a/dom/canvas/WebGLShaderValidator.cpp +++ b/dom/canvas/WebGLShaderValidator.cpp @@ -6,9 +6,12 @@ #include "WebGLShaderValidator.h" #include "angle/ShaderLang.h" +#include "gfxPrefs.h" #include "GLContext.h" +#include "mozilla/Preferences.h" #include "MurmurHash3.h" #include "nsPrintfCString.h" +#include "nsTArray.h" #include #include #include "WebGLContext.h" @@ -32,13 +35,27 @@ ChooseValidatorCompileOptions(const ShBuiltInResources& resources, { int options = SH_VARIABLES | SH_ENFORCE_PACKING_RESTRICTIONS | + SH_INIT_VARYINGS_WITHOUT_STATIC_USE | SH_OBJECT_CODE | - SH_LIMIT_CALL_STACK_DEPTH; + SH_LIMIT_CALL_STACK_DEPTH | + SH_INIT_GL_POSITION; if (resources.MaxExpressionComplexity > 0) { options |= SH_LIMIT_EXPRESSION_COMPLEXITY; } + if (gfxPrefs::WebGLAllANGLEOptions()) { + return options | + SH_VALIDATE_LOOP_INDEXING | + SH_UNROLL_FOR_LOOP_WITH_INTEGER_INDEX | + SH_UNROLL_FOR_LOOP_WITH_SAMPLER_ARRAY_INDEX | + SH_EMULATE_BUILT_IN_FUNCTIONS | + SH_CLAMP_INDIRECT_ARRAY_BOUNDS | + SH_UNFOLD_SHORT_CIRCUIT | + SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS | + SH_REGENERATE_STRUCT_NAMES; + } + #ifndef XP_MACOSX // We want to do this everywhere, but to do this on Mac, we need // to do it only on Mac OSX > 10.6 as this causes the shader @@ -145,7 +162,7 @@ ShaderValidator::Create(GLenum shaderType, ShShaderSpec spec, if (!handle) return nullptr; - return new ShaderValidator(handle, compileOptions); + return new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors); } ShaderValidator::~ShaderValidator() @@ -193,12 +210,23 @@ StartsWith(const std::string& haystack, const char (&needle)[N]) bool ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const { - { - const std::vector& vertList = *ShGetUniforms(prev->mHandle); - const std::vector& fragList = *ShGetUniforms(mHandle); + if (!prev) { + nsPrintfCString error("Passed in NULL prev ShaderValidator."); + *out_log = error; + return false; + } - for (auto itrFrag = fragList.begin(); itrFrag != fragList.end(); ++itrFrag) { - for (auto itrVert = vertList.begin(); itrVert != vertList.end(); ++itrVert) { + { + const std::vector* vertPtr = ShGetUniforms(prev->mHandle); + const std::vector* fragPtr = ShGetUniforms(mHandle); + if (!vertPtr || !fragPtr) { + nsPrintfCString error("Could not create uniform list."); + *out_log = error; + return false; + } + + for (auto itrFrag = fragPtr->begin(); itrFrag != fragPtr->end(); ++itrFrag) { + for (auto itrVert = vertPtr->begin(); itrVert != vertPtr->end(); ++itrVert) { if (itrVert->name != itrFrag->name) continue; @@ -215,17 +243,32 @@ ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log } } { - const std::vector& vertList = *ShGetVaryings(prev->mHandle); - const std::vector& fragList = *ShGetVaryings(mHandle); + const std::vector* vertPtr = ShGetVaryings(prev->mHandle); + const std::vector* fragPtr = ShGetVaryings(mHandle); + if (!vertPtr || !fragPtr) { + nsPrintfCString error("Could not create varying list."); + *out_log = error; + return false; + } + + nsTArray staticUseVaryingList; + + for (auto itrFrag = fragPtr->begin(); itrFrag != fragPtr->end(); ++itrFrag) { + const ShVariableInfo varInfo = { itrFrag->type, + (int)itrFrag->elementCount() }; - for (auto itrFrag = fragList.begin(); itrFrag != fragList.end(); ++itrFrag) { static const char prefix[] = "gl_"; - if (StartsWith(itrFrag->name, prefix)) + if (StartsWith(itrFrag->name, prefix)) { + if (itrFrag->staticUse) + staticUseVaryingList.AppendElement(varInfo); + continue; + } bool definedInVertShader = false; + bool staticVertUse = false; - for (auto itrVert = vertList.begin(); itrVert != vertList.end(); ++itrVert) { + for (auto itrVert = vertPtr->begin(); itrVert != vertPtr->end(); ++itrVert) { if (itrVert->name != itrFrag->name) continue; @@ -238,6 +281,7 @@ ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log } definedInVertShader = true; + staticVertUse = itrVert->staticUse; break; } @@ -248,6 +292,18 @@ ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log *out_log = error; return false; } + + if (staticVertUse && itrFrag->staticUse) + staticUseVaryingList.AppendElement(varInfo); + } + + if (!ShCheckVariablesWithinPackingLimits(mMaxVaryingVectors, + staticUseVaryingList.Elements(), + staticUseVaryingList.Length())) + { + *out_log = "Statically used varyings do not fit within packing limits. (see" + " GLSL ES Specification 1.0.17, p111)"; + return false; } } diff --git a/dom/canvas/WebGLShaderValidator.h b/dom/canvas/WebGLShaderValidator.h index 1f794bf041..71220d46cc 100644 --- a/dom/canvas/WebGLShaderValidator.h +++ b/dom/canvas/WebGLShaderValidator.h @@ -18,6 +18,7 @@ class ShaderValidator final { const ShHandle mHandle; const int mCompileOptions; + const int mMaxVaryingVectors; bool mHasRun; public: @@ -27,9 +28,10 @@ public: int compileOptions); private: - ShaderValidator(ShHandle handle, int compileOptions) + ShaderValidator(ShHandle handle, int compileOptions, int maxVaryingVectors) : mHandle(handle) , mCompileOptions(compileOptions) + , mMaxVaryingVectors(maxVaryingVectors) , mHasRun(false) { } diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 07d5b27507..5ae1351a6c 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -31,10 +31,12 @@ EXPORTS.mozilla.dom += [ 'CanvasPath.h', 'CanvasPattern.h', 'CanvasRenderingContext2D.h', + 'CanvasRenderingContextHelper.h', 'CanvasUtils.h', 'ImageBitmap.h', 'ImageBitmapSource.h', 'ImageData.h', + 'OffscreenCanvas.h', 'TextMetrics.h', 'WebGLVertexArrayObject.h', ] @@ -46,11 +48,13 @@ DEFINES['NOMINMAX'] = True UNIFIED_SOURCES += [ 'CanvasImageCache.cpp', 'CanvasRenderingContext2D.cpp', + 'CanvasRenderingContextHelper.cpp', 'CanvasUtils.cpp', 'DocumentRendererChild.cpp', 'DocumentRendererParent.cpp', 'ImageBitmap.cpp', 'ImageData.cpp', + 'OffscreenCanvas.cpp', ] # WebGL Sources @@ -157,6 +161,7 @@ LOCAL_INCLUDES += [ '/dom/base', '/dom/html', '/dom/svg', + '/dom/workers', '/dom/xul', '/gfx/gl', '/image', diff --git a/dom/canvas/nsICanvasRenderingContextInternal.h b/dom/canvas/nsICanvasRenderingContextInternal.h index ab61047624..0682207a7d 100644 --- a/dom/canvas/nsICanvasRenderingContextInternal.h +++ b/dom/canvas/nsICanvasRenderingContextInternal.h @@ -12,11 +12,12 @@ #include "nsIDocShell.h" #include "nsRefreshDriver.h" #include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/OffscreenCanvas.h" #include "mozilla/RefPtr.h" #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \ -{ 0x3cc9e801, 0x1806, 0x4ff6, \ - { 0x86, 0x14, 0xf9, 0xd0, 0xf4, 0xfb, 0x3b, 0x08 } } +{ 0xb84f2fed, 0x9d4b, 0x430b, \ + { 0xbd, 0xfb, 0x85, 0x57, 0x8a, 0xc2, 0xb4, 0x4b } } class gfxASurface; class nsDisplayListBuilder; @@ -79,6 +80,11 @@ public: return mCanvasElement; } + void SetOffscreenCanvas(mozilla::dom::OffscreenCanvas* aOffscreenCanvas) + { + mOffscreenCanvas = aOffscreenCanvas; + } + // Dimensions of the canvas, in pixels. virtual int32_t GetWidth() const = 0; virtual int32_t GetHeight() const = 0; @@ -151,6 +157,10 @@ public: // Given a point, return hit region ID if it exists or an empty string if it doesn't virtual nsString GetHitRegion(const mozilla::gfx::Point& point) { return nsString(); } + virtual void OnVisibilityChange() {} + + virtual void OnMemoryPressure() {} + // // shmem support // @@ -163,6 +173,7 @@ public: protected: RefPtr mCanvasElement; + RefPtr mOffscreenCanvas; RefPtr mRefreshDriver; }; diff --git a/dom/canvas/test/mochitest.ini b/dom/canvas/test/mochitest.ini index 62926a4972..655d7f4d30 100644 --- a/dom/canvas/test/mochitest.ini +++ b/dom/canvas/test/mochitest.ini @@ -27,6 +27,10 @@ support-files = imagebitmap_on_worker.js imagebitmap_structuredclone.js imagebitmap_structuredclone_iframe.html + offscreencanvas.js + offscreencanvas_mask.svg + offscreencanvas_neuter.js + offscreencanvas_serviceworker_inner.html [test_2d.clearRect.image.offscreen.html] [test_2d.clip.winding.html] @@ -260,3 +264,23 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965 skip-if = (os == 'win' || os == 'mac') && debug # bug 1131931 [test_createPattern_broken.html] [test_setlinedash.html] +[test_filter.html] +[test_offscreencanvas_basic_webgl.html] +tags = offscreencanvas +[test_offscreencanvas_dynamic_fallback.html] +tags = offscreencanvas +[test_offscreencanvas_sharedworker.html] +tags = offscreencanvas +[test_offscreencanvas_serviceworker.html] +tags = offscreencanvas +skip-if = buildapp == 'b2g' +[test_offscreencanvas_neuter.html] +tags = offscreencanvas +[test_offscreencanvas_many.html] +tags = offscreencanvas +skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3') +[test_offscreencanvas_sizechange.html] +tags = offscreencanvas +[test_offscreencanvas_subworker.html] +tags = offscreencanvas +skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3') diff --git a/dom/canvas/test/offscreencanvas.js b/dom/canvas/test/offscreencanvas.js new file mode 100644 index 0000000000..6dec8220f4 --- /dev/null +++ b/dom/canvas/test/offscreencanvas.js @@ -0,0 +1,299 @@ +/* WebWorker for test_offscreencanvas_*.html */ +var port = null; + +function ok(expect, msg) { + if (port) { + port.postMessage({type: "test", result: !!expect, name: msg}); + } else { + postMessage({type: "test", result: !!expect, name: msg}); + } +} + +function finish() { + if (port) { + port.postMessage({type: "finish"}); + } else { + postMessage({type: "finish"}); + } +} + +function drawCount(count) { + if (port) { + port.postMessage({type: "draw", count: count}); + } else { + postMessage({type: "draw", count: count}); + } +} + +//-------------------------------------------------------------------- +// WebGL Drawing Functions +//-------------------------------------------------------------------- +function createDrawFunc(canvas) { + var gl; + + try { + gl = canvas.getContext("experimental-webgl"); + } catch (e) {} + + if (!gl) { + ok(false, "WebGL is unavailable"); + return null; + } + + var vertSrc = "attribute vec2 position; \ + void main(void) { \ + gl_Position = vec4(position, 0.0, 1.0); \ + }"; + + var fragSrc = "precision mediump float; \ + void main(void) { \ + gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); \ + }"; + + // Returns a valid shader, or null on errors. + var createShader = function(src, t) { + var shader = gl.createShader(t); + + gl.shaderSource(shader, src); + gl.compileShader(shader); + + return shader; + }; + + var createProgram = function(vsSrc, fsSrc) { + var vs = createShader(vsSrc, gl.VERTEX_SHADER); + var fs = createShader(fsSrc, gl.FRAGMENT_SHADER); + + var prog = gl.createProgram(); + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + + if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { + var str = "Shader program linking failed:"; + str += "\nShader program info log:\n" + gl.getProgramInfoLog(prog); + str += "\n\nVert shader log:\n" + gl.getShaderInfoLog(vs); + str += "\n\nFrag shader log:\n" + gl.getShaderInfoLog(fs); + console.log(str); + ok(false, "Shader program linking failed"); + return null; + } + + return prog; + }; + + gl.disable(gl.DEPTH_TEST); + + var program = createProgram(vertSrc, fragSrc); + ok(program, "Creating shader program"); + + program.positionAttr = gl.getAttribLocation(program, "position"); + ok(program.positionAttr >= 0, "position attribute should be valid"); + + var vertCoordArr = new Float32Array([ + -1, -1, + 1, -1, + -1, 1, + 1, 1, + ]); + var vertCoordBuff = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, vertCoordBuff); + gl.bufferData(gl.ARRAY_BUFFER, vertCoordArr, gl.STATIC_DRAW); + + var checkGLError = function(prefix, refValue) { + if (!refValue) { + refValue = 0; + } + + var error = gl.getError(); + ok(error == refValue, + prefix + 'gl.getError should be 0x' + refValue.toString(16) + + ', was 0x' + error.toString(16) + '.'); + }; + + var testPixel = function(x, y, refData, infoString) { + var pixel = new Uint8Array(4); + gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel); + + var pixelMatches = pixel[0] == refData[0] && + pixel[1] == refData[1] && + pixel[2] == refData[2] && + pixel[3] == refData[3]; + ok(pixelMatches, infoString); + }; + + var preDraw = function(prefix) { + gl.clearColor(1.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + testPixel(0, 0, [255, 0, 0, 255], prefix + 'Should be red before drawing.'); + }; + + var postDraw = function(prefix) { + testPixel(0, 0, [0, 255, 0, 255], prefix + 'Should be green after drawing.'); + }; + + gl.useProgram(program); + gl.enableVertexAttribArray(program.position); + gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0); + + // Start drawing + checkGLError('after setup'); + + return function(prefix) { + if (prefix) { + prefix = "[" + prefix + "] "; + } else { + prefix = ""; + } + + gl.viewport(0, 0, canvas.width, canvas.height); + + preDraw(prefix); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + postDraw(prefix); + gl.commit(); + checkGLError(prefix); + }; +} + +/* entry point */ +function entryFunction(testStr, subtests, offscreenCanvas) { + var test = testStr; + var canvas = offscreenCanvas; + + if (test != "subworker") { + ok(canvas, "Canvas successfully transfered to worker"); + ok(canvas.getContext, "Canvas has getContext"); + + ok(canvas.width == 64, "OffscreenCanvas width should be 64"); + ok(canvas.height == 64, "OffscreenCanvas height should be 64"); + } + + var draw; + + //------------------------------------------------------------------------ + // Basic WebGL test + //------------------------------------------------------------------------ + if (test == "webgl") { + draw = createDrawFunc(canvas); + if (!draw) { + finish(); + return; + } + + var count = 0; + var iid = setInterval(function() { + if (count++ > 20) { + clearInterval(iid); + ok(true, "Worker is done"); + finish(); + return; + } + draw("loop " +count); + }, 0); + } + //------------------------------------------------------------------------ + // Test dynamic fallback + //------------------------------------------------------------------------ + else if (test == "webgl_fallback") { + draw = createDrawFunc(canvas); + if (!draw) { + return; + } + + var count = 0; + var iid = setInterval(function() { + ++count; + draw("loop " + count); + drawCount(count); + }, 0); + } + //------------------------------------------------------------------------ + // Canvas Size Change from Worker + //------------------------------------------------------------------------ + else if (test == "webgl_changesize") { + draw = createDrawFunc(canvas); + if (!draw) { + finish(); + return; + } + + draw("64x64"); + + setTimeout(function() { + canvas.width = 128; + canvas.height = 128; + draw("Increased to 128x128"); + + setTimeout(function() { + canvas.width = 32; + canvas.width = 32; + draw("Decreased to 32x32"); + + setTimeout(function() { + canvas.width = 64; + canvas.height = 64; + draw("Increased to 64x64"); + + ok(true, "Worker is done"); + finish(); + }, 0); + }, 0); + }, 0); + } + //------------------------------------------------------------------------ + // Using OffscreenCanvas from sub workers + //------------------------------------------------------------------------ + else if (test == "subworker") { + /* subworker tests take a list of tests to run on children */ + var stillRunning = 0; + subtests.forEach(function (subtest) { + ++stillRunning; + var subworker = new Worker('offscreencanvas.js'); + subworker.onmessage = function(evt) { + /* report finish to parent when all children are finished */ + if (evt.data.type == "finish") { + subworker.terminate(); + if (--stillRunning == 0) { + ok(true, "Worker is done"); + finish(); + } + return; + } + /* relay all other messages to parent */ + postMessage(evt.data); + }; + + var findTransferables = function(t) { + if (t.test == "subworker") { + var result = []; + t.subtests.forEach(function(test) { + result = result.concat(findTransferables(test)); + }); + + return result; + } else { + return [t.canvas]; + } + }; + + subworker.postMessage(subtest, findTransferables(subtest)); + }); + } +}; + +onmessage = function(evt) { + port = evt.ports[0]; + entryFunction(evt.data.test, evt.data.subtests, evt.data.canvas); +}; + +onconnect = function(evt) { + port = evt.ports[0]; + + port.addEventListener('message', function(evt) { + entryFunction(evt.data.test, evt.data.subtests, evt.data.canvas); + }); + + port.start(); +}; diff --git a/dom/canvas/test/offscreencanvas_mask.svg b/dom/canvas/test/offscreencanvas_mask.svg new file mode 100644 index 0000000000..34347b68b3 --- /dev/null +++ b/dom/canvas/test/offscreencanvas_mask.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/dom/canvas/test/offscreencanvas_neuter.js b/dom/canvas/test/offscreencanvas_neuter.js new file mode 100644 index 0000000000..30648d740a --- /dev/null +++ b/dom/canvas/test/offscreencanvas_neuter.js @@ -0,0 +1 @@ +/* empty worker for test_offscreencanvas_disable.html */ diff --git a/dom/canvas/test/offscreencanvas_serviceworker_inner.html b/dom/canvas/test/offscreencanvas_serviceworker_inner.html new file mode 100644 index 0000000000..b153f9524a --- /dev/null +++ b/dom/canvas/test/offscreencanvas_serviceworker_inner.html @@ -0,0 +1,32 @@ + + + +WebGL in OffscreenCanvas + + + + + + diff --git a/dom/canvas/test/reftest/filters/default-color.html b/dom/canvas/test/reftest/filters/default-color.html new file mode 100644 index 0000000000..82fb5eda38 --- /dev/null +++ b/dom/canvas/test/reftest/filters/default-color.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/drop-shadow-transformed.html b/dom/canvas/test/reftest/filters/drop-shadow-transformed.html new file mode 100644 index 0000000000..0cf33deea8 --- /dev/null +++ b/dom/canvas/test/reftest/filters/drop-shadow-transformed.html @@ -0,0 +1,17 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/drop-shadow.html b/dom/canvas/test/reftest/filters/drop-shadow.html new file mode 100644 index 0000000000..6977b7d5e0 --- /dev/null +++ b/dom/canvas/test/reftest/filters/drop-shadow.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/global-alpha-ref.html b/dom/canvas/test/reftest/filters/global-alpha-ref.html new file mode 100644 index 0000000000..2577581401 --- /dev/null +++ b/dom/canvas/test/reftest/filters/global-alpha-ref.html @@ -0,0 +1,18 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/global-alpha.html b/dom/canvas/test/reftest/filters/global-alpha.html new file mode 100644 index 0000000000..4676e00605 --- /dev/null +++ b/dom/canvas/test/reftest/filters/global-alpha.html @@ -0,0 +1,17 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/global-composite-operation-ref.html b/dom/canvas/test/reftest/filters/global-composite-operation-ref.html new file mode 100644 index 0000000000..cad9089354 --- /dev/null +++ b/dom/canvas/test/reftest/filters/global-composite-operation-ref.html @@ -0,0 +1,26 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/global-composite-operation.html b/dom/canvas/test/reftest/filters/global-composite-operation.html new file mode 100644 index 0000000000..61a6f206a3 --- /dev/null +++ b/dom/canvas/test/reftest/filters/global-composite-operation.html @@ -0,0 +1,21 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/liveness.html b/dom/canvas/test/reftest/filters/liveness.html new file mode 100644 index 0000000000..3895051796 --- /dev/null +++ b/dom/canvas/test/reftest/filters/liveness.html @@ -0,0 +1,18 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/multiple-drop-shadows.html b/dom/canvas/test/reftest/filters/multiple-drop-shadows.html new file mode 100644 index 0000000000..f8d9261c65 --- /dev/null +++ b/dom/canvas/test/reftest/filters/multiple-drop-shadows.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/ref.html b/dom/canvas/test/reftest/filters/ref.html new file mode 100644 index 0000000000..bb634fe66d --- /dev/null +++ b/dom/canvas/test/reftest/filters/ref.html @@ -0,0 +1,17 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/reftest.list b/dom/canvas/test/reftest/filters/reftest.list new file mode 100644 index 0000000000..9dd1d8c476 --- /dev/null +++ b/dom/canvas/test/reftest/filters/reftest.list @@ -0,0 +1,20 @@ +default-preferences pref(canvas.filters.enabled,true) + +== default-color.html ref.html +== drop-shadow.html ref.html +== drop-shadow-transformed.html ref.html +== global-alpha.html global-alpha-ref.html +== global-composite-operation.html global-composite-operation-ref.html +== liveness.html ref.html +== multiple-drop-shadows.html shadow-ref.html +== shadow.html shadow-ref.html +== subregion-fill-paint.html subregion-ref.html +== subregion-stroke-paint.html subregion-ref.html +== svg-bbox.html svg-bbox-ref.html +== svg-inline.html ref.html +== svg-liveness.html ref.html +== svg-off-screen.html ref.html +== units.html ref.html +== units-em.html ref.html +== units-ex.html ref.html +== units-off-screen.html ref.html diff --git a/dom/canvas/test/reftest/filters/shadow-ref.html b/dom/canvas/test/reftest/filters/shadow-ref.html new file mode 100644 index 0000000000..736c5f94dd --- /dev/null +++ b/dom/canvas/test/reftest/filters/shadow-ref.html @@ -0,0 +1,19 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/shadow.html b/dom/canvas/test/reftest/filters/shadow.html new file mode 100644 index 0000000000..61de33bdc2 --- /dev/null +++ b/dom/canvas/test/reftest/filters/shadow.html @@ -0,0 +1,18 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/subregion-fill-paint.html b/dom/canvas/test/reftest/filters/subregion-fill-paint.html new file mode 100644 index 0000000000..854190359f --- /dev/null +++ b/dom/canvas/test/reftest/filters/subregion-fill-paint.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/dom/canvas/test/reftest/filters/subregion-ref.html b/dom/canvas/test/reftest/filters/subregion-ref.html new file mode 100644 index 0000000000..97b231b946 --- /dev/null +++ b/dom/canvas/test/reftest/filters/subregion-ref.html @@ -0,0 +1,15 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/subregion-stroke-paint.html b/dom/canvas/test/reftest/filters/subregion-stroke-paint.html new file mode 100644 index 0000000000..24ed92a9be --- /dev/null +++ b/dom/canvas/test/reftest/filters/subregion-stroke-paint.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/dom/canvas/test/reftest/filters/svg-bbox-ref.html b/dom/canvas/test/reftest/filters/svg-bbox-ref.html new file mode 100644 index 0000000000..323cea948a --- /dev/null +++ b/dom/canvas/test/reftest/filters/svg-bbox-ref.html @@ -0,0 +1,15 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/svg-bbox.html b/dom/canvas/test/reftest/filters/svg-bbox.html new file mode 100644 index 0000000000..f25e26355d --- /dev/null +++ b/dom/canvas/test/reftest/filters/svg-bbox.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/dom/canvas/test/reftest/filters/svg-inline.html b/dom/canvas/test/reftest/filters/svg-inline.html new file mode 100644 index 0000000000..f9be99800a --- /dev/null +++ b/dom/canvas/test/reftest/filters/svg-inline.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/dom/canvas/test/reftest/filters/svg-liveness.html b/dom/canvas/test/reftest/filters/svg-liveness.html new file mode 100644 index 0000000000..732fe7562f --- /dev/null +++ b/dom/canvas/test/reftest/filters/svg-liveness.html @@ -0,0 +1,64 @@ + + + + + + + + diff --git a/dom/canvas/test/reftest/filters/svg-off-screen.html b/dom/canvas/test/reftest/filters/svg-off-screen.html new file mode 100644 index 0000000000..1aa22cd990 --- /dev/null +++ b/dom/canvas/test/reftest/filters/svg-off-screen.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/dom/canvas/test/reftest/filters/units-em.html b/dom/canvas/test/reftest/filters/units-em.html new file mode 100644 index 0000000000..1f280c1b5b --- /dev/null +++ b/dom/canvas/test/reftest/filters/units-em.html @@ -0,0 +1,17 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/units-off-screen.html b/dom/canvas/test/reftest/filters/units-off-screen.html new file mode 100644 index 0000000000..879e575c10 --- /dev/null +++ b/dom/canvas/test/reftest/filters/units-off-screen.html @@ -0,0 +1,21 @@ + + + + + + diff --git a/dom/canvas/test/reftest/filters/units-pt.html b/dom/canvas/test/reftest/filters/units-pt.html new file mode 100644 index 0000000000..74fecb3d81 --- /dev/null +++ b/dom/canvas/test/reftest/filters/units-pt.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/dom/canvas/test/reftest/filters/units.html b/dom/canvas/test/reftest/filters/units.html new file mode 100644 index 0000000000..d12abdeb13 --- /dev/null +++ b/dom/canvas/test/reftest/filters/units.html @@ -0,0 +1,16 @@ + + + + + + + diff --git a/dom/canvas/test/test_filter.html b/dom/canvas/test/test_filter.html new file mode 100644 index 0000000000..f4cac00a4b --- /dev/null +++ b/dom/canvas/test/test_filter.html @@ -0,0 +1,45 @@ + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_basic_webgl.html b/dom/canvas/test/test_offscreencanvas_basic_webgl.html new file mode 100644 index 0000000000..55b1868120 --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_basic_webgl.html @@ -0,0 +1,62 @@ + + + +WebGL in OffscreenCanvas + + + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_dynamic_fallback.html b/dom/canvas/test/test_offscreencanvas_dynamic_fallback.html new file mode 100644 index 0000000000..8e4f0f8dea --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_dynamic_fallback.html @@ -0,0 +1,80 @@ + + + +WebGL in OffscreenCanvas + + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_many.html b/dom/canvas/test/test_offscreencanvas_many.html new file mode 100644 index 0000000000..6bf2680c50 --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_many.html @@ -0,0 +1,67 @@ + + + +WebGL in OffscreenCanvas + + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_neuter.html b/dom/canvas/test/test_offscreencanvas_neuter.html new file mode 100644 index 0000000000..2af080c7c1 --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_neuter.html @@ -0,0 +1,78 @@ + + + +OffscreenCanvas: Test neutering + + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_serviceworker.html b/dom/canvas/test/test_offscreencanvas_serviceworker.html new file mode 100644 index 0000000000..c5cfb93db1 --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_serviceworker.html @@ -0,0 +1,46 @@ + + + +WebGL in OffscreenCanvas + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_sharedworker.html b/dom/canvas/test/test_offscreencanvas_sharedworker.html new file mode 100644 index 0000000000..28d7ce37c8 --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_sharedworker.html @@ -0,0 +1,47 @@ + + + +WebGL in OffscreenCanvas + + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_sizechange.html b/dom/canvas/test/test_offscreencanvas_sizechange.html new file mode 100644 index 0000000000..cecbac3483 --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_sizechange.html @@ -0,0 +1,41 @@ + + + +WebGL in OffscreenCanvas + + + + + + + + diff --git a/dom/canvas/test/test_offscreencanvas_subworker.html b/dom/canvas/test/test_offscreencanvas_subworker.html new file mode 100644 index 0000000000..b3fbae821c --- /dev/null +++ b/dom/canvas/test/test_offscreencanvas_subworker.html @@ -0,0 +1,90 @@ + + + +OffscreenCanvas: Test subworkers + + + + + + + + diff --git a/dom/canvas/test/webgl-mochitest/webgl-util.js b/dom/canvas/test/webgl-mochitest/webgl-util.js index e71d84cfe6..8206c6e2b7 100644 --- a/dom/canvas/test/webgl-mochitest/webgl-util.js +++ b/dom/canvas/test/webgl-mochitest/webgl-util.js @@ -73,12 +73,6 @@ WebGLUtil = (function() { gl = canvas.getContext('webgl2'); } catch(e) {} - if (!gl) { - try { - gl = canvas.getContext('experimental-webgl2'); - } catch(e) {} - } - if (!gl) { todo(false, 'WebGL2 is not supported'); onFinished(); diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp index 39b6ec28e6..11345c7876 100644 --- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -10,7 +10,6 @@ #include "DataTransfer.h" #include "nsIDOMDocument.h" -#include "nsIVariant.h" #include "nsISupportsPrimitives.h" #include "nsIScriptSecurityManager.h" #include "mozilla/dom/DOMStringList.h" @@ -24,6 +23,7 @@ #include "nsIScriptContext.h" #include "nsIDocument.h" #include "nsIScriptGlobalObject.h" +#include "nsVariant.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/DataTransferBinding.h" #include "mozilla/dom/Directory.h" @@ -418,12 +418,7 @@ void DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData, ErrorResult& aRv) { - nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); - if (!variant) { - aRv.Throw(NS_ERROR_OUT_OF_MEMORY); - return; - } - + RefPtr variant = new nsVariant(); variant->SetAsAString(aData); aRv = MozSetDataAt(aFormat, variant, 0); @@ -1369,9 +1364,7 @@ DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex) if (!data) return; - nsCOMPtr variant = do_CreateInstance(NS_VARIANT_CONTRACTID); - if (!variant) - return; + RefPtr variant = new nsVariant(); nsCOMPtr supportsstr = do_QueryInterface(data); if (supportsstr) { diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index fa8ed7883b..ae42225ea9 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -19,8 +19,10 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/HTMLCanvasElementBinding.h" #include "mozilla/dom/MouseEvent.h" +#include "mozilla/dom/OffscreenCanvas.h" #include "mozilla/EventDispatcher.h" #include "mozilla/gfx/Rect.h" +#include "mozilla/layers/AsyncCanvasRenderer.h" #include "mozilla/MouseEvents.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" @@ -239,18 +241,135 @@ HTMLCanvasPrintState::NotifyDone() // --------------------------------------------------------------------------- +HTMLCanvasElementObserver::HTMLCanvasElementObserver(HTMLCanvasElement* aElement) + : mElement(aElement) +{ + RegisterVisibilityChangeEvent(); + RegisterMemoryPressureEvent(); +} + +HTMLCanvasElementObserver::~HTMLCanvasElementObserver() +{ + Destroy(); +} + +void +HTMLCanvasElementObserver::Destroy() +{ + UnregisterMemoryPressureEvent(); + UnregisterVisibilityChangeEvent(); + mElement = nullptr; +} + +void +HTMLCanvasElementObserver::RegisterVisibilityChangeEvent() +{ + if (!mElement) { + return; + } + + nsIDocument* document = mElement->OwnerDoc(); + document->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + this, true, false); +} + +void +HTMLCanvasElementObserver::UnregisterVisibilityChangeEvent() +{ + if (!mElement) { + return; + } + + nsIDocument* document = mElement->OwnerDoc(); + document->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), + this, true); +} + +void +HTMLCanvasElementObserver::RegisterMemoryPressureEvent() +{ + if (!mElement) { + return; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + + MOZ_ASSERT(observerService); + + if (observerService) + observerService->AddObserver(this, "memory-pressure", false); +} + +void +HTMLCanvasElementObserver::UnregisterMemoryPressureEvent() +{ + if (!mElement) { + return; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + + // Do not assert on observerService here. This might be triggered by + // the cycle collector at a late enough time, that XPCOM services are + // no longer available. See bug 1029504. + if (observerService) + observerService->RemoveObserver(this, "memory-pressure"); +} + +NS_IMETHODIMP +HTMLCanvasElementObserver::Observe(nsISupports*, const char* aTopic, const char16_t*) +{ + if (!mElement || strcmp(aTopic, "memory-pressure")) { + return NS_OK; + } + + mElement->OnMemoryPressure(); + + return NS_OK; +} + +NS_IMETHODIMP +HTMLCanvasElementObserver::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString type; + aEvent->GetType(type); + if (!mElement || !type.EqualsLiteral("visibilitychange")) { + return NS_OK; + } + + mElement->OnVisibilityChange(); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(HTMLCanvasElementObserver, nsIObserver) + +// --------------------------------------------------------------------------- + HTMLCanvasElement::HTMLCanvasElement(already_AddRefed& aNodeInfo) : nsGenericHTMLElement(aNodeInfo), + mResetLayer(true) , mWriteOnly(false) { } HTMLCanvasElement::~HTMLCanvasElement() { + if (mContextObserver) { + mContextObserver->Destroy(); + mContextObserver = nullptr; + } + ResetPrintCallback(); if (mRequestedFrameRefreshObserver) { mRequestedFrameRefreshObserver->DetachFromRefreshDriver(); } + + if (mAsyncCanvasRenderer) { + mAsyncCanvasRenderer->mHTMLCanvasElement = nullptr; + } } NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLCanvasElement, nsGenericHTMLElement, @@ -272,6 +391,22 @@ HTMLCanvasElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) return HTMLCanvasElementBinding::Wrap(aCx, this, aGivenProto); } +already_AddRefed +HTMLCanvasElement::CreateContext(CanvasContextType aContextType) +{ + RefPtr ret = + CanvasRenderingContextHelper::CreateContext(aContextType); + + // Add Observer for webgl canvas. + if (aContextType == CanvasContextType::WebGL1 || + aContextType == CanvasContextType::WebGL2) { + mContextObserver = new HTMLCanvasElementObserver(this); + } + + ret->SetCanvasElement(this); + return ret.forget(); +} + nsIntSize HTMLCanvasElement::GetWidthHeight() { @@ -490,35 +625,6 @@ HTMLCanvasElement::ToDataURL(const nsAString& aType, JS::Handle aPara return ToDataURLImpl(aCx, aType, aParams, aDataURL); } -// HTMLCanvasElement::mozFetchAsStream - -NS_IMETHODIMP -HTMLCanvasElement::MozFetchAsStream(nsIInputStreamCallback *aCallback, - const nsAString& aType) -{ - if (!nsContentUtils::IsCallerChrome()) - return NS_ERROR_FAILURE; - - nsresult rv; - nsCOMPtr inputData; - - nsAutoString type(aType); - rv = ExtractData(type, EmptyString(), getter_AddRefs(inputData)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr asyncData = do_QueryInterface(inputData, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr mainThread; - rv = NS_GetMainThread(getter_AddRefs(mainThread)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr asyncCallback = - NS_NewInputStreamReadyEvent(aCallback, mainThread); - - return asyncCallback->OnInputStreamReady(asyncData); -} - void HTMLCanvasElement::SetMozPrintCallback(PrintCallback* aCallback) { @@ -585,51 +691,10 @@ HTMLCanvasElement::ExtractData(nsAString& aType, aOptions, GetSize(), mCurrentContext, + mAsyncCanvasRenderer, aStream); } -nsresult -HTMLCanvasElement::ParseParams(JSContext* aCx, - const nsAString& aType, - const JS::Value& aEncoderOptions, - nsAString& aParams, - bool* usingCustomParseOptions) -{ - // Quality parameter is only valid for the image/jpeg MIME type - if (aType.EqualsLiteral("image/jpeg")) { - if (aEncoderOptions.isNumber()) { - double quality = aEncoderOptions.toNumber(); - // Quality must be between 0.0 and 1.0, inclusive - if (quality >= 0.0 && quality <= 1.0) { - aParams.AppendLiteral("quality="); - aParams.AppendInt(NS_lround(quality * 100.0)); - } - } - } - - // If we haven't parsed the aParams check for proprietary options. - // The proprietary option -moz-parse-options will take a image lib encoder - // parse options string as is and pass it to the encoder. - *usingCustomParseOptions = false; - if (aParams.Length() == 0 && aEncoderOptions.isString()) { - NS_NAMED_LITERAL_STRING(mozParseOptions, "-moz-parse-options:"); - nsAutoJSString paramString; - if (!paramString.init(aCx, aEncoderOptions.toString())) { - return NS_ERROR_FAILURE; - } - if (StringBeginsWith(paramString, mozParseOptions)) { - nsDependentSubstring parseOptions = Substring(paramString, - mozParseOptions.Length(), - paramString.Length() - - mozParseOptions.Length()); - aParams.Append(parseOptions); - *usingCustomParseOptions = true; - } - } - - return NS_OK; -} - nsresult HTMLCanvasElement::ToDataURLImpl(JSContext* aCx, const nsAString& aMimeType, @@ -688,84 +753,38 @@ HTMLCanvasElement::ToBlob(JSContext* aCx, return; } - nsAutoString type; - nsContentUtils::ASCIIToLower(aType, type); - - nsAutoString params; - bool usingCustomParseOptions; - aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions); - if (aRv.Failed()) { - return; - } - - if (mCurrentContext) { - // We disallow canvases of width or height zero, and set them to 1, so - // we will have a discrepancy with the sizes of the canvas and the context. - // That discrepancy is OK, the rest are not. - nsIntSize elementSize = GetWidthHeight(); - if ((elementSize.width != mCurrentContext->GetWidth() && - (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) || - (elementSize.height != mCurrentContext->GetHeight() && - (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - } - - uint8_t* imageBuffer = nullptr; - int32_t format = 0; - if (mCurrentContext) { - mCurrentContext->GetImageBuffer(&imageBuffer, &format); - } - - // Encoder callback when encoding is complete. - class EncodeCallback : public EncodeCompleteCallback - { - public: - EncodeCallback(nsIGlobalObject* aGlobal, FileCallback* aCallback) - : mGlobal(aGlobal) - , mFileCallback(aCallback) {} - - // This is called on main thread. - nsresult ReceiveBlob(already_AddRefed aBlob) - { - RefPtr blob = aBlob; - - ErrorResult rv; - uint64_t size = blob->GetSize(rv); - if (rv.Failed()) { - rv.SuppressException(); - } else { - AutoJSAPI jsapi; - if (jsapi.Init(mGlobal)) { - JS_updateMallocCounter(jsapi.cx(), size); - } - } - - RefPtr newBlob = Blob::Create(mGlobal, blob->Impl()); - - mFileCallback->Call(*newBlob, rv); - - mGlobal = nullptr; - mFileCallback = nullptr; - - return rv.StealNSResult(); - } - - nsCOMPtr mGlobal; - RefPtr mFileCallback; - }; - nsCOMPtr global = OwnerDoc()->GetScopeObject(); MOZ_ASSERT(global); - RefPtr callback = new EncodeCallback(global, &aCallback); - aRv = ImageEncoder::ExtractDataAsync(type, - params, - usingCustomParseOptions, - imageBuffer, - format, - GetSize(), - callback); + + CanvasRenderingContextHelper::ToBlob(aCx, global, aCallback, aType, + aParams, aRv); + +} + +OffscreenCanvas* +HTMLCanvasElement::TransferControlToOffscreen(ErrorResult& aRv) +{ + if (mCurrentContext) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + if (!mOffscreenCanvas) { + nsIntSize sz = GetWidthHeight(); + RefPtr renderer = GetAsyncCanvasRenderer(); + renderer->SetWidth(sz.width); + renderer->SetHeight(sz.height); + + mOffscreenCanvas = new OffscreenCanvas(sz.width, + sz.height, + GetCompositorBackendType(), + renderer); + mContextObserver = new HTMLCanvasElementObserver(this); + } else { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + } + + return mOffscreenCanvas; } already_AddRefed @@ -836,76 +855,6 @@ HTMLCanvasElement::MozGetAsBlobImpl(const nsAString& aName, return NS_OK; } -static bool -GetCanvasContextType(const nsAString& str, CanvasContextType* const out_type) -{ - if (str.EqualsLiteral("2d")) { - *out_type = CanvasContextType::Canvas2D; - return true; - } - - if (str.EqualsLiteral("experimental-webgl")) { - *out_type = CanvasContextType::WebGL1; - return true; - } - -#ifdef MOZ_WEBGL_CONFORMANT - if (str.EqualsLiteral("webgl")) { - /* WebGL 1.0, $2.1 "Context Creation": - * If the user agent supports both the webgl and experimental-webgl - * canvas context types, they shall be treated as aliases. - */ - *out_type = CanvasContextType::WebGL1; - return true; - } -#endif - - if (WebGL2Context::IsSupported()) { - if (str.EqualsLiteral("experimental-webgl2")) { - *out_type = CanvasContextType::WebGL2; - return true; - } - } - - return false; -} - -static already_AddRefed -CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas) -{ - MOZ_ASSERT(contextType != CanvasContextType::NoContext); - RefPtr ret; - - switch (contextType) { - case CanvasContextType::NoContext: - break; - case CanvasContextType::Canvas2D: - Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); - ret = new CanvasRenderingContext2D(); - break; - - case CanvasContextType::WebGL1: - Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1); - - ret = WebGL1Context::Create(); - if (!ret) - return nullptr; - break; - - case CanvasContextType::WebGL2: - Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1); - - ret = WebGL2Context::Create(); - if (!ret) - return nullptr; - break; - } - MOZ_ASSERT(ret); - - ret->SetCanvasElement(canvas); - return ret.forget(); -} - nsresult HTMLCanvasElement::GetContext(const nsAString& aContextId, nsISupports** aContext) @@ -919,45 +868,14 @@ already_AddRefed HTMLCanvasElement::GetContext(JSContext* aCx, const nsAString& aContextId, JS::Handle aContextOptions, - ErrorResult& rv) + ErrorResult& aRv) { - CanvasContextType contextType; - if (!GetCanvasContextType(aContextId, &contextType)) + if (mOffscreenCanvas) { return nullptr; - - if (!mCurrentContext) { - // This canvas doesn't have a context yet. - - RefPtr context; - context = CreateContextForCanvas(contextType, this); - if (!context) - return nullptr; - - // Ensure that the context participates in CC. Note that returning a - // CC participant from QI doesn't addref. - nsXPCOMCycleCollectionParticipant* cp = nullptr; - CallQueryInterface(context, &cp); - if (!cp) { - rv.Throw(NS_ERROR_FAILURE); - return nullptr; - } - - mCurrentContext = context.forget(); - mCurrentContextType = contextType; - - rv = UpdateContext(aCx, aContextOptions); - if (rv.Failed()) { - rv = NS_OK; // See bug 645792 - return nullptr; - } - } else { - // We already have a context of some type. - if (contextType != mCurrentContextType) - return nullptr; } - nsCOMPtr context = mCurrentContext; - return context.forget(); + return CanvasRenderingContextHelper::GetContext(aCx, aContextId, + aContextOptions, aRv); } NS_IMETHODIMP @@ -979,7 +897,7 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId, // This canvas doesn't have a context yet. RefPtr context; - context = CreateContextForCanvas(contextType, this); + context = CreateContext(contextType); if (!context) { *aContext = nullptr; return NS_OK; @@ -1001,36 +919,6 @@ HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId, return NS_OK; } -nsresult -HTMLCanvasElement::UpdateContext(JSContext* aCx, JS::Handle aNewContextOptions) -{ - if (!mCurrentContext) - return NS_OK; - - nsIntSize sz = GetWidthHeight(); - - nsCOMPtr currentContext = mCurrentContext; - - nsresult rv = currentContext->SetIsOpaque(HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque)); - if (NS_FAILED(rv)) { - mCurrentContext = nullptr; - return rv; - } - - rv = currentContext->SetContextOptions(aCx, aNewContextOptions); - if (NS_FAILED(rv)) { - mCurrentContext = nullptr; - return rv; - } - - rv = currentContext->SetDimensions(sz.width, sz.height); - if (NS_FAILED(rv)) { - mCurrentContext = nullptr; - return rv; - } - - return rv; -} nsIntSize HTMLCanvasElement::GetSize() @@ -1134,6 +1022,12 @@ HTMLCanvasElement::GetIsOpaque() return mCurrentContext->GetIsOpaque(); } + return GetOpaqueAttr(); +} + +bool +HTMLCanvasElement::GetOpaqueAttr() +{ return HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque); } @@ -1142,16 +1036,57 @@ HTMLCanvasElement::GetCanvasLayer(nsDisplayListBuilder* aBuilder, CanvasLayer *aOldLayer, LayerManager *aManager) { - if (!mCurrentContext) - return nullptr; + // The address of sOffscreenCanvasLayerUserDataDummy is used as the user + // data key for retained LayerManagers managed by FrameLayerBuilder. + // We don't much care about what value in it, so just assign a dummy + // value for it. + static uint8_t sOffscreenCanvasLayerUserDataDummy = 0; - return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager); + if (mCurrentContext) { + return mCurrentContext->GetCanvasLayer(aBuilder, aOldLayer, aManager); + } + + if (mOffscreenCanvas) { + if (!mResetLayer && + aOldLayer && aOldLayer->HasUserData(&sOffscreenCanvasLayerUserDataDummy)) { + RefPtr ret = aOldLayer; + return ret.forget(); + } + + RefPtr layer = aManager->CreateCanvasLayer(); + if (!layer) { + NS_WARNING("CreateCanvasLayer failed!"); + return nullptr; + } + + LayerUserData* userData = nullptr; + layer->SetUserData(&sOffscreenCanvasLayerUserDataDummy, userData); + + CanvasLayer::Data data; + data.mRenderer = GetAsyncCanvasRenderer(); + data.mSize = GetWidthHeight(); + layer->Initialize(data); + + layer->Updated(); + return layer.forget(); + } + + return nullptr; } bool -HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager *aManager) +HTMLCanvasElement::ShouldForceInactiveLayer(LayerManager* aManager) { - return !mCurrentContext || mCurrentContext->ShouldForceInactiveLayer(aManager); + if (mCurrentContext) { + return mCurrentContext->ShouldForceInactiveLayer(aManager); + } + + if (mOffscreenCanvas) { + // TODO: We should handle offscreen canvas case. + return false; + } + + return true; } void @@ -1266,5 +1201,155 @@ HTMLCanvasElement::GetSurfaceSnapshot(bool* aPremultAlpha) return mCurrentContext->GetSurfaceSnapshot(aPremultAlpha); } +AsyncCanvasRenderer* +HTMLCanvasElement::GetAsyncCanvasRenderer() +{ + if (!mAsyncCanvasRenderer) { + mAsyncCanvasRenderer = new AsyncCanvasRenderer(); + mAsyncCanvasRenderer->mHTMLCanvasElement = this; + } + + return mAsyncCanvasRenderer; +} + +layers::LayersBackend +HTMLCanvasElement::GetCompositorBackendType() const +{ + nsIWidget* docWidget = nsContentUtils::WidgetForDocument(OwnerDoc()); + if (docWidget) { + layers::LayerManager* layerManager = docWidget->GetLayerManager(); + return layerManager->GetCompositorBackendType(); + } + + return LayersBackend::LAYERS_NONE; +} + +void +HTMLCanvasElement::OnVisibilityChange() +{ + if (OwnerDoc()->Hidden()) { + return; + } + + if (mOffscreenCanvas) { + class Runnable final : public nsCancelableRunnable + { + public: + explicit Runnable(AsyncCanvasRenderer* aRenderer) + : mRenderer(aRenderer) + {} + + NS_IMETHOD Run() + { + if (mRenderer && mRenderer->mContext) { + mRenderer->mContext->OnVisibilityChange(); + } + + return NS_OK; + } + + void Revoke() + { + mRenderer = nullptr; + } + + private: + RefPtr mRenderer; + }; + + RefPtr runnable = new Runnable(mAsyncCanvasRenderer); + nsCOMPtr activeThread = mAsyncCanvasRenderer->GetActiveThread(); + if (activeThread) { + activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + } + return; + } + + if (mCurrentContext) { + mCurrentContext->OnVisibilityChange(); + } +} + +void +HTMLCanvasElement::OnMemoryPressure() +{ + if (mOffscreenCanvas) { + class Runnable final : public nsCancelableRunnable + { + public: + explicit Runnable(AsyncCanvasRenderer* aRenderer) + : mRenderer(aRenderer) + {} + + NS_IMETHOD Run() + { + if (mRenderer && mRenderer->mContext) { + mRenderer->mContext->OnMemoryPressure(); + } + + return NS_OK; + } + + void Revoke() + { + mRenderer = nullptr; + } + + private: + RefPtr mRenderer; + }; + + RefPtr runnable = new Runnable(mAsyncCanvasRenderer); + nsCOMPtr activeThread = mAsyncCanvasRenderer->GetActiveThread(); + if (activeThread) { + activeThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + } + return; + } + + if (mCurrentContext) { + mCurrentContext->OnMemoryPressure(); + } +} + +/* static */ void +HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer) +{ + HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement; + if (!element) { + return; + } + + if (element->GetWidthHeight() == aRenderer->GetSize()) { + return; + } + + gfx::IntSize asyncCanvasSize = aRenderer->GetSize(); + + ErrorResult rv; + element->SetUnsignedIntAttr(nsGkAtoms::width, asyncCanvasSize.width, rv); + if (rv.Failed()) { + NS_WARNING("Failed to set width attribute to a canvas element asynchronously."); + } + + element->SetUnsignedIntAttr(nsGkAtoms::height, asyncCanvasSize.height, rv); + if (rv.Failed()) { + NS_WARNING("Failed to set height attribute to a canvas element asynchronously."); + } + + element->mResetLayer = true; +} + +/* static */ void +HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer) +{ + HTMLCanvasElement *element = aRenderer->mHTMLCanvasElement; + if (!element) { + return; + } + + element->InvalidateCanvasContent(nullptr); +} + } // namespace dom } // namespace mozilla diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h index a565c71bd4..1d11a27706 100644 --- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -8,20 +8,27 @@ #include "mozilla/Attributes.h" #include "mozilla/WeakPtr.h" +#include "nsIDOMEventListener.h" #include "nsIDOMHTMLCanvasElement.h" +#include "nsIObserver.h" #include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" #include "nsSize.h" #include "nsError.h" +#include "mozilla/dom/CanvasRenderingContextHelper.h" #include "mozilla/gfx/Rect.h" +#include "mozilla/layers/LayersTypes.h" class nsICanvasRenderingContextInternal; class nsITimerCallback; namespace mozilla { +class WebGLContext; + namespace layers { +class AsyncCanvasRenderer; class CanvasLayer; class Image; class LayerManager; @@ -35,14 +42,33 @@ class CanvasCaptureMediaStream; class File; class FileCallback; class HTMLCanvasPrintState; +class OffscreenCanvas; class PrintCallback; class RequestedFrameRefreshObserver; -enum class CanvasContextType : uint8_t { - NoContext, - Canvas2D, - WebGL1, - WebGL2 +// Listen visibilitychange and memory-pressure event and inform +// context when event is fired. +class HTMLCanvasElementObserver final : public nsIObserver + , public nsIDOMEventListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIDOMEVENTLISTENER + + explicit HTMLCanvasElementObserver(HTMLCanvasElement* aElement); + void Destroy(); + + void RegisterVisibilityChangeEvent(); + void UnregisterVisibilityChangeEvent(); + + void RegisterMemoryPressureEvent(); + void UnregisterMemoryPressureEvent(); + +private: + ~HTMLCanvasElementObserver(); + + HTMLCanvasElement* mElement; }; /* @@ -84,13 +110,15 @@ protected: }; class HTMLCanvasElement final : public nsGenericHTMLElement, - public nsIDOMHTMLCanvasElement + public nsIDOMHTMLCanvasElement, + public CanvasRenderingContextHelper { enum { DEFAULT_CANVAS_WIDTH = 300, DEFAULT_CANVAS_HEIGHT = 150 }; + typedef layers::AsyncCanvasRenderer AsyncCanvasRenderer; typedef layers::CanvasLayer CanvasLayer; typedef layers::LayerManager LayerManager; @@ -116,6 +144,11 @@ public: } void SetHeight(uint32_t aHeight, ErrorResult& aRv) { + if (mOffscreenCanvas) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + SetUnsignedIntAttr(nsGkAtoms::height, aHeight, aRv); } uint32_t Width() @@ -124,30 +157,45 @@ public: } void SetWidth(uint32_t aWidth, ErrorResult& aRv) { + if (mOffscreenCanvas) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + SetUnsignedIntAttr(nsGkAtoms::width, aWidth, aRv); } - already_AddRefed + + virtual already_AddRefed GetContext(JSContext* aCx, const nsAString& aContextId, JS::Handle aContextOptions, - ErrorResult& aRv); + ErrorResult& aRv) override; + void ToDataURL(JSContext* aCx, const nsAString& aType, JS::Handle aParams, nsAString& aDataURL, ErrorResult& aRv) { aRv = ToDataURL(aType, aParams, aCx, aDataURL); } + void ToBlob(JSContext* aCx, FileCallback& aCallback, const nsAString& aType, JS::Handle aParams, ErrorResult& aRv); + OffscreenCanvas* TransferControlToOffscreen(ErrorResult& aRv); + bool MozOpaque() const { return GetBoolAttr(nsGkAtoms::moz_opaque); } void SetMozOpaque(bool aValue, ErrorResult& aRv) { + if (mOffscreenCanvas) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + SetHTMLBoolAttr(nsGkAtoms::moz_opaque, aValue, aRv); } already_AddRefed MozGetAsFile(const nsAString& aName, @@ -160,11 +208,6 @@ public: aRv = MozGetIPCContext(aContextId, getter_AddRefs(context)); return context.forget(); } - void MozFetchAsStream(nsIInputStreamCallback* aCallback, - const nsAString& aType, ErrorResult& aRv) - { - aRv = MozFetchAsStream(aCallback, aType); - } PrintCallback* GetMozPrintCallback() const; void SetMozPrintCallback(PrintCallback* aCallback); @@ -209,6 +252,7 @@ public: * across its entire area. */ bool GetIsOpaque(); + virtual bool GetOpaqueAttr() override; virtual already_AddRefed GetSurfaceSnapshot(bool* aPremultAlpha = nullptr); @@ -287,19 +331,25 @@ public: nsresult GetContext(const nsAString& aContextId, nsISupports** aContext); + layers::LayersBackend GetCompositorBackendType() const; + + void OnVisibilityChange(); + + void OnMemoryPressure(); + + static void SetAttrFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer); + static void InvalidateFromAsyncCanvasRenderer(AsyncCanvasRenderer *aRenderer); + protected: virtual ~HTMLCanvasElement(); virtual JSObject* WrapNode(JSContext* aCx, JS::Handle aGivenProto) override; - nsIntSize GetWidthHeight(); + virtual nsIntSize GetWidthHeight() override; + + virtual already_AddRefed + CreateContext(CanvasContextType aContextType) override; - nsresult UpdateContext(JSContext* aCx, JS::Handle options); - nsresult ParseParams(JSContext* aCx, - const nsAString& aType, - const JS::Value& aEncoderOptions, - nsAString& aParams, - bool* usingCustomParseOptions); nsresult ExtractData(nsAString& aType, const nsAString& aOptions, nsIInputStream** aStream); @@ -312,13 +362,17 @@ protected: nsISupports** aResult); void CallPrintCallback(); - CanvasContextType mCurrentContextType; + AsyncCanvasRenderer* GetAsyncCanvasRenderer(); + + bool mResetLayer; RefPtr mOriginalCanvas; RefPtr mPrintCallback; - nsCOMPtr mCurrentContext; RefPtr mPrintState; nsTArray> mRequestedFrameListeners; RefPtr mRequestedFrameRefreshObserver; + RefPtr mAsyncCanvasRenderer; + RefPtr mOffscreenCanvas; + RefPtr mContextObserver; public: // Record whether this canvas should be write-only or not. diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 6aa319905b..982bee20fe 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -63,6 +63,7 @@ #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "nsLayoutUtils.h" +#include "nsVariant.h" #include "nsIDOMMutationEvent.h" #include "mozilla/ContentEvents.h" @@ -807,11 +808,12 @@ UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir) aDir->GetPath(unicodePath); if (unicodePath.IsEmpty()) // nothing to do return NS_OK; - nsCOMPtr prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID); - if (!prefValue) - return NS_ERROR_OUT_OF_MEMORY; + RefPtr prefValue = new nsVariant(); prefValue->SetAsAString(unicodePath); + // Use the document's current load context to ensure that the content pref + // service doesn't persistently store this directory for this domain if the + // user is using private browsing: nsCOMPtr loadContext = aDoc->GetLoadContext(); return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext, nullptr); } diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index fafaeb0d3e..c99b908b26 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -40,8 +40,17 @@ class Date; class File; class FileList; -class UploadLastDir final : public nsIObserver, public nsSupportsWeakReference { - +/** + * A class we use to create a singleton object that is used to keep track of + * the last directory from which the user has picked files (via + * ) on a per-domain basis. The implementation uses + * nsIContentPrefService2/NS_CONTENT_PREF_SERVICE_CONTRACTID to store the last + * directory per-domain, and to ensure that whether the directories are + * persistently saved (saved across sessions) or not honors whether or not the + * page is being viewed in private browsing. + */ +class UploadLastDir final : public nsIObserver, public nsSupportsWeakReference +{ ~UploadLastDir() {} public: diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 71b09eee3d..2e95a1703b 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -70,6 +70,7 @@ #include "MediaSourceDecoder.h" #include "AudioStreamTrack.h" #include "VideoStreamTrack.h" +#include "MediaTrackList.h" #include "AudioChannelService.h" @@ -708,6 +709,7 @@ void HTMLMediaElement::AbortExistingLoads() EndSrcMediaStreamPlayback(); } + RemoveMediaElementFromURITable(); mLoadingSrc = nullptr; mMediaSource = nullptr; @@ -911,6 +913,7 @@ void HTMLMediaElement::SelectResource() NS_ASSERTION(!mIsLoadingFromSourceChildren, "Should think we're not loading from source children by default"); + RemoveMediaElementFromURITable(); mLoadingSrc = uri; mMediaSource = mSrcMediaSource; UpdatePreloadAction(); @@ -957,6 +960,8 @@ void HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack* aTrack) return; } + LOG(LogLevel::Debug, ("MediaElement %p MediaStreamTrack %p enabled", this)); + // TODO: We are dealing with single audio track and video track for now. if (AudioTrack* track = aTrack->AsAudioTrack()) { if (!track->Enabled()) { @@ -975,6 +980,8 @@ void HTMLMediaElement::NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream) return; } + LOG(LogLevel::Debug, ("MediaElement %p MediaStream tracks available", this)); + bool videoHasChanged = IsVideo() && HasVideo() != !VideoTracks()->IsEmpty(); if (videoHasChanged) { @@ -1049,6 +1056,7 @@ void HTMLMediaElement::LoadFromSourceChildren() continue; } + RemoveMediaElementFromURITable(); mLoadingSrc = uri; mMediaSource = childSrc->GetSrcMediaSource(); NS_ASSERTION(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING, @@ -2080,6 +2088,7 @@ HTMLMediaElement::HTMLMediaElement(already_AddRefed& aNo mPlayingThroughTheAudioChannel(false), mDisableVideo(false), mPlayBlockedBecauseHidden(false), + mMediaStreamTrackListener(nullptr), mElementInTreeState(ELEMENT_NOT_INTREE), mHasUserInteraction(false), mDefaultPlaybackStartPosition(0.0) @@ -3078,10 +3087,34 @@ public: mElement->NotifyMediaStreamTracksAvailable(aStream); } + private: WeakPtr mElement; }; +class HTMLMediaElement::MediaStreamTrackListener : + public DOMMediaStream::TrackListener +{ +public: + explicit MediaStreamTrackListener(HTMLMediaElement* aElement): + mElement(aElement) {} + + void NotifyTrackAdded(const RefPtr& aTrack) override + { + mElement->NotifyMediaStreamTrackAdded(aTrack); + } + + void NotifyTrackRemoved(const RefPtr& aTrack) override + { + mElement->NotifyMediaStreamTrackRemoved(aTrack); + } + +protected: + ~MediaStreamTrackListener() {} + + HTMLMediaElement* const mElement; +}; + void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags) { if (!mSrcStream) { @@ -3098,6 +3131,10 @@ void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags) } mSrcStreamIsPlaying = shouldPlay; + LOG(LogLevel::Debug, ("MediaElement %p %s playback of DOMMediaStream %p", + this, shouldPlay ? "Setting up" : "Removing", + mSrcStream.get())); + if (shouldPlay) { mSrcStreamPausedCurrentTime = -1; @@ -3177,14 +3214,14 @@ void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream) UpdateSrcMediaStreamPlaying(); - // Note: we must call DisconnectTrackListListeners(...) before dropping - // mSrcStream. // If we pause this media element, track changes in the underlying stream // will continue to fire events at this element and alter its track list. // That's simpler than delaying the events, but probably confusing... - mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks()); + ConstructMediaTracks(); mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this)); + mMediaStreamTrackListener = new MediaStreamTrackListener(this); + mSrcStream->RegisterTrackListener(mMediaStreamTrackListener); ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE); ChangeDelayLoadStatus(false); @@ -3199,11 +3236,116 @@ void HTMLMediaElement::EndSrcMediaStreamPlayback() UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM); - mSrcStream->DisconnectTrackListListeners(AudioTracks(), VideoTracks()); + mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener); + mMediaStreamTrackListener = nullptr; mSrcStream = nullptr; } +static already_AddRefed +CreateAudioTrack(AudioStreamTrack* aStreamTrack) +{ + nsAutoString id; + nsAutoString label; + aStreamTrack->GetId(id); + aStreamTrack->GetLabel(label); + + return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"), + label, EmptyString(), + aStreamTrack->Enabled()); +} + +static already_AddRefed +CreateVideoTrack(VideoStreamTrack* aStreamTrack) +{ + nsAutoString id; + nsAutoString label; + aStreamTrack->GetId(id); + aStreamTrack->GetLabel(label); + + return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"), + label, EmptyString()); +} + +void HTMLMediaElement::ConstructMediaTracks() +{ + nsTArray> tracks; + mSrcStream->GetTracks(tracks); + + int firstEnabledVideo = -1; + for (const RefPtr& track : tracks) { + if (track->Ended()) { + continue; + } + + if (AudioStreamTrack* t = track->AsAudioStreamTrack()) { + RefPtr audioTrack = CreateAudioTrack(t); + AudioTracks()->AddTrack(audioTrack); + } else if (VideoStreamTrack* t = track->AsVideoStreamTrack()) { + RefPtr videoTrack = CreateVideoTrack(t); + VideoTracks()->AddTrack(videoTrack); + firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0) + ? (VideoTracks()->Length() - 1) + : firstEnabledVideo; + } + } + + if (VideoTracks()->Length() > 0) { + // If media resource does not indicate a particular set of video tracks to + // enable, the one that is listed first in the element's videoTracks object + // must be selected. + int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0; + (*VideoTracks())[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS); + } +} + +void +HTMLMediaElement::NotifyMediaStreamTrackAdded(const RefPtr& aTrack) +{ + MOZ_ASSERT(aTrack); + +#ifdef DEBUG + nsString id; + aTrack->GetId(id); + + LOG(LogLevel::Debug, ("%p, Adding MediaTrack with id %s", + this, NS_ConvertUTF16toUTF8(id).get())); +#endif + + if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) { + RefPtr audioTrack = CreateAudioTrack(t); + AudioTracks()->AddTrack(audioTrack); + } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) { + RefPtr videoTrack = CreateVideoTrack(t); + VideoTracks()->AddTrack(videoTrack); + } +} + +void +HTMLMediaElement::NotifyMediaStreamTrackRemoved(const RefPtr& aTrack) +{ + MOZ_ASSERT(aTrack); + + nsAutoString id; + aTrack->GetId(id); + + LOG(LogLevel::Debug, ("%p, Removing MediaTrack with id %s", + this, NS_ConvertUTF16toUTF8(id).get())); + + if (MediaTrack* t = AudioTracks()->GetTrackById(id)) { + AudioTracks()->RemoveTrack(t); + } else if (MediaTrack* t = VideoTracks()->GetTrackById(id)) { + VideoTracks()->RemoveTrack(t); + } else { + // XXX Uncomment this when DOMMediaStream doesn't call NotifyTrackRemoved + // multiple times for the same track, i.e., when it implements the + // "addtrack" and "removetrack" events. + // NS_ASSERTION(false, "MediaStreamTrack ended but did not exist in track lists"); + return; + } +} + + void HTMLMediaElement::ProcessMediaFragmentURI() { nsMediaFragmentURIParser parser(mLoadingSrc); @@ -3572,6 +3714,8 @@ HTMLMediaElement::UpdateReadyStateInternal() { if (!mDecoder && !mSrcStream) { // Not initialized - bail out. + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Not initialized", this)); return; } @@ -3579,6 +3723,8 @@ HTMLMediaElement::UpdateReadyStateInternal() // aNextFrame might have a next frame because the decoder can advance // on its own thread before MetadataLoaded gets a chance to run. // The arrival of more data can't change us out of this readyState. + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Decoder ready state < HAVE_METADATA", this)); return; } @@ -3586,11 +3732,23 @@ HTMLMediaElement::UpdateReadyStateInternal() bool hasAudio = !AudioTracks()->IsEmpty(); bool hasVideo = !VideoTracks()->IsEmpty(); - if ((!hasAudio && !hasVideo) || - (IsVideo() && hasVideo && !HasVideo())) { + if (!hasAudio && !hasVideo) { + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Stream with no tracks", this)); return; } + if (IsVideo() && hasVideo && !HasVideo()) { + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Stream waiting for video", this)); + return; + } + + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() Stream has " + "metadata; audioTracks=%d, videoTracks=%d, " + "hasVideoFrame=%d", this, AudioTracks()->Length(), + VideoTracks()->Length(), HasVideo())); + // We are playing a stream that has video and a video frame is now set. // This means we have all metadata needed to change ready state. MediaInfo mediaInfo = mMediaInfo; @@ -3609,6 +3767,8 @@ HTMLMediaElement::UpdateReadyStateInternal() } if (nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) { + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA", this)); ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA); return; } @@ -3619,6 +3779,8 @@ HTMLMediaElement::UpdateReadyStateInternal() // Also, if video became available after advancing to HAVE_CURRENT_DATA // while we are still playing, we need to revert to HAVE_METADATA until // a video frame is available. + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Playing video but no video frame; Forcing HAVE_METADATA", this)); ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA); return; } @@ -3633,6 +3795,8 @@ HTMLMediaElement::UpdateReadyStateInternal() // should remain at HAVE_CURRENT_DATA in this case. // Note that this state transition includes the case where we finished // downloaded the whole data stream. + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Decoder download suspended by cache", this)); ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA); return; } @@ -3648,6 +3812,8 @@ HTMLMediaElement::UpdateReadyStateInternal() } if (mSrcStream) { + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Stream HAVE_ENOUGH_DATA", this)); ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA); return; } @@ -3662,9 +3828,13 @@ HTMLMediaElement::UpdateReadyStateInternal() // without stopping to buffer. if (mDecoder->CanPlayThrough()) { + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Decoder can play through", this)); ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA); return; } + LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() " + "Default; Decoder has future data", this)); ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA); } diff --git a/dom/html/HTMLMediaElement.h b/dom/html/HTMLMediaElement.h index 53645dae8e..fc26a99d67 100644 --- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -651,6 +651,7 @@ protected: class MediaLoadListener; class MediaStreamTracksAvailableCallback; + class MediaStreamTrackListener; class StreamListener; class StreamSizeListener; @@ -742,6 +743,25 @@ protected: enum { REMOVING_SRC_STREAM = 0x1 }; void UpdateSrcMediaStreamPlaying(uint32_t aFlags = 0); + /** + * If loading and playing a MediaStream, for each MediaStreamTrack in the + * MediaStream, create a corresponding AudioTrack or VideoTrack during the + * phase of resource fetching. + */ + void ConstructMediaTracks(); + + /** + * Called by our DOMMediaStream::TrackListener when a new MediaStreamTrack has + * been added to the playback stream of |mSrcStream|. + */ + void NotifyMediaStreamTrackAdded(const RefPtr& aTrack); + + /** + * Called by our DOMMediaStream::TrackListener when a MediaStreamTrack in + * |mSrcStream|'s playback stream has ended. + */ + void NotifyMediaStreamTrackRemoved(const RefPtr& aTrack); + /** * Returns an nsDOMMediaStream containing the played contents of this * element. When aFinishWhenEnded is true, when this element ends playback @@ -1398,6 +1418,8 @@ protected: RefPtr mVideoTrackList; + RefPtr mMediaStreamTrackListener; + enum ElementInTreeState { // The MediaElement is not in the DOM tree now. ELEMENT_NOT_INTREE, diff --git a/dom/html/TextTrackManager.cpp b/dom/html/TextTrackManager.cpp index 4dcca72508..2579bbcc28 100644 --- a/dom/html/TextTrackManager.cpp +++ b/dom/html/TextTrackManager.cpp @@ -13,6 +13,7 @@ #include "mozilla/dom/Event.h" #include "mozilla/ClearOnShutdown.h" #include "nsComponentManagerUtils.h" +#include "nsVariant.h" #include "nsVideoFrame.h" #include "nsIFrame.h" #include "nsTArrayHelpers.h" @@ -214,8 +215,7 @@ TextTrackManager::UpdateCueDisplay() mTextTracks->UpdateAndGetShowingCues(activeCues); if (activeCues.Length() > 0) { - nsCOMPtr jsCues = - do_CreateInstance("@mozilla.org/variant;1"); + RefPtr jsCues = new nsVariant(); jsCues->SetAsArray(nsIDataType::VTYPE_INTERFACE, &NS_GET_IID(nsIDOMEventTarget), diff --git a/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl b/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl index eaccc631b0..8e589629e2 100644 --- a/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl +++ b/dom/interfaces/html/nsIDOMHTMLCanvasElement.idl @@ -19,7 +19,7 @@ interface nsIDOMBlob; interface nsIVariant; interface nsIInputStreamCallback; -[uuid(2c984658-2e7c-4774-8ac5-cf1b39f8bec3)] +[uuid(4e8f1316-b601-471d-8f44-3c650d91ee9b)] interface nsIDOMHTMLCanvasElement : nsISupports { attribute unsigned long width; @@ -43,10 +43,5 @@ interface nsIDOMHTMLCanvasElement : nsISupports // A Mozilla-only extension to get a canvas context backed by double-buffered // shared memory. Only privileged callers can call this. nsISupports MozGetIPCContext(in DOMString contextId); - - // A Mozilla-only extension that returns the canvas' image data as a data - // stream in the desired image format. - void mozFetchAsStream(in nsIInputStreamCallback callback, - [optional] in DOMString type); }; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index aeb59e9c70..6afdc92957 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -82,6 +82,7 @@ #include "nsMemoryInfoDumper.h" #include "nsServiceManagerUtils.h" #include "nsStyleSheetService.h" +#include "nsVariant.h" #include "nsXULAppAPI.h" #include "nsIScriptError.h" #include "nsIConsoleService.h" @@ -2838,9 +2839,7 @@ ContentChild::RecvInvokeDragSession(nsTArray&& aTransfers, auto& items = aTransfers[i].items(); for (uint32_t j = 0; j < items.Length(); ++j) { const IPCDataTransferItem& item = items[j]; - nsCOMPtr variant = - do_CreateInstance(NS_VARIANT_CONTRACTID); - NS_ENSURE_TRUE(variant, false); + RefPtr variant = new nsVariant(); if (item.data().type() == IPCDataTransferData::TnsString) { const nsString& data = item.data().get_nsString(); variant->SetAsAString(data); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 9a09322f8f..16133b1fce 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -66,6 +66,7 @@ #include "nsIXULWindow.h" #include "nsIRemoteBrowser.h" #include "nsViewManager.h" +#include "nsVariant.h" #include "nsIWidget.h" #include "nsIWindowMediator.h" #include "nsIWindowWatcher.h" @@ -3503,7 +3504,7 @@ TabParent::RecvInvokeDragSession(nsTArray&& aTransfers, } mDragAreaX = aDragAreaX; mDragAreaY = aDragAreaY; - + esm->BeginTrackingRemoteDragGesture(mFrameElement); return true; @@ -3516,11 +3517,7 @@ TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer) nsTArray& itemArray = mInitialDataTransferItems[i]; for (uint32_t j = 0; j < itemArray.Length(); ++j) { DataTransferItem& item = itemArray[j]; - nsCOMPtr variant = - do_CreateInstance(NS_VARIANT_CONTRACTID); - if (!variant) { - break; - } + RefPtr variant = new nsVariant(); // Special case kFilePromiseMime so that we get the right // nsIFlavorDataProvider for it. if (item.mFlavor.EqualsLiteral(kFilePromiseMime)) { diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index e98fd33e26..ba37643f20 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -109,6 +109,8 @@ MediaLoadSourceMediaNotMatched=Specified "media" attribute of "%1$S" does not ma MediaLoadUnsupportedMimeType=HTTP "Content-Type" of "%1$S" is not supported. Load of media resource %2$S failed. # LOCALIZATION NOTE: %S is the URL of the media resource which failed to load because of error in decoding. MediaLoadDecodeError=Media resource %S could not be decoded. +# LOCALIZATION NOTE: Do not translate "MediaStream", "stop()" and "MediaStreamTrack" +MediaStreamStopDeprecatedWarning=MediaStream.stop() is deprecated and will soon be removed. Use MediaStreamTrack.stop() instead. # LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name" DOMExceptionCodeWarning=Use of DOMException's code attribute is deprecated. Use name instead. # LOCALIZATION NOTE: Do not translate "__exposedProps__" diff --git a/dom/media/DOMMediaStream.cpp b/dom/media/DOMMediaStream.cpp index 5ba1793ffd..6afcaa62d6 100644 --- a/dom/media/DOMMediaStream.cpp +++ b/dom/media/DOMMediaStream.cpp @@ -6,7 +6,9 @@ #include "DOMMediaStream.h" #include "nsContentUtils.h" #include "nsServiceManagerUtils.h" +#include "nsIScriptError.h" #include "nsIUUIDGenerator.h" +#include "nsPIDOMWindow.h" #include "mozilla/dom/MediaStreamBinding.h" #include "mozilla/dom/LocalMediaStreamBinding.h" #include "mozilla/dom/AudioNode.h" @@ -21,6 +23,13 @@ #include "VideoStreamTrack.h" #include "Layers.h" +#ifdef LOG +#undef LOG +#endif + +static PRLogModuleInfo* gMediaStreamLog; +#define LOG(type, msg) MOZ_LOG(gMediaStreamLog, type, msg) + using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::layers; @@ -97,6 +106,17 @@ public: MediaInputPort* GetInputPort() const { return mInputPort; } MediaStreamTrack* GetTrack() const { return mTrack; } + /** + * Blocks aTrackId from going into mInputPort unless the port has been + * destroyed. + */ + void BlockTrackId(TrackID aTrackId) + { + if (mInputPort) { + mInputPort->BlockTrackId(aTrackId); + } + } + private: RefPtr mInputPort; RefPtr mTrack; @@ -110,6 +130,83 @@ NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release) +/** + * Listener registered on the Owned stream to detect added and ended owned + * tracks for keeping the list of MediaStreamTracks in sync with the tracks + * added and ended directly at the source. + */ +class DOMMediaStream::OwnedStreamListener : public MediaStreamListener { +public: + explicit OwnedStreamListener(DOMMediaStream* aStream) + : mStream(aStream) + {} + + void Forget() { mStream = nullptr; } + + void DoNotifyTrackCreated(TrackID aTrackId, MediaSegment::Type aType) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mStream) { + return; + } + + MediaStreamTrack* track = mStream->FindOwnedDOMTrack( + mStream->GetOwnedStream(), aTrackId); + if (track) { + // This track has already been manually created. Abort. + return; + } + + NS_WARN_IF_FALSE(!mStream->mTracks.IsEmpty(), + "A new track was detected on the input stream; creating a corresponding MediaStreamTrack. " + "Initial tracks should be added manually to immediately and synchronously be available to JS."); + mStream->CreateOwnDOMTrack(aTrackId, aType); + } + + void DoNotifyTrackEnded(TrackID aTrackId) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mStream) { + return; + } + + RefPtr track = + mStream->FindOwnedDOMTrack(mStream->GetOwnedStream(), aTrackId); + NS_ASSERTION(track, "Owned MediaStreamTracks must be known by the DOMMediaStream"); + if (track) { + LOG(LogLevel::Debug, ("DOMMediaStream %p MediaStreamTrack %p ended at the source. Marking it ended.", + mStream, track.get())); + track->NotifyEnded(); + } + } + + void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, + StreamTime aTrackOffset, uint32_t aTrackEvents, + const MediaSegment& aQueuedMedia, + MediaStream* aInputStream, + TrackID aInputTrackID) override + { + if (aTrackEvents & TRACK_EVENT_CREATED) { + nsCOMPtr runnable = + NS_NewRunnableMethodWithArgs( + this, &OwnedStreamListener::DoNotifyTrackCreated, + aID, aQueuedMedia.GetType()); + aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); + } else if (aTrackEvents & TRACK_EVENT_ENDED) { + nsCOMPtr runnable = + NS_NewRunnableMethodWithArgs( + this, &OwnedStreamListener::DoNotifyTrackEnded, aID); + aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); + } + } + +private: + // These fields may only be accessed on the main thread + DOMMediaStream* mStream; +}; + /** * Listener registered on the Playback stream to detect when tracks end and when * all new tracks this iteration have been created - for when several tracks are @@ -121,93 +218,66 @@ public: : mStream(aStream) {} - // Main thread only - void Forget() { mStream = nullptr; } - DOMMediaStream* GetStream() { return mStream; } + void Forget() + { + MOZ_ASSERT(NS_IsMainThread()); + mStream = nullptr; + } - class TrackChange : public nsRunnable { - public: - TrackChange(PlaybackStreamListener* aListener, - TrackID aID, StreamTime aTrackOffset, - uint32_t aEvents, MediaSegment::Type aType, - MediaStream* aInputStream, TrackID aInputTrackID) - : mListener(aListener), mID(aID), mEvents(aEvents), mType(aType) - , mInputStream(aInputStream), mInputTrackID(aInputTrackID) - { + void DoNotifyTrackEnded(MediaStream* aInputStream, + TrackID aInputTrackID) + { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mStream) { + return; } - NS_IMETHOD Run() - { - NS_ASSERTION(NS_IsMainThread(), "main thread only"); + LOG(LogLevel::Debug, ("DOMMediaStream %p Track %u of stream %p ended", + mStream, aInputTrackID, aInputStream)); - DOMMediaStream* stream = mListener->GetStream(); - if (!stream) { - return NS_OK; - } + RefPtr track = + mStream->FindPlaybackDOMTrack(aInputStream, aInputTrackID); + if (track) { + LOG(LogLevel::Debug, ("DOMMediaStream %p Playback track; notifying stream listeners.", + mStream)); + mStream->NotifyTrackRemoved(track); + } else { + LOG(LogLevel::Debug, ("DOMMediaStream %p Not a playback track.", mStream)); + } + } - MOZ_ASSERT(mEvents & MediaStreamListener::TRACK_EVENT_ENDED); - RefPtr track = stream->FindOwnedDOMTrack(mInputStream, mID); - if (track) { - track->NotifyEnded(); - } + void DoNotifyFinishedTrackCreation() + { + MOZ_ASSERT(NS_IsMainThread()); - track = stream->FindPlaybackDOMTrack(mInputStream, mInputTrackID); - if (track) { - stream->NotifyMediaStreamTrackEnded(track); - } - return NS_OK; + if (!mStream) { + return; } - StreamTime mEndTime; - RefPtr mListener; - TrackID mID; - uint32_t mEvents; - MediaSegment::Type mType; - RefPtr mInputStream; - TrackID mInputTrackID; - }; + mStream->NotifyTracksCreated(); + } - virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, - StreamTime aTrackOffset, - uint32_t aTrackEvents, - const MediaSegment& aQueuedMedia, - MediaStream* aInputStream, - TrackID aInputTrackID) override + // The methods below are called on the MediaStreamGraph thread. + + void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID, + StreamTime aTrackOffset, uint32_t aTrackEvents, + const MediaSegment& aQueuedMedia, + MediaStream* aInputStream, + TrackID aInputTrackID) override { if (aTrackEvents & TRACK_EVENT_ENDED) { - RefPtr runnable = - new TrackChange(this, aID, aTrackOffset, aTrackEvents, - aQueuedMedia.GetType(), aInputStream, aInputTrackID); + nsCOMPtr runnable = + NS_NewRunnableMethodWithArgs, TrackID>( + this, &PlaybackStreamListener::DoNotifyTrackEnded, aInputStream, aInputTrackID); aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); } } - class TracksCreatedRunnable : public nsRunnable { - public: - explicit TracksCreatedRunnable(PlaybackStreamListener* aListener) - : mListener(aListener) - { - } - - NS_IMETHOD Run() - { - MOZ_ASSERT(NS_IsMainThread()); - - DOMMediaStream* stream = mListener->GetStream(); - if (!stream) { - return NS_OK; - } - - stream->TracksCreated(); - return NS_OK; - } - - RefPtr mListener; - }; - - virtual void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override + void NotifyFinishedTrackCreation(MediaStreamGraph* aGraph) override { - RefPtr runnable = new TracksCreatedRunnable(this); + nsCOMPtr runnable = + NS_NewRunnableMethod(this, &PlaybackStreamListener::DoNotifyFinishedTrackCreation); aGraph->DispatchToMainThreadAfterStreamStateUpdate(runnable.forget()); } @@ -268,6 +338,10 @@ DOMMediaStream::DOMMediaStream() nsCOMPtr uuidgen = do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (!gMediaStreamLog) { + gMediaStreamLog = PR_NewLogModule("MediaStream"); + } + if (NS_SUCCEEDED(rv) && uuidgen) { nsID uuid; memset(&uuid, 0, sizeof(uuid)); @@ -288,9 +362,14 @@ DOMMediaStream::~DOMMediaStream() void DOMMediaStream::Destroy() { - if (mListener) { - mListener->Forget(); - mListener = nullptr; + LOG(LogLevel::Debug, ("DOMMediaStream %p Being destroyed.", this)); + if (mOwnedListener) { + mOwnedListener->Forget(); + mOwnedListener = nullptr; + } + if (mPlaybackListener) { + mPlaybackListener->Forget(); + mPlaybackListener = nullptr; } if (mPlaybackPort) { mPlaybackPort->Destroy(); @@ -366,10 +445,69 @@ DOMMediaStream::GetTracks(nsTArray >& aTracks) } } +void +DOMMediaStream::AddTrack(MediaStreamTrack& aTrack) +{ + RefPtr dest = mPlaybackStream->AsProcessedStream(); + MOZ_ASSERT(dest); + if (!dest) { + return; + } + + LOG(LogLevel::Info, ("DOMMediaStream %p Adding track %p (from stream %p with ID %d)", + this, &aTrack, aTrack.GetStream(), aTrack.GetTrackID())); + + if (HasTrack(aTrack)) { + LOG(LogLevel::Debug, ("DOMMediaStream %p already contains track %p", this, &aTrack)); + return; + } + + RefPtr addedDOMStream = aTrack.GetStream(); + MOZ_RELEASE_ASSERT(addedDOMStream); + + RefPtr owningStream = addedDOMStream->GetOwnedStream(); + MOZ_RELEASE_ASSERT(owningStream); + + CombineWithPrincipal(addedDOMStream->mPrincipal); + + // Hook up the underlying track with our underlying playback stream. + RefPtr inputPort = + GetPlaybackStream()->AllocateInputPort(owningStream, aTrack.GetTrackID()); + RefPtr trackPort = + new TrackPort(inputPort, &aTrack, TrackPort::InputPortOwnership::OWNED); + mTracks.AppendElement(trackPort.forget()); + NotifyTrackAdded(&aTrack); + + LOG(LogLevel::Debug, ("DOMMediaStream %p Added track %p", this, &aTrack)); +} + +void +DOMMediaStream::RemoveTrack(MediaStreamTrack& aTrack) +{ + LOG(LogLevel::Info, ("DOMMediaStream %p Removing track %p (from stream %p with ID %d)", + this, &aTrack, aTrack.GetStream(), aTrack.GetTrackID())); + + RefPtr toRemove = FindPlaybackTrackPort(aTrack); + if (!toRemove) { + LOG(LogLevel::Debug, ("DOMMediaStream %p does not contain track %p", this, &aTrack)); + return; + } + + // If the track comes from a TRACK_ANY input port (i.e., mOwnedPort), we need + // to block it in the port. Doing this for a locked track is still OK as it + // will first block the track, then destroy the port. Both cause the track to + // end. + toRemove->BlockTrackId(aTrack.GetTrackID()); + + DebugOnly removed = mTracks.RemoveElement(toRemove); + MOZ_ASSERT(removed); + LOG(LogLevel::Debug, ("DOMMediaStream %p Removed track %p", this, &aTrack)); +} + bool DOMMediaStream::HasTrack(const MediaStreamTrack& aTrack) const { - return !!FindPlaybackDOMTrack(aTrack.GetStream()->GetOwnedStream(), aTrack.GetTrackID()); + return !!FindPlaybackTrackPort(aTrack); } bool @@ -428,9 +566,14 @@ DOMMediaStream::InitStreamCommon(MediaStream* aStream, mPlaybackStream->SetAutofinish(true); mPlaybackPort = mPlaybackStream->AllocateInputPort(mOwnedStream); - // Setup track listener - mListener = new PlaybackStreamListener(this); - mPlaybackStream->AddListener(mListener); + LOG(LogLevel::Debug, ("DOMMediaStream %p Initiated with mInputStream=%p, mOwnedStream=%p, mPlaybackStream=%p", + this, mInputStream, mOwnedStream, mPlaybackStream)); + + // Setup track listeners + mOwnedListener = new OwnedStreamListener(this); + mOwnedStream->AddListener(mOwnedListener); + mPlaybackListener = new PlaybackStreamListener(this); + mPlaybackStream->AddListener(mPlaybackListener); } already_AddRefed @@ -554,6 +697,8 @@ DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType) MOZ_CRASH("Unhandled track type"); } + LOG(LogLevel::Debug, ("DOMMediaStream %p Created new track %p with ID %u", this, track, aTrackID)); + RefPtr ownedTrackPort = new TrackPort(mOwnedPort, track, TrackPort::InputPortOwnership::EXTERNAL); mOwnedTracks.AppendElement(ownedTrackPort.forget()); @@ -562,7 +707,7 @@ DOMMediaStream::CreateOwnDOMTrack(TrackID aTrackID, MediaSegment::Type aType) new TrackPort(mPlaybackPort, track, TrackPort::InputPortOwnership::EXTERNAL); mTracks.AppendElement(playbackTrackPort.forget()); - NotifyMediaStreamTrackCreated(track); + NotifyTrackAdded(track); return track; } @@ -603,6 +748,17 @@ DOMMediaStream::FindPlaybackDOMTrack(MediaStream* aInputStream, TrackID aInputTr return nullptr; } +DOMMediaStream::TrackPort* +DOMMediaStream::FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const +{ + for (const RefPtr& info : mTracks) { + if (info->GetTrack() == &aTrack) { + return info; + } + } + return nullptr; +} + void DOMMediaStream::NotifyMediaStreamGraphShutdown() { @@ -610,7 +766,7 @@ DOMMediaStream::NotifyMediaStreamGraphShutdown() // to prevent leaks. mNotifiedOfMediaStreamGraphShutdown = true; mRunOnTracksAvailable.Clear(); - + mTrackListeners.Clear(); mConsumersToKeepAlive.Clear(); } @@ -634,7 +790,7 @@ DOMMediaStream::OnTracksAvailable(OnTracksAvailableCallback* aRunnable) } void -DOMMediaStream::TracksCreated() +DOMMediaStream::NotifyTracksCreated() { mTracksCreated = true; CheckTracksAvailable(); @@ -654,117 +810,62 @@ DOMMediaStream::CheckTracksAvailable() } } +void +DOMMediaStream::RegisterTrackListener(TrackListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (mNotifiedOfMediaStreamGraphShutdown) { + // No more tracks will ever be added, so just do nothing. + return; + } + mTrackListeners.AppendElement(aListener); +} + +void +DOMMediaStream::UnregisterTrackListener(TrackListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + mTrackListeners.RemoveElement(aListener); +} + +void +DOMMediaStream::NotifyTrackAdded( + const RefPtr& aTrack) +{ + MOZ_ASSERT(NS_IsMainThread()); + + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + const RefPtr& listener = mTrackListeners[i]; + listener->NotifyTrackAdded(aTrack); + } +} + +void +DOMMediaStream::NotifyTrackRemoved( + const RefPtr& aTrack) +{ + MOZ_ASSERT(NS_IsMainThread()); + + for (int32_t i = mTrackListeners.Length() - 1; i >= 0; --i) { + const RefPtr& listener = mTrackListeners[i]; + listener->NotifyTrackRemoved(aTrack); + } +} + void DOMMediaStream::CreateAndAddPlaybackStreamListener(MediaStream* aStream) { MOZ_ASSERT(GetCameraStream(), "I'm a hack. Only DOMCameraControl may use me."); - mListener = new PlaybackStreamListener(this); - aStream->AddListener(mListener); -} - -already_AddRefed -DOMMediaStream::CreateAudioTrack(AudioStreamTrack* aStreamTrack) -{ - nsAutoString id; - nsAutoString label; - aStreamTrack->GetId(id); - aStreamTrack->GetLabel(label); - - return MediaTrackList::CreateAudioTrack(id, NS_LITERAL_STRING("main"), - label, EmptyString(), - aStreamTrack->Enabled()); -} - -already_AddRefed -DOMMediaStream::CreateVideoTrack(VideoStreamTrack* aStreamTrack) -{ - nsAutoString id; - nsAutoString label; - aStreamTrack->GetId(id); - aStreamTrack->GetLabel(label); - - return MediaTrackList::CreateVideoTrack(id, NS_LITERAL_STRING("main"), - label, EmptyString()); -} - -void -DOMMediaStream::ConstructMediaTracks(AudioTrackList* aAudioTrackList, - VideoTrackList* aVideoTrackList) -{ - MediaTrackListListener audioListener(aAudioTrackList); - mMediaTrackListListeners.AppendElement(audioListener); - MediaTrackListListener videoListener(aVideoTrackList); - mMediaTrackListListeners.AppendElement(videoListener); - - int firstEnabledVideo = -1; - for (const RefPtr& info : mTracks) { - if (AudioStreamTrack* t = info->GetTrack()->AsAudioStreamTrack()) { - RefPtr track = CreateAudioTrack(t); - aAudioTrackList->AddTrack(track); - } else if (VideoStreamTrack* t = info->GetTrack()->AsVideoStreamTrack()) { - RefPtr track = CreateVideoTrack(t); - aVideoTrackList->AddTrack(track); - firstEnabledVideo = (t->Enabled() && firstEnabledVideo < 0) - ? (aVideoTrackList->Length() - 1) - : firstEnabledVideo; - } - } - - if (aVideoTrackList->Length() > 0) { - // If media resource does not indicate a particular set of video tracks to - // enable, the one that is listed first in the element's videoTracks object - // must be selected. - int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0; - (*aVideoTrackList)[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS); - } -} - -void -DOMMediaStream::DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList, - const VideoTrackList* aVideoTrackList) -{ - for (auto i = mMediaTrackListListeners.Length(); i > 0; ) { // unsigned! - --i; // 0 ... Length()-1 range - if (mMediaTrackListListeners[i].mMediaTrackList == aAudioTrackList || - mMediaTrackListListeners[i].mMediaTrackList == aVideoTrackList) { - mMediaTrackListListeners.RemoveElementAt(i); - } - } -} - -void -DOMMediaStream::NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack) -{ - MOZ_ASSERT(aTrack); - - for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) { - if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) { - RefPtr track = CreateAudioTrack(t); - mMediaTrackListListeners[i].NotifyMediaTrackCreated(track); - } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) { - RefPtr track = CreateVideoTrack(t); - mMediaTrackListListeners[i].NotifyMediaTrackCreated(track); - } - } -} - -void -DOMMediaStream::NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack) -{ - MOZ_ASSERT(aTrack); - - nsAutoString id; - aTrack->GetId(id); - for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) { - mMediaTrackListListeners[i].NotifyMediaTrackEnded(id); - } + mPlaybackListener = new PlaybackStreamListener(this); + aStream->AddListener(mPlaybackListener); } DOMLocalMediaStream::~DOMLocalMediaStream() { if (mInputStream) { // Make sure Listeners of this stream know it's going away - Stop(); + StopImpl(); } } @@ -776,6 +877,20 @@ DOMLocalMediaStream::WrapObject(JSContext* aCx, JS::Handle aGivenProt void DOMLocalMediaStream::Stop() +{ + nsCOMPtr pWindow = do_QueryInterface(GetParentObject()); + nsIDocument* document = pWindow ? pWindow->GetExtantDoc() : nullptr; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Media"), + document, + nsContentUtils::eDOM_PROPERTIES, + "MediaStreamStopDeprecatedWarning"); + + StopImpl(); +} + +void +DOMLocalMediaStream::StopImpl() { if (mInputStream && mInputStream->AsSourceStream()) { mInputStream->AsSourceStream()->EndAllTrackAndFinish(); diff --git a/dom/media/DOMMediaStream.h b/dom/media/DOMMediaStream.h index 2ad8cfa4a0..020a0a73b0 100644 --- a/dom/media/DOMMediaStream.h +++ b/dom/media/DOMMediaStream.h @@ -182,11 +182,31 @@ class DOMMediaStream : public DOMEventTargetHelper typedef dom::VideoTrack VideoTrack; typedef dom::AudioTrackList AudioTrackList; typedef dom::VideoTrackList VideoTrackList; - typedef dom::MediaTrackListListener MediaTrackListListener; public: typedef dom::MediaTrackConstraints MediaTrackConstraints; - typedef uint8_t TrackTypeHints; + + class TrackListener { + NS_INLINE_DECL_REFCOUNTING(TrackListener) + + public: + /** + * Called when the DOMMediaStream has a new track added, either by + * JS (addTrack()) or the source creating one. + */ + virtual void + NotifyTrackAdded(const RefPtr& aTrack) {}; + + /** + * Called when the DOMMediaStream removes a track, either by + * JS (removeTrack()) or the source ending it. + */ + virtual void + NotifyTrackRemoved(const RefPtr& aTrack) {}; + + protected: + virtual ~TrackListener() {} + }; DOMMediaStream(); @@ -210,6 +230,8 @@ public: void GetAudioTracks(nsTArray >& aTracks); void GetVideoTracks(nsTArray >& aTracks); void GetTracks(nsTArray >& aTracks); + void AddTrack(MediaStreamTrack& aTrack); + void RemoveTrack(MediaStreamTrack& aTrack); // NON-WebIDL @@ -233,6 +255,11 @@ public: */ MediaStreamTrack* FindPlaybackDOMTrack(MediaStream* aOwningStream, TrackID aTrackID) const; + /** + * Returns the TrackPort connecting mOwnedStream to mPlaybackStream for aTrack. + */ + TrackPort* FindPlaybackTrackPort(const MediaStreamTrack& aTrack) const; + MediaStream* GetInputStream() const { return mInputStream; } ProcessedMediaStream* GetOwnedStream() const { return mOwnedStream; } ProcessedMediaStream* GetPlaybackStream() const { return mPlaybackStream; } @@ -388,23 +415,8 @@ public: } } - /** - * If loading and playing a MediaStream in a media element, for each - * MediaStreamTrack in the MediaStream, create a corresponding AudioTrack or - * VideoTrack during the phase of resource fetching. - */ - void ConstructMediaTracks(AudioTrackList* aAudioTrackList, - VideoTrackList* aVideoTrackList); - - /** - * MUST call this before the AudioTrackList or VideoTrackList go away - */ - void DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList, - const VideoTrackList* aVideoTrackList); - - virtual void NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack); - - virtual void NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack); + void RegisterTrackListener(TrackListener* aListener); + void UnregisterTrackListener(TrackListener* aListener); protected: virtual ~DOMMediaStream(); @@ -414,14 +426,21 @@ protected: void InitTrackUnionStream(nsIDOMWindow* aWindow, MediaStreamGraph* aGraph); void InitAudioCaptureStream(nsIDOMWindow* aWindow, MediaStreamGraph* aGraph); void InitStreamCommon(MediaStream* aStream, MediaStreamGraph* aGraph); - already_AddRefed CreateAudioTrack(AudioStreamTrack* aStreamTrack); - already_AddRefed CreateVideoTrack(VideoStreamTrack* aStreamTrack); + + void CheckTracksAvailable(); // Called when MediaStreamGraph has finished an iteration where tracks were // created. - void TracksCreated(); + void NotifyTracksCreated(); - void CheckTracksAvailable(); + // Dispatches NotifyTrackAdded() to all registered track listeners. + void NotifyTrackAdded(const RefPtr& aTrack); + + // Dispatches NotifyTrackRemoved() to all registered track listeners. + void NotifyTrackRemoved(const RefPtr& aTrack); + + class OwnedStreamListener; + friend class OwnedStreamListener; class PlaybackStreamListener; friend class PlaybackStreamListener; @@ -464,7 +483,8 @@ protected: // MediaStreamTracks corresponding to tracks in our mPlaybackStream. nsAutoTArray, 2> mTracks; - RefPtr mListener; + RefPtr mOwnedListener; + RefPtr mPlaybackListener; nsTArray > mRunOnTracksAvailable; @@ -478,9 +498,8 @@ protected: bool mNotifiedOfMediaStreamGraphShutdown; - // Send notifications to AudioTrackList or VideoTrackList, if this MediaStream - // is consumed by a HTMLMediaElement. - nsTArray mMediaTrackListListeners; + // The track listeners subscribe to changes in this stream's track set. + nsTArray> mTrackListeners; private: void NotifyPrincipalChanged(); @@ -512,7 +531,7 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - virtual void Stop(); + void Stop(); virtual MediaEngineSource* GetMediaEngine(TrackID aTrackID) { return nullptr; } @@ -538,6 +557,8 @@ public: protected: virtual ~DOMLocalMediaStream(); + + void StopImpl(); }; NS_DEFINE_STATIC_IID_ACCESSOR(DOMLocalMediaStream, diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp index d728b1b621..8b1fc8e862 100644 --- a/dom/media/MediaManager.cpp +++ b/dom/media/MediaManager.cpp @@ -49,6 +49,7 @@ #include "Latency.h" #include "nsProxyRelease.h" #include "nsNullPrincipal.h" +#include "nsVariant.h" // For PR_snprintf #include "prprf.h" @@ -640,7 +641,7 @@ class nsDOMUserMediaStream : public DOMLocalMediaStream { public: static already_AddRefed - CreateTrackUnionStream(nsIDOMWindow* aWindow, + CreateSourceStream(nsIDOMWindow* aWindow, GetUserMediaCallbackMediaStreamListener* aListener, AudioDevice* aAudioDevice, VideoDevice* aVideoDevice, @@ -649,7 +650,7 @@ public: RefPtr stream = new nsDOMUserMediaStream(aListener, aAudioDevice, aVideoDevice); - stream->InitTrackUnionStream(aWindow, aMSG); + stream->InitSourceStream(aWindow, aMSG); return stream.forget(); } @@ -676,20 +677,10 @@ public: virtual ~nsDOMUserMediaStream() { - Stop(); + StopImpl(); - if (mPort) { - mPort->Destroy(); - } - if (mSourceStream) { - mSourceStream->Destroy(); - } - } - - virtual void Stop() override - { - if (mSourceStream) { - mSourceStream->EndAllTrackAndFinish(); + if (GetSourceStream()) { + GetSourceStream()->Destroy(); } } @@ -699,8 +690,8 @@ public: // XXX This will not handle more complex cases well. virtual void StopTrack(TrackID aTrackID) override { - if (mSourceStream) { - mSourceStream->EndTrack(aTrackID); + if (GetSourceStream()) { + GetSourceStream()->EndTrack(aTrackID); // We could override NotifyMediaStreamTrackEnded(), and maybe should, but it's // risky to do late in a release since that will affect all track ends, and not // just StopTrack()s. @@ -729,7 +720,7 @@ public: promise->MaybeReject(error); return promise.forget(); } - if (!mSourceStream) { + if (!GetSourceStream()) { RefPtr error = new MediaStreamError(window, NS_LITERAL_STRING("InternalError"), NS_LITERAL_STRING("No stream.")); @@ -777,8 +768,8 @@ public: // Allow getUserMedia to pass input data directly to PeerConnection/MediaPipeline virtual bool AddDirectListener(MediaStreamDirectListener *aListener) override { - if (mSourceStream) { - mSourceStream->AddDirectListener(aListener); + if (GetSourceStream()) { + GetSourceStream()->AddDirectListener(aListener); return true; // application should ignore NotifyQueuedTrackData } return false; @@ -801,8 +792,8 @@ public: virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) override { - if (mSourceStream) { - mSourceStream->RemoveDirectListener(aListener); + if (GetSourceStream()) { + GetSourceStream()->RemoveDirectListener(aListener); } } @@ -836,10 +827,14 @@ public: return nullptr; } - // The actual MediaStream is a TrackUnionStream. But these resources need to be - // explicitly destroyed too. - RefPtr mSourceStream; - RefPtr mPort; + SourceMediaStream* GetSourceStream() + { + if (GetInputStream()) { + return GetInputStream()->AsSourceStream(); + } + return nullptr; + } + RefPtr mListener; RefPtr mAudioDevice; // so we can turn on AEC RefPtr mVideoDevice; @@ -1002,9 +997,8 @@ public: MediaStreamGraph::GetInstance(graphDriverType, dom::AudioChannel::Normal); - RefPtr stream = msg->CreateSourceStream(nullptr); - RefPtr domStream; + RefPtr stream; // AudioCapture is a special case, here, in the sense that we're not really // using the audio source and the SourceMediaStream, which acts as // placeholders. We re-route a number of stream internaly in the MSG and mix @@ -1015,38 +1009,33 @@ public: // It should be possible to pipe the capture stream to anything. CORS is // not a problem here, we got explicit user content. domStream->SetPrincipal(window->GetExtantDoc()->NodePrincipal()); + stream = msg->CreateSourceStream(nullptr); // Placeholder msg->RegisterCaptureStreamForWindow( mWindowID, domStream->GetInputStream()->AsProcessedStream()); window->SetAudioCapture(true); } else { // Normal case, connect the source stream to the track union stream to // avoid us blocking - RefPtr trackunion = - nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener, - mAudioDevice, mVideoDevice, - msg); - trackunion->GetInputStream()->AsProcessedStream()->SetAutofinish(true); - RefPtr port = trackunion->GetInputStream()->AsProcessedStream()-> - AllocateInputPort(stream); - trackunion->mSourceStream = stream; - trackunion->mPort = port.forget(); - // Log the relationship between SourceMediaStream and TrackUnion stream - // Make sure logger starts before capture - AsyncLatencyLogger::Get(true); - LogLatency(AsyncLatencyLogger::MediaStreamCreate, - reinterpret_cast(stream.get()), - reinterpret_cast(trackunion->GetInputStream())); + domStream = nsDOMUserMediaStream::CreateSourceStream(window, mListener, + mAudioDevice, mVideoDevice, + msg); + + if (mAudioDevice) { + domStream->CreateOwnDOMTrack(kAudioTrack, MediaSegment::AUDIO); + } + if (mVideoDevice) { + domStream->CreateOwnDOMTrack(kVideoTrack, MediaSegment::VIDEO); + } nsCOMPtr principal; if (mPeerIdentity) { principal = nsNullPrincipal::Create(); - trackunion->SetPeerIdentity(mPeerIdentity.forget()); + domStream->SetPeerIdentity(mPeerIdentity.forget()); } else { principal = window->GetExtantDoc()->NodePrincipal(); } - trackunion->CombineWithPrincipal(principal); - - domStream = trackunion.forget(); + domStream->CombineWithPrincipal(principal); + stream = domStream->GetInputStream()->AsSourceStream(); } if (!domStream || sInShutdown) { @@ -1068,6 +1057,7 @@ public: // Activate our listener. We'll call Start() on the source when get a callback // that the MediaStream has started consuming. The listener is freed // when the page is invalidated (on navigation or close). + MOZ_ASSERT(stream); mListener->Activate(stream.forget(), mAudioDevice, mVideoDevice); // Note: includes JS callbacks; must be released on MainThread @@ -1081,7 +1071,7 @@ public: // Dispatch to the media thread to ask it to start the sources, // because that can take a while. - // Pass ownership of trackunion to the MediaOperationTask + // Pass ownership of domStream to the MediaOperationTask // to ensure it's kept alive until the MediaOperationTask runs (at least). MediaManager::PostTask(FROM_HERE, new MediaOperationTask(MEDIA_START, mListener, domStream, @@ -1509,6 +1499,7 @@ MediaManager::MediaManager() : mMediaThread(nullptr) , mMutex("mozilla::MediaManager") , mBackend(nullptr) { + mPrefs.mFreq = 1000; // 1KHz test tone mPrefs.mWidth = 0; // adaptive default mPrefs.mHeight = 0; // adaptive default mPrefs.mFPS = MediaEngine::DEFAULT_VIDEO_FPS; @@ -1521,8 +1512,8 @@ MediaManager::MediaManager() GetPrefs(branch, nullptr); } } - LOG(("%s: default prefs: %dx%d @%dfps (min %d)", __FUNCTION__, - mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS)); + LOG(("%s: default prefs: %dx%d @%dfps (min %d), %dHz test tones", __FUNCTION__, + mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS, mPrefs.mFreq)); } NS_IMPL_ISUPPORTS(MediaManager, nsIMediaManagerService, nsIObserver) @@ -1586,6 +1577,38 @@ MediaManager::Get() { prefs->AddObserver("media.navigator.video.default_fps", sSingleton, false); prefs->AddObserver("media.navigator.video.default_minfps", sSingleton, false); } + + // Prepare async shutdown + + nsCOMPtr profileBeforeChange; + { + nsCOMPtr svc = services::GetAsyncShutdown(); + MOZ_RELEASE_ASSERT(svc); + nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + } + + class Blocker : public media::ShutdownBlocker + { + public: + Blocker() + : media::ShutdownBlocker(NS_LITERAL_STRING( + "Media shutdown: blocking on media thread")) {} + + NS_IMETHOD BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override + { + MOZ_RELEASE_ASSERT(MediaManager::GetIfExists()); + MediaManager::GetIfExists()->Shutdown(); + return NS_OK; + } + }; + + sSingleton->mShutdownBlocker = new Blocker(); + nsresult rv = profileBeforeChange->AddBlocker(sSingleton->mShutdownBlocker, + NS_LITERAL_STRING(__FILE__), + __LINE__, + NS_LITERAL_STRING("Media shutdown")); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); #ifdef MOZ_B2G // Init MediaPermissionManager before sending out any permission requests. (void) MediaPermissionManager::GetInstance(); @@ -2173,7 +2196,7 @@ MediaManager::AnonymizeId(nsAString& aId, const nsACString& aOriginKey) already_AddRefed MediaManager::ToJSArray(SourceSet& aDevices) { - nsCOMPtr var = do_CreateInstance("@mozilla.org/variant;1"); + RefPtr var = new nsVariant(); size_t len = aDevices.Length(); if (len) { nsTArray tmp(len); @@ -2498,6 +2521,115 @@ MediaManager::GetPrefs(nsIPrefBranch *aBranch, const char *aData) GetPref(aBranch, "media.navigator.video.default_height", aData, &mPrefs.mHeight); GetPref(aBranch, "media.navigator.video.default_fps", aData, &mPrefs.mFPS); GetPref(aBranch, "media.navigator.video.default_minfps", aData, &mPrefs.mMinFPS); + GetPref(aBranch, "media.navigator.audio.fake_frequency", aData, &mPrefs.mFreq); +} + +void +MediaManager::Shutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (sInShutdown) { + return; + } + sInShutdown = true; + + nsCOMPtr obs = services::GetObserverService(); + + obs->RemoveObserver(this, "xpcom-will-shutdown"); + obs->RemoveObserver(this, "last-pb-context-exited"); + obs->RemoveObserver(this, "getUserMedia:privileged:allow"); + obs->RemoveObserver(this, "getUserMedia:response:allow"); + obs->RemoveObserver(this, "getUserMedia:response:deny"); + obs->RemoveObserver(this, "getUserMedia:revoke"); + + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + prefs->RemoveObserver("media.navigator.video.default_width", this); + prefs->RemoveObserver("media.navigator.video.default_height", this); + prefs->RemoveObserver("media.navigator.video.default_fps", this); + prefs->RemoveObserver("media.navigator.video.default_minfps", this); + prefs->RemoveObserver("media.navigator.audio.fake_frequency", this); + } + + // Close off any remaining active windows. + GetActiveWindows()->Clear(); + mActiveCallbacks.Clear(); + mCallIds.Clear(); + { + MutexAutoLock lock(mMutex); + if (mBackend) { + mBackend->Shutdown(); // ok to invoke multiple times + } + } + + // Because mMediaThread is not an nsThread, we must dispatch to it so it can + // clean up BackgroundChild. Continue stopping thread once this is done. + + class ShutdownTask : public Task + { + public: + ShutdownTask(already_AddRefed aBackend, + nsRunnable* aReply) + : mReply(aReply) + , mBackend(aBackend) {} + private: + virtual void + Run() + { + LOG(("MediaManager Thread Shutdown")); + MOZ_ASSERT(MediaManager::IsInMediaThread()); + mozilla::ipc::BackgroundChild::CloseForCurrentThread(); + // must explicitly do this before dispatching the reply, since the reply may kill us with Stop() + mBackend = nullptr; // last reference, will invoke Shutdown() again + + if (NS_FAILED(NS_DispatchToMainThread(mReply.forget()))) { + LOG(("Will leak thread: DispatchToMainthread of reply runnable failed in MediaManager shutdown")); + } + } + RefPtr mReply; + RefPtr mBackend; + }; + + // Post ShutdownTask to execute on mMediaThread and pass in a lambda + // callback to be executed back on this thread once it is done. + // + // The lambda callback "captures" the 'this' pointer for member access. + // This is safe since this is guaranteed to be here since sSingleton isn't + // cleared until the lambda function clears it. + + // note that this == sSingleton + RefPtr that(sSingleton); + // Release the backend (and call Shutdown()) from within the MediaManager thread + RefPtr temp; + { + MutexAutoLock lock(mMutex); + temp = mBackend.forget(); + } + // Don't use MediaManager::PostTask() because we're sInShutdown=true here! + mMediaThread->message_loop()->PostTask(FROM_HERE, new ShutdownTask( + temp.forget(), + media::NewRunnableFrom([this, that]() mutable { + LOG(("MediaManager shutdown lambda running, releasing MediaManager singleton and thread")); + if (mMediaThread) { + mMediaThread->Stop(); + } + + // Remove async shutdown blocker + + nsCOMPtr profileBeforeChange; + { + nsCOMPtr svc = services::GetAsyncShutdown(); + MOZ_RELEASE_ASSERT(svc); + nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(profileBeforeChange)); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + } + profileBeforeChange->RemoveBlocker(sSingleton->mShutdownBlocker); + + // we hold a ref to 'that' which is the same as sSingleton + sSingleton = nullptr; + + return NS_OK; + }))); } nsresult @@ -2505,7 +2637,6 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(NS_IsMainThread()); - nsCOMPtr obs = services::GetObserverService(); if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { nsCOMPtr branch( do_QueryInterface(aSubject) ); @@ -2515,92 +2646,8 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, mPrefs.mWidth, mPrefs.mHeight, mPrefs.mFPS, mPrefs.mMinFPS)); } } else if (!strcmp(aTopic, "xpcom-will-shutdown")) { - sInShutdown = true; - - obs->RemoveObserver(this, "xpcom-will-shutdown"); - obs->RemoveObserver(this, "last-pb-context-exited"); - obs->RemoveObserver(this, "getUserMedia:privileged:allow"); - obs->RemoveObserver(this, "getUserMedia:response:allow"); - obs->RemoveObserver(this, "getUserMedia:response:deny"); - obs->RemoveObserver(this, "getUserMedia:revoke"); - - nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); - if (prefs) { - prefs->RemoveObserver("media.navigator.video.default_width", this); - prefs->RemoveObserver("media.navigator.video.default_height", this); - prefs->RemoveObserver("media.navigator.video.default_fps", this); - prefs->RemoveObserver("media.navigator.video.default_minfps", this); - } - - // Close off any remaining active windows. - GetActiveWindows()->Clear(); - mActiveCallbacks.Clear(); - mCallIds.Clear(); - { - MutexAutoLock lock(mMutex); - if (mBackend) { - mBackend->Shutdown(); // ok to invoke multiple times - } - } - - // Because mMediaThread is not an nsThread, we must dispatch to it so it can - // clean up BackgroundChild. Continue stopping thread once this is done. - - class ShutdownTask : public Task - { - public: - ShutdownTask(already_AddRefed aBackend, - nsRunnable* aReply) - : mReply(aReply) - , mBackend(aBackend) {} - private: - virtual void - Run() - { - LOG(("MediaManager Thread Shutdown")); - MOZ_ASSERT(MediaManager::IsInMediaThread()); - mozilla::ipc::BackgroundChild::CloseForCurrentThread(); - // must explicitly do this before dispatching the reply, since the reply may kill us with Stop() - mBackend = nullptr; // last reference, will invoke Shutdown() again - - if (NS_FAILED(NS_DispatchToMainThread(mReply.forget()))) { - LOG(("Will leak thread: DispatchToMainthread of reply runnable failed in MediaManager shutdown")); - } - } - RefPtr mReply; - RefPtr mBackend; - }; - - // Post ShutdownTask to execute on mMediaThread and pass in a lambda - // callback to be executed back on this thread once it is done. - // - // The lambda callback "captures" the 'this' pointer for member access. - // This is safe since this is guaranteed to be here since sSingleton isn't - // cleared until the lambda function clears it. - - // note that this == sSingleton - RefPtr that(sSingleton); - // Release the backend (and call Shutdown()) from within the MediaManager thread - RefPtr temp; - { - MutexAutoLock lock(mMutex); - temp = mBackend.forget(); - } - // Don't use MediaManager::PostTask() because we're sInShutdown=true here! - mMediaThread->message_loop()->PostTask(FROM_HERE, new ShutdownTask( - temp.forget(), - media::NewRunnableFrom([this, that]() mutable { - LOG(("MediaManager shutdown lambda running, releasing MediaManager singleton and thread")); - if (mMediaThread) { - mMediaThread->Stop(); - } - // we hold a ref to 'that' which is the same as sSingleton - sSingleton = nullptr; - - return NS_OK; - }))); + Shutdown(); return NS_OK; - } else if (!strcmp(aTopic, "last-pb-context-exited")) { // Clear memory of private-browsing-specific deviceIds. Fire and forget. media::SanitizeOriginKeys(0, true); @@ -2715,27 +2762,35 @@ MediaManager::Observe(nsISupports* aSubject, const char* aTopic, return NS_OK; } -static PLDHashOperator -WindowsHashToArrayFunc (const uint64_t& aId, - StreamListeners* aData, - void *userArg) +nsresult +MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray** aArray) { - nsISupportsArray *array = - static_cast(userArg); - nsPIDOMWindow *window = static_cast - (nsGlobalWindow::GetInnerWindowWithId(aId)); + MOZ_ASSERT(aArray); + nsISupportsArray* array; + nsresult rv = NS_NewISupportsArray(&array); // AddRefs + if (NS_FAILED(rv)) { + return rv; + } - MOZ_ASSERT(window); - if (window) { + for (auto iter = mActiveWindows.Iter(); !iter.Done(); iter.Next()) { + const uint64_t& id = iter.Key(); + StreamListeners* listeners = iter.UserData(); + + nsPIDOMWindow* window = static_cast + (nsGlobalWindow::GetInnerWindowWithId(id)); + MOZ_ASSERT(window); + if (!window) { + continue; + } // mActiveWindows contains both windows that have requested device // access and windows that are currently capturing media. We want // to return only the latter. See bug 975177. bool capturing = false; - if (aData) { - uint32_t length = aData->Length(); + if (listeners) { + uint32_t length = listeners->Length(); for (uint32_t i = 0; i < length; ++i) { RefPtr listener = - aData->ElementAt(i); + listeners->ElementAt(i); if (listener->CapturingVideo() || listener->CapturingAudio() || listener->CapturingScreen() || listener->CapturingWindow() || listener->CapturingApplication()) { @@ -2744,24 +2799,10 @@ WindowsHashToArrayFunc (const uint64_t& aId, } } } - - if (capturing) + if (capturing) { array->AppendElement(window); + } } - return PL_DHASH_NEXT; -} - - -nsresult -MediaManager::GetActiveMediaCaptureWindows(nsISupportsArray **aArray) -{ - MOZ_ASSERT(aArray); - nsISupportsArray *array; - nsresult rv = NS_NewISupportsArray(&array); // AddRefs - if (NS_FAILED(rv)) - return rv; - - mActiveWindows.EnumerateRead(WindowsHashToArrayFunc, array); *aArray = array; return NS_OK; diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h index e9c9cb2de0..2244643779 100644 --- a/dom/media/MediaManager.h +++ b/dom/media/MediaManager.h @@ -515,6 +515,7 @@ private: MediaManager(); ~MediaManager() {} + void Shutdown(); void StopScreensharing(uint64_t aWindowID); void IterateWindowListeners(nsPIDOMWindow *aWindow, @@ -530,6 +531,7 @@ private: // Always exists nsAutoPtr mMediaThread; + nsCOMPtr mShutdownBlocker; Mutex mMutex; // protected with mMutex: diff --git a/dom/media/MediaStreamGraph.cpp b/dom/media/MediaStreamGraph.cpp index d4b9f475ba..b57741511b 100644 --- a/dom/media/MediaStreamGraph.cpp +++ b/dom/media/MediaStreamGraph.cpp @@ -2697,26 +2697,16 @@ NS_IMPL_ISUPPORTS(MediaStreamGraphShutdownObserver, nsIObserver) static bool gShutdownObserverRegistered = false; -namespace { - -PLDHashOperator -ForceShutdownEnumerator(const uint32_t& /* aAudioChannel */, - MediaStreamGraphImpl* aGraph, - void* /* aUnused */) -{ - aGraph->ForceShutDown(); - return PL_DHASH_NEXT; -} - -} // namespace - NS_IMETHODIMP MediaStreamGraphShutdownObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) { if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { - gGraphs.EnumerateRead(ForceShutdownEnumerator, nullptr); + for (auto iter = gGraphs.Iter(); !iter.Done(); iter.Next()) { + MediaStreamGraphImpl* graph = iter.UserData(); + graph->ForceShutDown(); + } nsContentUtils::UnregisterShutdownObserver(this); gShutdownObserverRegistered = false; } diff --git a/dom/media/MediaStreamTrack.h b/dom/media/MediaStreamTrack.h index e81d2f3a2c..3feaa8ab3a 100644 --- a/dom/media/MediaStreamTrack.h +++ b/dom/media/MediaStreamTrack.h @@ -61,6 +61,7 @@ public: already_AddRefed ApplyConstraints(const dom::MediaTrackConstraints& aConstraints, ErrorResult &aRv); + bool Ended() const { return mEnded; } // Notifications from the MediaStreamGraph void NotifyEnded() { mEnded = true; } diff --git a/dom/media/MediaTrackList.cpp b/dom/media/MediaTrackList.cpp index 41fec4afe9..8fc36a7cb2 100644 --- a/dom/media/MediaTrackList.cpp +++ b/dom/media/MediaTrackList.cpp @@ -16,33 +16,6 @@ namespace mozilla { namespace dom { -void -MediaTrackListListener::NotifyMediaTrackCreated(MediaTrack* aTrack) -{ - if (!mMediaTrackList && !aTrack) { - return; - } - - if (aTrack->AsAudioTrack() && mMediaTrackList->AsAudioTrackList()) { - mMediaTrackList->AddTrack(aTrack); - } else if (aTrack->AsVideoTrack() && mMediaTrackList->AsVideoTrackList()) { - mMediaTrackList->AddTrack(aTrack); - } -} - -void -MediaTrackListListener::NotifyMediaTrackEnded(const nsAString& aId) -{ - if (!mMediaTrackList) { - return; - } - - const RefPtr track = mMediaTrackList->GetTrackById(aId); - if (track) { - mMediaTrackList->RemoveTrack(track); - } -} - MediaTrackList::MediaTrackList(nsPIDOMWindow* aOwnerWindow, HTMLMediaElement* aMediaElement) : DOMEventTargetHelper(aOwnerWindow) diff --git a/dom/media/MediaTrackList.h b/dom/media/MediaTrackList.h index 5578bd675c..d06e1bbc8d 100644 --- a/dom/media/MediaTrackList.h +++ b/dom/media/MediaTrackList.h @@ -20,41 +20,6 @@ class AudioTrackList; class VideoTrackList; class AudioTrack; class VideoTrack; -class MediaTrackList; - -/** - * This is for the media resource to notify its audio track and video track, - * when a media-resource-specific track has ended, or whether it has enabled or - * not. All notification methods are called from the main thread. - */ -class MediaTrackListListener -{ -public: - friend class mozilla::DOMMediaStream; - - explicit MediaTrackListListener(MediaTrackList* aMediaTrackList) - : mMediaTrackList(aMediaTrackList) {}; - - ~MediaTrackListListener() - { - mMediaTrackList = nullptr; - }; - - // Notify mMediaTrackList that a track has created by the media resource, - // and this corresponding MediaTrack object should be added into - // mMediaTrackList, and fires a addtrack event. - void NotifyMediaTrackCreated(MediaTrack* aTrack); - - // Notify mMediaTrackList that a track has ended by the media resource, - // and this corresponding MediaTrack object should be removed from - // mMediaTrackList, and fires a removetrack event. - void NotifyMediaTrackEnded(const nsAString& aId); - -protected: - // A weak reference to a MediaTrackList object, its lifetime managed by its - // owner. - MediaTrackList* mMediaTrackList; -}; /** * Base class of AudioTrackList and VideoTrackList. The AudioTrackList and @@ -120,7 +85,6 @@ public: IMPL_EVENT_HANDLER(addtrack) IMPL_EVENT_HANDLER(removetrack) - friend class MediaTrackListListener; friend class AudioTrack; friend class VideoTrack; diff --git a/dom/media/TrackUnionStream.cpp b/dom/media/TrackUnionStream.cpp index e23a8bcd6a..8310d22ac3 100644 --- a/dom/media/TrackUnionStream.cpp +++ b/dom/media/TrackUnionStream.cpp @@ -55,8 +55,10 @@ TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) : void TrackUnionStream::RemoveInput(MediaInputPort* aPort) { + STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing input %p", this, aPort)); for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) { if (mTrackMap[i].mInputPort == aPort) { + STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p removing trackmap entry %d", this, i)); EndTrack(i); mTrackMap.RemoveElementAt(i); } @@ -218,6 +220,7 @@ TrackUnionStream::TrackUnionStream(DOMMediaStream* aWrapper) : StreamBuffer::Track* outputTrack = mBuffer.FindTrack(mTrackMap[aIndex].mOutputTrackID); if (!outputTrack || outputTrack->IsEnded()) return; + STREAM_LOG(LogLevel::Debug, ("TrackUnionStream %p ending track %d", this, outputTrack->GetID())); for (uint32_t j = 0; j < mListeners.Length(); ++j) { MediaStreamListener* l = mListeners[j]; StreamTime offset = outputTrack->GetSegment()->GetDuration(); diff --git a/dom/media/gmp/GMPServiceChild.cpp b/dom/media/gmp/GMPServiceChild.cpp index e5f48194ad..64f96c49f7 100644 --- a/dom/media/gmp/GMPServiceChild.cpp +++ b/dom/media/gmp/GMPServiceChild.cpp @@ -295,18 +295,14 @@ GMPServiceChild::RemoveGMPContentParent(GMPContentParent* aGMPContentParent) } } -static PLDHashOperator -FillProcessIDArray(const uint64_t& aKey, GMPContentParent*, void* aUserArg) -{ - static_cast*>(aUserArg)->AppendElement(aKey); - return PL_DHASH_NEXT; -} - void GMPServiceChild::GetAlreadyBridgedTo(nsTArray& aAlreadyBridgedTo) { aAlreadyBridgedTo.SetCapacity(mContentParents.Count()); - mContentParents.EnumerateRead(FillProcessIDArray, &aAlreadyBridgedTo); + for (auto iter = mContentParents.Iter(); !iter.Done(); iter.Next()) { + const uint64_t& id = iter.Key(); + aAlreadyBridgedTo.AppendElement(id); + } } class OpenPGMPServiceChild : public nsRunnable diff --git a/dom/media/systemservices/MediaParent.cpp b/dom/media/systemservices/MediaParent.cpp index 0a84186d82..b619b24e76 100644 --- a/dom/media/systemservices/MediaParent.cpp +++ b/dom/media/systemservices/MediaParent.cpp @@ -215,31 +215,6 @@ class OriginKeyStore : public nsISupports return NS_OK; } - static PLDHashOperator - HashWriter(const nsACString& aOrigin, OriginKey* aOriginKey, void *aUserArg) - { - auto* stream = static_cast(aUserArg); - - if (!aOriginKey->mSecondsStamp) { - return PL_DHASH_NEXT; // don't write temporal ones - } - - nsCString buffer; - buffer.Append(aOriginKey->mKey); - buffer.Append(' '); - buffer.AppendInt(aOriginKey->mSecondsStamp); - buffer.Append(' '); - buffer.Append(aOrigin); - buffer.Append('\n'); - - uint32_t count; - nsresult rv = stream->Write(buffer.Data(), buffer.Length(), &count); - if (NS_WARN_IF(NS_FAILED(rv)) || count != buffer.Length()) { - return PL_DHASH_STOP; - } - return PL_DHASH_NEXT; - } - nsresult Write() { @@ -265,7 +240,27 @@ class OriginKeyStore : public nsISupports if (count != buffer.Length()) { return NS_ERROR_UNEXPECTED; } - mKeys.EnumerateRead(HashWriter, stream.get()); + for (auto iter = mKeys.Iter(); !iter.Done(); iter.Next()) { + const nsACString& origin = iter.Key(); + OriginKey* originKey = iter.UserData(); + + if (!originKey->mSecondsStamp) { + continue; // don't write temporal ones + } + nsCString buffer; + buffer.Append(originKey->mKey); + buffer.Append(' '); + buffer.AppendInt(originKey->mSecondsStamp); + buffer.Append(' '); + buffer.Append(origin); + buffer.Append('\n'); + + uint32_t count; + nsresult rv = stream->Write(buffer.Data(), buffer.Length(), &count); + if (NS_WARN_IF(NS_FAILED(rv)) || count != buffer.Length()) { + break; + } + } nsCOMPtr safeStream = do_QueryInterface(stream); MOZ_ASSERT(safeStream); diff --git a/dom/media/systemservices/MediaUtils.cpp b/dom/media/systemservices/MediaUtils.cpp index 17fb500efb..a77e3404ab 100644 --- a/dom/media/systemservices/MediaUtils.cpp +++ b/dom/media/systemservices/MediaUtils.cpp @@ -9,5 +9,7 @@ namespace mozilla { namespace media { -} -} +NS_IMPL_ISUPPORTS(ShutdownBlocker, nsIAsyncShutdownBlocker) + +} // namespace media +} // namespace mozilla diff --git a/dom/media/systemservices/MediaUtils.h b/dom/media/systemservices/MediaUtils.h index dd6cbfc534..d50472ffd6 100644 --- a/dom/media/systemservices/MediaUtils.h +++ b/dom/media/systemservices/MediaUtils.h @@ -9,6 +9,7 @@ #include "nsAutoPtr.h" #include "nsThreadUtils.h" +#include "nsIAsyncShutdown.h" namespace mozilla { namespace media { @@ -357,6 +358,35 @@ private: ~Refcountable>() {} }; +/* media::ShutdownBlocker - Async shutdown helper. + */ + +class ShutdownBlocker : public nsIAsyncShutdownBlocker +{ +public: + ShutdownBlocker(const nsString& aName) : mName(aName) {} + + NS_IMETHOD + BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override = 0; + + NS_IMETHOD GetName(nsAString& aName) override + { + aName = mName; + return NS_OK; + } + + NS_IMETHOD GetState(nsIPropertyBag**) override + { + return NS_OK; + } + + NS_DECL_ISUPPORTS +protected: + virtual ~ShutdownBlocker() {} +private: + const nsString mName; +}; + } // namespace media } // namespace mozilla diff --git a/dom/media/tests/mochitest/mediaStreamPlayback.js b/dom/media/tests/mochitest/mediaStreamPlayback.js index 5fea8877a6..058b89b7e9 100644 --- a/dom/media/tests/mochitest/mediaStreamPlayback.js +++ b/dom/media/tests/mochitest/mediaStreamPlayback.js @@ -173,10 +173,10 @@ LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype * @param {Boolean} isResume specifies if this media element is being resumed * from a previous run */ - playMediaWithStreamStop : { + playMediaWithDeprecatedStreamStop : { value: function(isResume) { return this.startMedia(isResume) - .then(() => this.stopStreamInMediaPlayback()) + .then(() => this.deprecatedStopStreamInMediaPlayback()) .then(() => this.stopMediaElement()); } }, @@ -189,7 +189,7 @@ LocalMediaStreamPlayback.prototype = Object.create(MediaStreamPlayback.prototype * being played. * */ - stopStreamInMediaPlayback : { + deprecatedStopStreamInMediaPlayback : { value: function () { return new Promise((resolve, reject) => { /** diff --git a/dom/media/tests/mochitest/test_getUserMedia_basicScreenshare.html b/dom/media/tests/mochitest/test_getUserMedia_basicScreenshare.html index 3611845142..ec3b4ad18a 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_basicScreenshare.html +++ b/dom/media/tests/mochitest/test_getUserMedia_basicScreenshare.html @@ -33,7 +33,7 @@ checkMediaStreamTracks(constraints, stream); var playback = new LocalMediaStreamPlayback(testVideo, stream); - return playback.playMediaWithStreamStop(false); + return playback.playMediaWithDeprecatedStreamStop(false); }); }); diff --git a/dom/media/tests/mochitest/test_getUserMedia_basicWindowshare.html b/dom/media/tests/mochitest/test_getUserMedia_basicWindowshare.html index 5b9c8b75b7..8e472a7146 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_basicWindowshare.html +++ b/dom/media/tests/mochitest/test_getUserMedia_basicWindowshare.html @@ -33,7 +33,7 @@ checkMediaStreamTracks(constraints, stream); var playback = new LocalMediaStreamPlayback(testVideo, stream); - return playback.playMediaWithStreamStop(false); + return playback.playMediaWithDeprecatedStreamStop(false); }); }); diff --git a/dom/media/tests/mochitest/test_getUserMedia_stopAudioStream.html b/dom/media/tests/mochitest/test_getUserMedia_stopAudioStream.html index bbb198501d..133e398d5d 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_stopAudioStream.html +++ b/dom/media/tests/mochitest/test_getUserMedia_stopAudioStream.html @@ -17,7 +17,7 @@ var testAudio = createMediaElement('audio', 'testAudio'); var streamPlayback = new LocalMediaStreamPlayback(testAudio, stream); - return streamPlayback.playMediaWithStreamStop(false); + return streamPlayback.playMediaWithDeprecatedStreamStop(false); }); }); diff --git a/dom/media/tests/mochitest/test_getUserMedia_stopAudioStreamWithFollowupAudio.html b/dom/media/tests/mochitest/test_getUserMedia_stopAudioStreamWithFollowupAudio.html index 094d0e7f38..32c03b258c 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_stopAudioStreamWithFollowupAudio.html +++ b/dom/media/tests/mochitest/test_getUserMedia_stopAudioStreamWithFollowupAudio.html @@ -18,7 +18,7 @@ var testAudio = createMediaElement('audio', 'testAudio'); var streamPlayback = new LocalMediaStreamPlayback(testAudio, firstStream); - return streamPlayback.playMediaWithStreamStop(false) + return streamPlayback.playMediaWithDeprecatedStreamStop(false) .then(() => getUserMedia({audio: true})) .then(secondStream => { streamPlayback.mediaStream = secondStream; diff --git a/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStream.html b/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStream.html index 3f024f202a..697fc9773e 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStream.html +++ b/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStream.html @@ -18,7 +18,7 @@ var testVideo = createMediaElement('video', 'testVideo'); var playback = new LocalMediaStreamPlayback(testVideo, stream); - return playback.playMediaWithStreamStop(false); + return playback.playMediaWithDeprecatedStreamStop(false); }); }); diff --git a/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html b/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html index 2296c3a676..c22985dc20 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html +++ b/dom/media/tests/mochitest/test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html @@ -21,7 +21,7 @@ var testVideo = createMediaElement('video', 'testVideo'); var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream); - return streamPlayback.playMediaWithStreamStop(false) + return streamPlayback.playMediaWithDeprecatedStreamStop(false) .then(() => getUserMedia({video: true, audio: true})) .then(secondStream => { streamPlayback.mediaStream = secondStream; diff --git a/dom/media/tests/mochitest/test_getUserMedia_stopVideoStream.html b/dom/media/tests/mochitest/test_getUserMedia_stopVideoStream.html index 037be76900..823e42a18c 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoStream.html +++ b/dom/media/tests/mochitest/test_getUserMedia_stopVideoStream.html @@ -18,7 +18,7 @@ var testVideo = createMediaElement('video', 'testVideo'); var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream); - return streamPlayback.playMediaWithStreamStop(false); + return streamPlayback.playMediaWithDeprecatedStreamStop(false); }); }); diff --git a/dom/media/tests/mochitest/test_getUserMedia_stopVideoStreamWithFollowupVideo.html b/dom/media/tests/mochitest/test_getUserMedia_stopVideoStreamWithFollowupVideo.html index a8aba8f2b2..c2c5591ffb 100644 --- a/dom/media/tests/mochitest/test_getUserMedia_stopVideoStreamWithFollowupVideo.html +++ b/dom/media/tests/mochitest/test_getUserMedia_stopVideoStreamWithFollowupVideo.html @@ -18,7 +18,7 @@ var testVideo = createMediaElement('video', 'testVideo'); var streamPlayback = new LocalMediaStreamPlayback(testVideo, stream); - return streamPlayback.playMediaWithStreamStop(false) + return streamPlayback.playMediaWithDeprecatedStreamStop(false) .then(() => getUserMedia({video: true})) .then(secondStream => { streamPlayback.mediaStream = secondStream; diff --git a/dom/media/webrtc/MediaEngine.h b/dom/media/webrtc/MediaEngine.h index 0e0fd870a8..85c7cb2880 100644 --- a/dom/media/webrtc/MediaEngine.h +++ b/dom/media/webrtc/MediaEngine.h @@ -197,6 +197,7 @@ public: int32_t mHeight; int32_t mFPS; int32_t mMinFPS; + int32_t mFreq; // for test tones (fake:true) // mWidth and/or mHeight may be zero (=adaptive default), so use functions. diff --git a/dom/media/webrtc/MediaEngineDefault.cpp b/dom/media/webrtc/MediaEngineDefault.cpp index aaefead47b..20bfb5ef98 100644 --- a/dom/media/webrtc/MediaEngineDefault.cpp +++ b/dom/media/webrtc/MediaEngineDefault.cpp @@ -304,15 +304,16 @@ class SineWaveGenerator { public: static const int bytesPerSample = 2; - static const int millisecondsPerSecond = 1000; - static const int frequency = 1000; + static const int millisecondsPerSecond = PR_MSEC_PER_SEC; - explicit SineWaveGenerator(int aSampleRate) : - mTotalLength(aSampleRate / frequency), + explicit SineWaveGenerator(uint32_t aSampleRate, uint32_t aFrequency) : + mTotalLength(aSampleRate / aFrequency), mReadLength(0) { - MOZ_ASSERT(mTotalLength * frequency == aSampleRate); + // If we allow arbitrary frequencies, there's no guarantee we won't get rounded here + // We could include an error term and adjust for it in generation; not worth the trouble + //MOZ_ASSERT(mTotalLength * aFrequency == aSampleRate); mAudioBuffer = new int16_t[mTotalLength]; - for(int i = 0; i < mTotalLength; i++) { + for (int i = 0; i < mTotalLength; i++) { // Set volume to -20db. It's from 32768.0 * 10^(-20/20) = 3276.8 mAudioBuffer[i] = (3276.8f * sin(2 * M_PI * i / mTotalLength)); } @@ -399,8 +400,9 @@ MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConst } mState = kAllocated; - // generate 1Khz sine wave - mSineGenerator = new SineWaveGenerator(AUDIO_RATE); + // generate sine wave (default 1KHz) + mSineGenerator = new SineWaveGenerator(AUDIO_RATE, + static_cast(aPrefs.mFreq ? aPrefs.mFreq : 1000)); return NS_OK; } diff --git a/dom/media/webrtc/MediaEngineWebRTC.cpp b/dom/media/webrtc/MediaEngineWebRTC.cpp index 9ab3b4ac4c..50af662fac 100644 --- a/dom/media/webrtc/MediaEngineWebRTC.cpp +++ b/dom/media/webrtc/MediaEngineWebRTC.cpp @@ -324,27 +324,6 @@ MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource, } } -static PLDHashOperator -ClearVideoSource (const nsAString&, // unused - MediaEngineVideoSource* aData, - void *userArg) -{ - if (aData) { - aData->Shutdown(); - } - return PL_DHASH_NEXT; -} - -static PLDHashOperator -ClearAudioSource(const nsAString &, // unused - MediaEngineAudioSource *aData, void *userArg) -{ - if (aData) { - aData->Shutdown(); - } - return PL_DHASH_NEXT; -} - void MediaEngineWebRTC::Shutdown() { @@ -354,8 +333,18 @@ MediaEngineWebRTC::Shutdown() LOG(("%s", __FUNCTION__)); // Shutdown all the sources, since we may have dangling references to the // sources in nsDOMUserMediaStreams waiting for GC/CC - mVideoSources.EnumerateRead(ClearVideoSource, nullptr); - mAudioSources.EnumerateRead(ClearAudioSource, nullptr); + for (auto iter = mVideoSources.Iter(); !iter.Done(); iter.Next()) { + MediaEngineVideoSource* source = iter.UserData(); + if (source) { + source->Shutdown(); + } + } + for (auto iter = mAudioSources.Iter(); !iter.Done(); iter.Next()) { + MediaEngineAudioSource* source = iter.UserData(); + if (source) { + source->Shutdown(); + } + } mVideoSources.Clear(); mAudioSources.Clear(); diff --git a/dom/media/webspeech/synth/SpeechSynthesis.cpp b/dom/media/webspeech/synth/SpeechSynthesis.cpp index f8960d06d6..1d98888411 100644 --- a/dom/media/webspeech/synth/SpeechSynthesis.cpp +++ b/dom/media/webspeech/synth/SpeechSynthesis.cpp @@ -32,14 +32,6 @@ GetSpeechSynthLog() namespace mozilla { namespace dom { -static PLDHashOperator -TraverseCachedVoices(const nsAString& aKey, SpeechSynthesisVoice* aEntry, void* aData) -{ - nsCycleCollectionTraversalCallback* cb = static_cast(aData); - cb->NoteXPCOMChild(aEntry); - return PL_DHASH_NEXT; -} - NS_IMPL_CYCLE_COLLECTION_CLASS(SpeechSynthesis) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SpeechSynthesis) @@ -55,7 +47,10 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SpeechSynthesis) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCurrentTask) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechQueue) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS - tmp->mVoiceCache.EnumerateRead(TraverseCachedVoices, &cb); + for (auto iter = tmp->mVoiceCache.Iter(); !iter.Done(); iter.Next()) { + SpeechSynthesisVoice* voice = iter.UserData(); + cb.NoteXPCOMChild(voice); + } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(SpeechSynthesis) diff --git a/dom/mobilemessage/DeletedMessageInfo.cpp b/dom/mobilemessage/DeletedMessageInfo.cpp index d9975f2d87..17b708a26b 100644 --- a/dom/mobilemessage/DeletedMessageInfo.cpp +++ b/dom/mobilemessage/DeletedMessageInfo.cpp @@ -69,10 +69,9 @@ DeletedMessageInfo::GetDeletedMessageIds(nsIVariant** aDeletedMessageIds) return NS_OK; } - nsresult rv; - mDeletedMessageIds = do_CreateInstance(NS_VARIANT_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); + mDeletedMessageIds = new nsVariant(); + nsresult rv; rv = mDeletedMessageIds->SetAsArray(nsIDataType::VTYPE_INT32, nullptr, length, @@ -103,10 +102,9 @@ DeletedMessageInfo::GetDeletedThreadIds(nsIVariant** aDeletedThreadIds) return NS_OK; } - nsresult rv; - mDeletedThreadIds = do_CreateInstance(NS_VARIANT_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); + mDeletedThreadIds = new nsVariant(); + nsresult rv; rv = mDeletedThreadIds->SetAsArray(nsIDataType::VTYPE_UINT64, nullptr, length, diff --git a/dom/storage/DOMStorageDBThread.cpp b/dom/storage/DOMStorageDBThread.cpp index 4bf233e8d8..4380667317 100644 --- a/dom/storage/DOMStorageDBThread.cpp +++ b/dom/storage/DOMStorageDBThread.cpp @@ -20,7 +20,7 @@ #include "mozIStorageValueArray.h" #include "mozIStorageFunction.h" #include "nsIObserverService.h" -#include "nsIVariant.h" +#include "nsVariant.h" #include "mozilla/IOInterposer.h" #include "mozilla/Services.h" @@ -403,10 +403,7 @@ nsReverseStringSQLFunction::OnFunctionCall( nsAutoCString result; ReverseString(stringToReverse, result); - nsCOMPtr outVar(do_CreateInstance( - NS_VARIANT_CONTRACTID, &rv)); - NS_ENSURE_SUCCESS(rv, rv); - + RefPtr outVar(new nsVariant()); rv = outVar->SetAsAUTF8String(result); NS_ENSURE_SUCCESS(rv, rv); diff --git a/dom/system/gonk/GonkGPSGeolocationProvider.cpp b/dom/system/gonk/GonkGPSGeolocationProvider.cpp index 0ff7c5557a..57f23b9c4f 100644 --- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp +++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp @@ -15,6 +15,7 @@ */ #include "GonkGPSGeolocationProvider.h" +#include "mozstumbler/MozStumbler.h" #include #include @@ -87,6 +88,72 @@ AGpsCallbacks GonkGPSGeolocationProvider::mAGPSCallbacks; AGpsRilCallbacks GonkGPSGeolocationProvider::mAGPSRILCallbacks; #endif // MOZ_B2G_RIL +double CalculateDeltaInMeter(double aLat, double aLon, double aLastLat, double aLastLon) +{ + // Use spherical law of cosines to calculate difference + // Not quite as correct as the Haversine but simpler and cheaper + const double radsInDeg = M_PI / 180.0; + const double rNewLat = aLat * radsInDeg; + const double rNewLon = aLon * radsInDeg; + const double rOldLat = aLastLat * radsInDeg; + const double rOldLon = aLastLon * radsInDeg; + // WGS84 equatorial radius of earth = 6378137m + double cosDelta = (sin(rNewLat) * sin(rOldLat)) + + (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon)); + if (cosDelta > 1.0) { + cosDelta = 1.0; + } else if (cosDelta < -1.0) { + cosDelta = -1.0; + } + return acos(cosDelta) * 6378137; +} + +class RequestCellInfoEvent : public nsRunnable { + public: + RequestCellInfoEvent(StumblerInfo *callback) + : mRequestCallback(callback) + {} + + NS_IMETHOD Run() { + MOZ_ASSERT(NS_IsMainThread()); + // Get Cell Info + nsCOMPtr service = + do_GetService(NS_MOBILE_CONNECTION_SERVICE_CONTRACTID); + + if (!service) { + nsContentUtils::LogMessageToConsole("Stumbler-can not get nsIMobileConnectionService \n"); + return NS_OK; + } + nsCOMPtr connection; + uint32_t numberOfRilServices = 1, cellInfoNum = 0; + + service->GetNumItems(&numberOfRilServices); + for (uint32_t rilNum = 0; rilNum < numberOfRilServices; rilNum++) { + service->GetItemByServiceId(rilNum /* Client Id */, getter_AddRefs(connection)); + if (!connection) { + nsContentUtils::LogMessageToConsole("Stumbler-can not get nsIMobileConnection by ServiceId %d \n", rilNum); + } else { + cellInfoNum++; + connection->GetCellInfoList(mRequestCallback); + } + } + mRequestCallback->SetCellInfoResponsesExpected(cellInfoNum); + + // Get Wifi AP Info + nsCOMPtr ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1"); + nsCOMPtr wifi = do_GetInterface(ir); + if (!wifi) { + mRequestCallback->SetWifiInfoResponseReceived(); + nsContentUtils::LogMessageToConsole("Stumbler-can not get nsIWifi interface\n"); + return NS_OK; + } + wifi->GetWifiScanResults(mRequestCallback); + return NS_OK; + } + private: + RefPtr mRequestCallback; +}; + void GonkGPSGeolocationProvider::LocationCallback(GpsLocation* location) { @@ -129,18 +196,122 @@ GonkGPSGeolocationProvider::LocationCallback(GpsLocation* location) // current time, most notably: the geolocation service which respects maximumAge // set in the DOM JS. + if (gDebug_isLoggingEnabled) { + nsContentUtils::LogMessageToConsole("geo: GPS got a fix (%f, %f). accuracy: %f", + location->latitude, + location->longitude, + location->accuracy); + } - NS_DispatchToMainThread(new UpdateLocationEvent(somewhere)); + RefPtr event = new UpdateLocationEvent(somewhere); + NS_DispatchToMainThread(event); + + const double kMinChangeInMeters = 30; + static int64_t lastTime_ms = 0; + static double sLastLat = 0; + static double sLastLon = 0; + double delta = -1.0; + int64_t timediff = (PR_Now() / PR_USEC_PER_MSEC) - lastTime_ms; + + if (0 != sLastLon || 0 != sLastLat) { + delta = CalculateDeltaInMeter(location->latitude, location->longitude, sLastLat, sLastLon); + } + if (gDebug_isLoggingEnabled) { + nsContentUtils::LogMessageToConsole("Stumbler-Location. [%f , %f] time_diff:%lld, delta : %f\n", + location->longitude, location->latitude, timediff, delta); + } + + // Consecutive GPS locations must be 30 meters and 3 seconds apart + if (lastTime_ms == 0 || ((timediff >= STUMBLE_INTERVAL_MS) && (delta > kMinChangeInMeters))){ + lastTime_ms = (PR_Now() / PR_USEC_PER_MSEC); + sLastLat = location->latitude; + sLastLon = location->longitude; + RefPtr requestCallback = new StumblerInfo(somewhere); + RefPtr runnable = new RequestCellInfoEvent(requestCallback); + NS_DispatchToMainThread(runnable); + } else { + if (gDebug_isLoggingEnabled) { + nsContentUtils::LogMessageToConsole( + "Stumbler-GPS locations less than 30 meters and 3 seconds. Ignore!\n"); + } + } } void GonkGPSGeolocationProvider::StatusCallback(GpsStatus* status) { + if (gDebug_isLoggingEnabled) { + switch (status->status) { + case GPS_STATUS_NONE: + nsContentUtils::LogMessageToConsole("geo: GPS_STATUS_NONE\n"); + break; + case GPS_STATUS_SESSION_BEGIN: + nsContentUtils::LogMessageToConsole("geo: GPS_STATUS_SESSION_BEGIN\n"); + break; + case GPS_STATUS_SESSION_END: + nsContentUtils::LogMessageToConsole("geo: GPS_STATUS_SESSION_END\n"); + break; + case GPS_STATUS_ENGINE_ON: + nsContentUtils::LogMessageToConsole("geo: GPS_STATUS_ENGINE_ON\n"); + break; + case GPS_STATUS_ENGINE_OFF: + nsContentUtils::LogMessageToConsole("geo: GPS_STATUS_ENGINE_OFF\n"); + break; + default: + nsContentUtils::LogMessageToConsole("geo: Unknown GPS status\n"); + break; + } + } } void GonkGPSGeolocationProvider::SvStatusCallback(GpsSvStatus* sv_info) { + if (gDebug_isLoggingEnabled) { + static int numSvs = 0; + static uint32_t numEphemeris = 0; + static uint32_t numAlmanac = 0; + static uint32_t numUsedInFix = 0; + + unsigned int i = 1; + uint32_t svAlmanacCount = 0; + for (i = 1; i > 0; i <<= 1) { + if (i & sv_info->almanac_mask) { + svAlmanacCount++; + } + } + + uint32_t svEphemerisCount = 0; + for (i = 1; i > 0; i <<= 1) { + if (i & sv_info->ephemeris_mask) { + svEphemerisCount++; + } + } + + uint32_t svUsedCount = 0; + for (i = 1; i > 0; i <<= 1) { + if (i & sv_info->used_in_fix_mask) { + svUsedCount++; + } + } + + // Log the message only if the the status changed. + if (sv_info->num_svs != numSvs || + svAlmanacCount != numAlmanac || + svEphemerisCount != numEphemeris || + svUsedCount != numUsedInFix) { + + nsContentUtils::LogMessageToConsole( + "geo: Number of SVs have (visibility, almanac, ephemeris): (%d, %d, %d)." + " %d of these SVs were used in fix.\n", + sv_info->num_svs, svAlmanacCount, svEphemerisCount, svUsedCount); + + numSvs = sv_info->num_svs; + numAlmanac = svAlmanacCount; + numEphemeris = svEphemerisCount; + numUsedInFix = svUsedCount; + } + } } void @@ -779,23 +950,7 @@ GonkGPSGeolocationProvider::NetworkLocationUpdate::Update(nsIDOMGeoPosition *pos static double sLastMLSPosLon = 0; if (0 != sLastMLSPosLon || 0 != sLastMLSPosLat) { - // Use spherical law of cosines to calculate difference - // Not quite as correct as the Haversine but simpler and cheaper - // Should the following be a utility function? Others might need this calc. - const double radsInDeg = M_PI / 180.0; - const double rNewLat = lat * radsInDeg; - const double rNewLon = lon * radsInDeg; - const double rOldLat = sLastMLSPosLat * radsInDeg; - const double rOldLon = sLastMLSPosLon * radsInDeg; - // WGS84 equatorial radius of earth = 6378137m - double cosDelta = (sin(rNewLat) * sin(rOldLat)) + - (cos(rNewLat) * cos(rOldLat) * cos(rOldLon - rNewLon)); - if (cosDelta > 1.0) { - cosDelta = 1.0; - } else if (cosDelta < -1.0) { - cosDelta = -1.0; - } - delta = acos(cosDelta) * 6378137; + delta = CalculateDeltaInMeter(lat, lon, sLastMLSPosLat, sLastMLSPosLon); } sLastMLSPosLat = lat; diff --git a/dom/system/gonk/moz.build b/dom/system/gonk/moz.build index ae74cdadf1..73efad2732 100644 --- a/dom/system/gonk/moz.build +++ b/dom/system/gonk/moz.build @@ -34,6 +34,7 @@ XPIDL_MODULE = 'dom_system_gonk' EXPORTS += [ 'GonkGPSGeolocationProvider.h', + 'mozstumbler/MozStumbler.h', 'nsVolume.h', 'nsVolumeService.h', ] @@ -46,6 +47,10 @@ UNIFIED_SOURCES += [ 'MozMtpDatabase.cpp', 'MozMtpServer.cpp', 'MozMtpStorage.cpp', + 'mozstumbler/MozStumbler.cpp', + 'mozstumbler/StumblerLogging.cpp', + 'mozstumbler/UploadStumbleRunnable.cpp', + 'mozstumbler/WriteStumbleOnThread.cpp', 'NetIdManager.cpp', 'NetworkUtils.cpp', 'NetworkWorker.cpp', diff --git a/dom/system/gonk/mozstumbler/MozStumbler.cpp b/dom/system/gonk/mozstumbler/MozStumbler.cpp new file mode 100644 index 0000000000..93a67c2176 --- /dev/null +++ b/dom/system/gonk/mozstumbler/MozStumbler.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MozStumbler.h" +#include "nsGeoPosition.h" +#include "nsPrintfCString.h" +#include "StumblerLogging.h" +#include "WriteStumbleOnThread.h" +#include "nsNetCID.h" +#include "nsDataHashtable.h" + +using namespace mozilla; +using namespace mozilla::dom; + + +NS_IMPL_ISUPPORTS(StumblerInfo, nsICellInfoListCallback, nsIWifiScanResultsReady) + +void +StumblerInfo::SetWifiInfoResponseReceived() +{ + mIsWifiInfoResponseReceived = true; + + if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) { + STUMBLER_DBG("Call DumpStumblerInfo from SetWifiInfoResponseReceived\n"); + DumpStumblerInfo(); + } +} + +void +StumblerInfo::SetCellInfoResponsesExpected(uint8_t count) +{ + mCellInfoResponsesExpected = count; + STUMBLER_DBG("SetCellInfoNum (%d)\n", count); + + if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) { + STUMBLER_DBG("Call DumpStumblerInfo from SetCellInfoResponsesExpected\n"); + DumpStumblerInfo(); + } +} + + +#define TEXT_LAT NS_LITERAL_CSTRING("latitude") +#define TEXT_LON NS_LITERAL_CSTRING("longitude") +#define TEXT_ACC NS_LITERAL_CSTRING("accuracy") +#define TEXT_ALT NS_LITERAL_CSTRING("altitude") +#define TEXT_ALTACC NS_LITERAL_CSTRING("altitudeAccuracy") +#define TEXT_HEAD NS_LITERAL_CSTRING("heading") +#define TEXT_SPD NS_LITERAL_CSTRING("speed") + +nsresult +StumblerInfo::LocationInfoToString(nsACString& aLocDesc) +{ + nsCOMPtr coords; + mPosition->GetCoords(getter_AddRefs(coords)); + if (!coords) { + return NS_ERROR_FAILURE; + } + + nsDataHashtable info; + + double val; + coords->GetLatitude(&val); + info.Put(TEXT_LAT, val); + coords->GetLongitude(&val); + info.Put(TEXT_LON, val); + coords->GetAccuracy(&val); + info.Put(TEXT_ACC, val); + coords->GetAltitude(&val); + info.Put(TEXT_ALT, val); + coords->GetAltitudeAccuracy(&val); + info.Put(TEXT_ALTACC, val); + coords->GetHeading(&val); + info.Put(TEXT_HEAD, val); + coords->GetSpeed(&val); + info.Put(TEXT_SPD, val); + + for (auto it = info.Iter(); !it.Done(); it.Next()) { + const nsACString& key = it.Key(); + val = it.UserData(); + if (!IsNaN(val)) { + aLocDesc += nsPrintfCString("\"%s\":%f,", key.BeginReading(), val); + } + } + + aLocDesc += nsPrintfCString("\"timestamp\":%lld,", PR_Now() / PR_USEC_PER_MSEC).get(); + return NS_OK; +} + +#define TEXT_RADIOTYPE NS_LITERAL_CSTRING("radioType") +#define TEXT_MCC NS_LITERAL_CSTRING("mobileCountryCode") +#define TEXT_MNC NS_LITERAL_CSTRING("mobileNetworkCode") +#define TEXT_LAC NS_LITERAL_CSTRING("locationAreaCode") +#define TEXT_CID NS_LITERAL_CSTRING("cellId") +#define TEXT_PSC NS_LITERAL_CSTRING("psc") +#define TEXT_STRENGTH_ASU NS_LITERAL_CSTRING("asu") +#define TEXT_STRENGTH_DBM NS_LITERAL_CSTRING("signalStrength") +#define TEXT_REGISTERED NS_LITERAL_CSTRING("serving") +#define TEXT_TIMEING_ADVANCE NS_LITERAL_CSTRING("timingAdvance") + +template void +ExtractCommonNonCDMACellInfoItems(nsCOMPtr& cell, nsDataHashtable& info) +{ + int32_t mcc, mnc, cid, sig; + + cell->GetMcc(&mcc); + cell->GetMnc(&mnc); + cell->GetCid(&cid); + cell->GetSignalStrength(&sig); + + info.Put(TEXT_MCC, mcc); + info.Put(TEXT_MNC, mnc); + info.Put(TEXT_CID, cid); + info.Put(TEXT_STRENGTH_ASU, sig); +} + +void +StumblerInfo::CellNetworkInfoToString(nsACString& aCellDesc) +{ + aCellDesc += "\"cellTowers\": ["; + + for (uint32_t idx = 0; idx < mCellInfo.Length() ; idx++) { + const char* radioType = 0; + int32_t type; + mCellInfo[idx]->GetType(&type); + bool registered; + mCellInfo[idx]->GetRegistered(®istered); + if (idx) { + aCellDesc += ",{"; + } else { + aCellDesc += "{"; + } + + STUMBLER_DBG("type=%d\n", type); + + nsDataHashtable info; + info.Put(TEXT_REGISTERED, registered); + + if(type == nsICellInfo::CELL_INFO_TYPE_GSM) { + radioType = "gsm"; + nsCOMPtr gsmCellInfo = do_QueryInterface(mCellInfo[idx]); + ExtractCommonNonCDMACellInfoItems(gsmCellInfo, info); + int32_t lac; + gsmCellInfo->GetLac(&lac); + info.Put(TEXT_LAC, lac); + } else if (type == nsICellInfo::CELL_INFO_TYPE_WCDMA) { + radioType = "wcdma"; + nsCOMPtr wcdmaCellInfo = do_QueryInterface(mCellInfo[idx]); + ExtractCommonNonCDMACellInfoItems(wcdmaCellInfo, info); + int32_t lac, psc; + wcdmaCellInfo->GetLac(&lac); + wcdmaCellInfo->GetPsc(&psc); + info.Put(TEXT_LAC, lac); + info.Put(TEXT_PSC, psc); + } else if (type == nsICellInfo::CELL_INFO_TYPE_CDMA) { + radioType = "cdma"; + nsCOMPtr cdmaCellInfo = do_QueryInterface(mCellInfo[idx]); + int32_t mnc, lac, cid, sig; + cdmaCellInfo->GetSystemId(&mnc); + cdmaCellInfo->GetNetworkId(&lac); + cdmaCellInfo->GetBaseStationId(&cid); + info.Put(TEXT_MNC, mnc); + info.Put(TEXT_LAC, lac); + info.Put(TEXT_CID, cid); + + cdmaCellInfo->GetEvdoDbm(&sig); + if (sig < 0 || sig == nsICellInfo::UNKNOWN_VALUE) { + cdmaCellInfo->GetCdmaDbm(&sig); + } + if (sig > -1 && sig != nsICellInfo::UNKNOWN_VALUE) { + sig *= -1; + info.Put(TEXT_STRENGTH_DBM, sig); + } + } else if (type == nsICellInfo::CELL_INFO_TYPE_LTE) { + radioType = "lte"; + nsCOMPtr lteCellInfo = do_QueryInterface(mCellInfo[idx]); + ExtractCommonNonCDMACellInfoItems(lteCellInfo, info); + int32_t lac, timingAdvance, pcid, rsrp; + lteCellInfo->GetTac(&lac); + lteCellInfo->GetTimingAdvance(&timingAdvance); + lteCellInfo->GetPcid(&pcid); + lteCellInfo->GetRsrp(&rsrp); + info.Put(TEXT_LAC, lac); + info.Put(TEXT_TIMEING_ADVANCE, timingAdvance); + info.Put(TEXT_PSC, pcid); + if (rsrp != nsICellInfo::UNKNOWN_VALUE) { + info.Put(TEXT_STRENGTH_DBM, rsrp * -1); + } + } + + aCellDesc += nsPrintfCString("\"%s\":\"%s\"", TEXT_RADIOTYPE.get(), radioType); + for (auto it = info.Iter(); !it.Done(); it.Next()) { + const nsACString& key = it.Key(); + int32_t value = it.UserData(); + if (value != nsICellInfo::UNKNOWN_VALUE) { + aCellDesc += nsPrintfCString(",\"%s\":%d", key.BeginReading(), value); + } + } + + aCellDesc += "}"; + } + aCellDesc += "]"; +} + +void +StumblerInfo::DumpStumblerInfo() +{ + if (!mIsWifiInfoResponseReceived || mCellInfoResponsesReceived != mCellInfoResponsesExpected) { + STUMBLER_DBG("CellInfoReceived=%d (Expected=%d), WifiInfoResponseReceived=%d\n", + mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived); + return; + } + mIsWifiInfoResponseReceived = false; + mCellInfoResponsesReceived = 0; + + nsAutoCString desc; + nsresult rv = LocationInfoToString(desc); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("LocationInfoToString failed, skip this dump"); + return; + } + + CellNetworkInfoToString(desc); + desc += mWifiDesc; + + STUMBLER_DBG("dispatch write event to thread\n"); + nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + nsCOMPtr event = new WriteStumbleOnThread(desc); + target->Dispatch(event, NS_DISPATCH_NORMAL); +} + +/* void notifyGetCellInfoList (in uint32_t count, [array, size_is (count)] in nsICellInfo result); */ +NS_IMETHODIMP +StumblerInfo::NotifyGetCellInfoList(uint32_t count, nsICellInfo** aCellInfos) +{ + MOZ_ASSERT(NS_IsMainThread()); + STUMBLER_DBG("There are %d cellinfo in the result\n", count); + + for (uint32_t i = 0; i < count; i++) { + mCellInfo.AppendElement(aCellInfos[i]); + } + mCellInfoResponsesReceived++; + DumpStumblerInfo(); + return NS_OK; +} + +/* void notifyGetCellInfoListFailed (in DOMString error); */ +NS_IMETHODIMP StumblerInfo::NotifyGetCellInfoListFailed(const nsAString& error) +{ + MOZ_ASSERT(NS_IsMainThread()); + mCellInfoResponsesReceived++; + STUMBLER_ERR("NotifyGetCellInfoListFailedm CellInfoReadyNum=%d, mCellInfoResponsesExpected=%d, mIsWifiInfoResponseReceived=%d", + mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived); + DumpStumblerInfo(); + return NS_OK; +} + +NS_IMETHODIMP +StumblerInfo::Onready(uint32_t count, nsIWifiScanResult** results) +{ + MOZ_ASSERT(NS_IsMainThread()); + STUMBLER_DBG("There are %d wifiAPinfo in the result\n",count); + + mWifiDesc += ",\"wifiAccessPoints\": ["; + bool firstItem = true; + for (uint32_t i = 0 ; i < count ; i++) { + nsString ssid; + results[i]->GetSsid(ssid); + if (ssid.IsEmpty()) { + STUMBLER_DBG("no ssid, skip this AP\n"); + continue; + } + + if (ssid.Length() >= 6) { + if (StringEndsWith(ssid, NS_LITERAL_STRING("_nomap"))) { + STUMBLER_DBG("end with _nomap. skip this AP(ssid :%s)\n", ssid.get()); + continue; + } + } + + if (firstItem) { + mWifiDesc += "{"; + firstItem = false; + } else { + mWifiDesc += ",{"; + } + + // mac address + nsString bssid; + results[i]->GetBssid(bssid); + // 00:00:00:00:00:00 --> 000000000000 + bssid.StripChars(":"); + mWifiDesc += "\"macAddress\":\""; + mWifiDesc += NS_ConvertUTF16toUTF8(bssid); + + uint32_t signal; + results[i]->GetSignalStrength(&signal); + mWifiDesc += "\",\"signalStrength\":"; + mWifiDesc.AppendInt(signal); + + mWifiDesc += "}"; + } + mWifiDesc += "]"; + + mIsWifiInfoResponseReceived = true; + DumpStumblerInfo(); + return NS_OK; +} + +NS_IMETHODIMP +StumblerInfo::Onfailure() +{ + MOZ_ASSERT(NS_IsMainThread()); + STUMBLER_ERR("GetWifiScanResults Onfailure\n"); + mIsWifiInfoResponseReceived = true; + DumpStumblerInfo(); + return NS_OK; +} + diff --git a/dom/system/gonk/mozstumbler/MozStumbler.h b/dom/system/gonk/mozstumbler/MozStumbler.h new file mode 100644 index 0000000000..880b365251 --- /dev/null +++ b/dom/system/gonk/mozstumbler/MozStumbler.h @@ -0,0 +1,45 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_system_mozstumbler_h__ +#define mozilla_system_mozstumbler_h__ + +#include "nsIDOMEventTarget.h" +#include "nsICellInfo.h" +#include "nsIWifi.h" + +#define STUMBLE_INTERVAL_MS 3000 + +class nsGeoPosition; + +class StumblerInfo final : public nsICellInfoListCallback, + public nsIWifiScanResultsReady +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICELLINFOLISTCALLBACK + NS_DECL_NSIWIFISCANRESULTSREADY + + explicit StumblerInfo(nsGeoPosition* position) + : mPosition(position), mCellInfoResponsesExpected(0), mCellInfoResponsesReceived(0), mIsWifiInfoResponseReceived(0) + {} + void SetWifiInfoResponseReceived(); + void SetCellInfoResponsesExpected(uint8_t count); + +private: + ~StumblerInfo() {} + void DumpStumblerInfo(); + nsresult LocationInfoToString(nsACString& aLocDesc); + void CellNetworkInfoToString(nsACString& aCellDesc); + nsTArray> mCellInfo; + nsCString mWifiDesc; + RefPtr mPosition; + int mCellInfoResponsesExpected; + int mCellInfoResponsesReceived; + bool mIsWifiInfoResponseReceived; +}; +#endif // mozilla_system_mozstumbler_h__ + diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.cpp b/dom/system/gonk/mozstumbler/StumblerLogging.cpp new file mode 100644 index 0000000000..f8968ec376 --- /dev/null +++ b/dom/system/gonk/mozstumbler/StumblerLogging.cpp @@ -0,0 +1,13 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "StumblerLogging.h" + +PRLogModuleInfo* GetLog() +{ + static PRLogModuleInfo* log = PR_NewLogModule("mozstumbler"); + return log; +} diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.h b/dom/system/gonk/mozstumbler/StumblerLogging.h new file mode 100644 index 0000000000..1a52b7553d --- /dev/null +++ b/dom/system/gonk/mozstumbler/StumblerLogging.h @@ -0,0 +1,18 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef STUMBLERLOGGING_H +#define STUMBLERLOGGING_H + +#include "mozilla/Logging.h" + +PRLogModuleInfo* GetLog(); + +#define STUMBLER_DBG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Debug, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__)) +#define STUMBLER_LOG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Info, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__)) +#define STUMBLER_ERR(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Error, ("STUMBLER -%s: " arg, __func__, ##__VA_ARGS__)) + +#endif diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp new file mode 100644 index 0000000000..404c9f540b --- /dev/null +++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "UploadStumbleRunnable.h" +#include "StumblerLogging.h" +#include "mozilla/dom/Event.h" +#include "nsIInputStream.h" +#include "nsIScriptSecurityManager.h" +#include "nsIURLFormatter.h" +#include "nsIXMLHttpRequest.h" +#include "nsNetUtil.h" +#include "nsVariant.h" + +UploadStumbleRunnable::UploadStumbleRunnable(nsIInputStream* aUploadData) +: mUploadInputStream(aUploadData) +{ +} + +NS_IMETHODIMP +UploadStumbleRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + nsresult rv = Upload(); + if (NS_FAILED(rv)) { + WriteStumbleOnThread::UploadEnded(false); + } + return NS_OK; +} + +nsresult +UploadStumbleRunnable::Upload() +{ + nsresult rv; + RefPtr variant = new nsVariant(); + + rv = variant->SetAsISupports(mUploadInputStream); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr xhr = do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr secman = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr systemPrincipal; + rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr formatter = + do_CreateInstance("@mozilla.org/toolkit/URLFormatterService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsString url; + rv = formatter->FormatURLPref(NS_LITERAL_STRING("geo.stumbler.url"), url); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xhr->Open(NS_LITERAL_CSTRING("POST"), NS_ConvertUTF16toUTF8(url), false, EmptyString(), EmptyString()); + NS_ENSURE_SUCCESS(rv, rv); + + xhr->SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), NS_LITERAL_CSTRING("gzip")); + xhr->SetMozBackgroundRequest(true); + // 60s timeout + xhr->SetTimeout(60 * 1000); + + nsCOMPtr target(do_QueryInterface(xhr)); + RefPtr listener = new UploadEventListener(xhr); + + const char* const sEventStrings[] = { + // nsIXMLHttpRequestEventTarget event types + "abort", + "error", + "load", + "timeout" + }; + + for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) { + nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]); + rv = target->AddEventListener(eventType, listener, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = xhr->Send(variant); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UploadEventListener, nsIDOMEventListener) + +UploadEventListener::UploadEventListener(nsIXMLHttpRequest* aXHR) +: mXHR(aXHR) +{ +} + +NS_IMETHODIMP +UploadEventListener::HandleEvent(nsIDOMEvent* aEvent) +{ + nsString type; + if (NS_FAILED(aEvent->GetType(type))) { + STUMBLER_ERR("Failed to get event type"); + WriteStumbleOnThread::UploadEnded(false); + return NS_ERROR_FAILURE; + } + + if (type.EqualsLiteral("load")) { + STUMBLER_DBG("Got load Event\n"); + } else if (type.EqualsLiteral("error") && mXHR) { + STUMBLER_ERR("Upload Error"); + } else { + STUMBLER_DBG("Receive %s Event", NS_ConvertUTF16toUTF8(type).get()); + } + + uint32_t statusCode = 0; + bool doDelete = false; + if (!mXHR) { + return NS_OK; + } + nsresult rv = mXHR->GetStatus(&statusCode); + if (NS_SUCCEEDED(rv)) { + STUMBLER_DBG("statuscode %d \n", statusCode); + } + + if (200 == statusCode || 400 == statusCode) { + doDelete = true; + } + + WriteStumbleOnThread::UploadEnded(doDelete); + nsCOMPtr target(do_QueryInterface(mXHR)); + + const char* const sEventStrings[] = { + // nsIXMLHttpRequestEventTarget event types + "abort", + "error", + "load", + "timeout" + }; + + for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) { + nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]); + rv = target->RemoveEventListener(eventType, this, false); + } + + mXHR = nullptr; + return NS_OK; +} diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h new file mode 100644 index 0000000000..a9ea83e37d --- /dev/null +++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h @@ -0,0 +1,46 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#ifndef UPLOADSTUMBLERUNNABLE_H +#define UPLOADSTUMBLERUNNABLE_H + +#include "nsIDOMEventListener.h" + +class nsIXMLHttpRequest; +class nsIInputStream; + +/* + This runnable is managed by WriteStumbleOnThread only, see that class + for how this is scheduled. + */ +class UploadStumbleRunnable final : public nsRunnable +{ +public: + explicit UploadStumbleRunnable(nsIInputStream* aUploadInputStream); + + NS_IMETHOD Run() override; +private: + virtual ~UploadStumbleRunnable() {} + nsCOMPtr mUploadInputStream; + nsresult Upload(); +}; + + +class UploadEventListener : public nsIDOMEventListener +{ +public: + UploadEventListener(nsIXMLHttpRequest* aXHR); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOMEVENTLISTENER + +protected: + virtual ~UploadEventListener() {} + nsCOMPtr mXHR; +}; + +#endif diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp new file mode 100644 index 0000000000..ef8593ed89 --- /dev/null +++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp @@ -0,0 +1,299 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WriteStumbleOnThread.h" +#include "StumblerLogging.h" +#include "UploadStumbleRunnable.h" +#include "nsDumpUtils.h" +#include "nsGZFileWriter.h" +#include "nsIFileStreams.h" +#include "nsIInputStream.h" +#include "nsPrintfCString.h" + +#define MAXFILESIZE_KB (15 * 1024) +#define ONEDAY_IN_MSEC (24 * 60 * 60 * 1000) +#define MAX_UPLOAD_ATTEMPTS 20 + +mozilla::Atomic WriteStumbleOnThread::sIsUploading(false); +mozilla::Atomic WriteStumbleOnThread::sIsAlreadyRunning(false); +WriteStumbleOnThread::UploadFreqGuard WriteStumbleOnThread::sUploadFreqGuard = {0}; + +#define FILENAME_INPROGRESS NS_LITERAL_CSTRING("stumbles.json.gz") +#define FILENAME_COMPLETED NS_LITERAL_CSTRING("stumbles.done.json.gz") +#define OUTPUT_DIR NS_LITERAL_CSTRING("mozstumbler") + +class DeleteRunnable : public nsRunnable +{ + public: + DeleteRunnable() {} + + NS_IMETHODIMP + Run() override + { + nsCOMPtr tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, + getter_AddRefs(tmpFile), + OUTPUT_DIR, + nsDumpUtils::CREATE); + if (NS_SUCCEEDED(rv)) { + tmpFile->Remove(true); + } + // critically, this sets this flag to false so writing can happen again + WriteStumbleOnThread::sIsUploading = false; + return NS_OK; + } + + private: + ~DeleteRunnable() {} +}; + +void +WriteStumbleOnThread::UploadEnded(bool deleteUploadFile) +{ + if (!deleteUploadFile) { + sIsUploading = false; + return; + } + + nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + nsCOMPtr event = new DeleteRunnable(); + target->Dispatch(event, NS_DISPATCH_NORMAL); +} + +void +WriteStumbleOnThread::WriteJSON(Partition aPart) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + nsCOMPtr tmpFile; + nsresult rv; + rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Open a file for stumble failed"); + return; + } + + RefPtr gzWriter = new nsGZFileWriter(nsGZFileWriter::Append); + rv = gzWriter->Init(tmpFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("gzWriter init failed"); + return; + } + + /* + The json format is like below. + {items:[ + {item}, + {item}, + {item} + ]} + */ + + // Need to add "]}" after the last item + if (aPart == Partition::End) { + gzWriter->Write("]}"); + rv = gzWriter->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream finish failed"); + } + + nsCOMPtr targetFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(targetFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + nsAutoString targetFilename; + rv = targetFile->GetLeafName(targetFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Get Filename failed"); + return; + } + rv = targetFile->Remove(true); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Remove File failed"); + return; + } + // Rename tmpfile + rv = tmpFile->MoveTo(/* directory */ nullptr, targetFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Rename File failed"); + return; + } + return; + } + + // Need to add "{items:[" before the first item + if (aPart == Partition::Begining) { + gzWriter->Write("{\"items\":[{"); + } else if (aPart == Partition::Middle) { + gzWriter->Write(",{"); + } + gzWriter->Write(mDesc.get()); + // one item is ended with '}' (e.g. {item}) + gzWriter->Write("}"); + rv = gzWriter->Finish(); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("ostream finish failed"); + } + + // check if it is the end of this file + int64_t fileSize = 0; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + return; + } + if (fileSize >= MAXFILESIZE_KB) { + WriteJSON(Partition::End); + return; + } +} + +WriteStumbleOnThread::Partition +WriteStumbleOnThread::GetWritePosition() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + nsCOMPtr tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("Open a file for stumble failed"); + return Partition::Unknown; + } + + int64_t fileSize = 0; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + return Partition::Unknown; + } + + if (fileSize == 0) { + return Partition::Begining; + } else if (fileSize >= MAXFILESIZE_KB) { + return Partition::End; + } else { + return Partition::Middle; + } +} + +NS_IMETHODIMP +WriteStumbleOnThread::Run() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + bool b = sIsAlreadyRunning.exchange(true); + if (b) { + return NS_OK; + } + + UploadFileStatus status = GetUploadFileStatus(); + + if (UploadFileStatus::NoFile != status) { + if (UploadFileStatus::ExistsAndReadyToUpload == status) { + Upload(); + } + } else { + Partition partition = GetWritePosition(); + if (partition == Partition::Unknown) { + STUMBLER_ERR("GetWritePosition failed, skip once"); + } else { + WriteJSON(partition); + } + } + + sIsAlreadyRunning = false; + return NS_OK; +} + + +/* + If the upload file exists, then check if it is one day old. + • if it is a day old -> ExistsAndReadyToUpload + • if it is less than the current day old -> Exists + • otherwise -> NoFile + + The Exists case means that the upload and the stumbling is rate limited + per-day to the size of the one file. + */ +WriteStumbleOnThread::UploadFileStatus +WriteStumbleOnThread::GetUploadFileStatus() +{ + nsCOMPtr tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + int64_t fileSize; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + return UploadFileStatus::NoFile; + } + if (fileSize <= 0) { + tmpFile->Remove(true); + return UploadFileStatus::NoFile; + } + + PRTime lastModifiedTime; + tmpFile->GetLastModifiedTime(&lastModifiedTime); + if ((PR_Now() / PR_USEC_PER_MSEC) - lastModifiedTime >= ONEDAY_IN_MSEC) { + return UploadFileStatus::ExistsAndReadyToUpload; + } + return UploadFileStatus::Exists; +} + +void +WriteStumbleOnThread::Upload() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + bool b = sIsUploading.exchange(true); + if (b) { + return; + } + + time_t seconds = time(0); + int day = seconds / (60 * 60 * 24); + + if (sUploadFreqGuard.daySinceEpoch < day) { + sUploadFreqGuard.daySinceEpoch = day; + sUploadFreqGuard.attempts = 0; + } + + sUploadFreqGuard.attempts++; + if (sUploadFreqGuard.attempts > MAX_UPLOAD_ATTEMPTS) { + STUMBLER_ERR("Too many upload attempts today"); + sIsUploading = false; + return; + } + + nsCOMPtr tmpFile; + nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile), + OUTPUT_DIR, nsDumpUtils::CREATE); + int64_t fileSize; + rv = tmpFile->GetFileSize(&fileSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + STUMBLER_ERR("GetFileSize failed"); + sIsUploading = false; + return; + } + + if (fileSize <= 0) { + sIsUploading = false; + return; + } + + // prepare json into nsIInputStream + nsCOMPtr inStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), tmpFile); + if (NS_FAILED(rv)) { + sIsUploading = false; + return; + } + + RefPtr uploader = new UploadStumbleRunnable(inStream); + NS_DispatchToMainThread(uploader); +} diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h new file mode 100644 index 0000000000..1e65ad08a6 --- /dev/null +++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h @@ -0,0 +1,84 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WriteStumbleOnThread_H +#define WriteStumbleOnThread_H + +#include "mozilla/Atomics.h" + +/* + This class is the entry point to stumbling, in that it + receives the location+cell+wifi string and writes it + to disk, or instead, it calls UploadStumbleRunnable + to upload the data. + + Writes will happen until the file is a max size, then stop. + Uploads will happen only when the file is one day old. + The purpose of these decisions is to have very simple rate-limiting + on the writes, as well as the uploads. + + There is only one file active; it is either being used for writing, + or for uploading. If the file is ready for uploading, no further + writes will take place until this file has been uploaded. + This can mean writing might not take place for days until the uploaded + file is processed. This is correct by-design. + + A notable limitation is that the upload is triggered by a location event, + this is used as an arbitrary and simple trigger. In future, there are + better events that can be used, such as detecting network activity. + + This thread is guarded so that only one instance is active (see the + mozilla::Atomics used for this). + */ +class WriteStumbleOnThread : public nsRunnable +{ +public: + explicit WriteStumbleOnThread(const nsCString& aDesc) + : mDesc(aDesc) + {} + + NS_IMETHODIMP Run() override; + + static void UploadEnded(bool deleteUploadFile); + // Don't write while uploading is happening + static mozilla::Atomic sIsUploading; + +private: + + enum class Partition { + Begining, + Middle, + End, + Unknown + }; + + enum class UploadFileStatus { + NoFile, Exists, ExistsAndReadyToUpload + }; + + ~WriteStumbleOnThread() {} + + Partition GetWritePosition(); + UploadFileStatus GetUploadFileStatus(); + void WriteJSON(Partition aPart); + void Upload(); + + nsCString mDesc; + + // Only run one instance of this + static mozilla::Atomic sIsAlreadyRunning; + + // Limit the upload attempts per day. If the device is rebooted + // this resets the allowed attempts, which is acceptable. + struct UploadFreqGuard { + int attempts; + int daySinceEpoch; + }; + static UploadFreqGuard sUploadFreqGuard; + +}; + +#endif diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 3eec7ebb73..0da5ceb871 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -871,6 +871,8 @@ var interfaceNamesInGlobalScope = "Notification", // IMPORTANT: Do not change this list without review from a DOM peer! "NotifyPaintEvent", +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "OffscreenCanvas", disabled: true}, // IMPORTANT: Do not change this list without review from a DOM peer! "OfflineAudioCompletionEvent", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/HTMLCanvasElement.webidl b/dom/webidl/HTMLCanvasElement.webidl index 693046f7a2..dde7841a5e 100644 --- a/dom/webidl/HTMLCanvasElement.webidl +++ b/dom/webidl/HTMLCanvasElement.webidl @@ -40,14 +40,19 @@ partial interface HTMLCanvasElement { File mozGetAsFile(DOMString name, optional DOMString? type = null); [ChromeOnly, Throws] nsISupports? MozGetIPCContext(DOMString contextId); - [ChromeOnly] - void mozFetchAsStream(nsIInputStreamCallback callback, optional DOMString? type = null); attribute PrintCallback? mozPrintCallback; [Throws, UnsafeInPrerendering, Pref="canvas.capturestream.enabled"] CanvasCaptureMediaStream captureStream(optional double frameRate); }; +// For OffscreenCanvas +// Reference: https://wiki.whatwg.org/wiki/OffscreenCanvas +partial interface HTMLCanvasElement { + [Pref="gfx.offscreencanvas.enabled", Throws] + OffscreenCanvas transferControlToOffscreen(); +}; + [ChromeOnly] interface MozCanvasPrintState { diff --git a/dom/webidl/MediaStream.webidl b/dom/webidl/MediaStream.webidl index 50ac83b989..a795ad0f54 100644 --- a/dom/webidl/MediaStream.webidl +++ b/dom/webidl/MediaStream.webidl @@ -27,17 +27,23 @@ dictionary MediaStreamConstraints { DOMString? peerIdentity = null; }; +// [Exposed=Window, +// Constructor, +// Constructor (MediaStream stream), +// Constructor (sequence tracks)] interface MediaStream : EventTarget { - readonly attribute DOMString id; - sequence getAudioTracks(); - sequence getVideoTracks(); - sequence getTracks(); - // MediaStreamTrack getTrackById (DOMString trackId); - // void addTrack (MediaStreamTrack track); - // void removeTrack (MediaStreamTrack track); - // attribute boolean ended; - // attribute EventHandler onended; - // attribute EventHandler onaddtrack; - // attribute EventHandler onremovetrack; - readonly attribute double currentTime; + readonly attribute DOMString id; + sequence getAudioTracks (); + sequence getVideoTracks (); + sequence getTracks (); + // MediaStreamTrack? getTrackById (DOMString trackId); + void addTrack (MediaStreamTrack track); + void removeTrack (MediaStreamTrack track); + // MediaStream clone (); + // readonly attribute boolean active; + // attribute EventHandler onactive; + // attribute EventHandler oninactive; + // attribute EventHandler onaddtrack; + // attribute EventHandler onremovetrack; + readonly attribute double currentTime; }; diff --git a/dom/webidl/OffscreenCanvas.webidl b/dom/webidl/OffscreenCanvas.webidl new file mode 100644 index 0000000000..592ac6e14f --- /dev/null +++ b/dom/webidl/OffscreenCanvas.webidl @@ -0,0 +1,28 @@ +/* -*- Mode: IDL; 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/. + * + * For more information on this interface, please see + * https://wiki.whatwg.org/wiki/OffscreenCanvas + * + * Current implementation focus on transfer canvas from main thread to worker. + * So there are some spec doesn't implement, such as [Constructor], toBlob() and + * transferToImageBitmap in OffscreenCanvas. Bug 1172796 will implement + * remaining spec. + */ + +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabled"] +interface OffscreenCanvas : EventTarget { + [Pure, SetterThrows] + attribute unsigned long width; + [Pure, SetterThrows] + attribute unsigned long height; + + [Throws] + nsISupports? getContext(DOMString contextId, + optional any contextOptions = null); +}; + +// OffscreenCanvas implements Transferable; diff --git a/dom/webidl/WebGLRenderingContext.webidl b/dom/webidl/WebGLRenderingContext.webidl index 1421a0020f..3ab31d10a3 100644 --- a/dom/webidl/WebGLRenderingContext.webidl +++ b/dom/webidl/WebGLRenderingContext.webidl @@ -45,24 +45,38 @@ dictionary WebGLContextAttributes { boolean failIfMajorPerformanceCaveat = false; }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLBuffer { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLFramebuffer { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLProgram { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLRenderbuffer { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLShader { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLTexture { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLUniformLocation { }; @@ -70,18 +84,24 @@ interface WebGLUniformLocation { interface WebGLVertexArrayObjectOES { }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLActiveInfo { readonly attribute GLint size; readonly attribute GLenum type; readonly attribute DOMString name; }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLShaderPrecisionFormat { readonly attribute GLint rangeMin; readonly attribute GLint rangeMax; readonly attribute GLint precision; }; +[Exposed=(Window,Worker), + Func="mozilla::dom::OffscreenCanvas::PrefEnabledOnWorkerThread"] interface WebGLRenderingContext { /* ClearBufferMask */ @@ -504,7 +524,7 @@ interface WebGLRenderingContext { const GLenum BROWSER_DEFAULT_WEBGL = 0x9244; // The canvas might actually be null in some cases, apparently. - readonly attribute HTMLCanvasElement? canvas; + readonly attribute (HTMLCanvasElement or OffscreenCanvas)? canvas; readonly attribute GLsizei drawingBufferWidth; readonly attribute GLsizei drawingBufferHeight; @@ -766,6 +786,14 @@ interface WebGLRenderingContext { void viewport(GLint x, GLint y, GLsizei width, GLsizei height); }; +// For OffscreenCanvas +// Reference: https://wiki.whatwg.org/wiki/OffscreenCanvas +[Exposed=(Window,Worker)] +partial interface WebGLRenderingContext { + [Func="mozilla::dom::OffscreenCanvas::PrefEnabled"] + void commit(); +}; + /*[Constructor(DOMString type, optional WebGLContextEventInit eventInit)] interface WebGLContextEvent : Event { readonly attribute DOMString statusMessage; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index fb79901acd..fadf5c03c9 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -347,6 +347,7 @@ WEBIDL_FILES = [ 'OfflineAudioCompletionEvent.webidl', 'OfflineAudioContext.webidl', 'OfflineResourceList.webidl', + 'OffscreenCanvas.webidl', 'OscillatorNode.webidl', 'PaintRequest.webidl', 'PaintRequestList.webidl', diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp index 7fc1a16c42..23b64695fa 100644 --- a/dom/workers/RuntimeService.cpp +++ b/dom/workers/RuntimeService.cpp @@ -169,6 +169,7 @@ static_assert(MAX_WORKERS_PER_DOMAIN >= 1, #define PREF_INTERCEPTION_OPAQUE_ENABLED "dom.serviceWorkers.interception.opaque.enabled" #define PREF_PUSH_ENABLED "dom.push.enabled" #define PREF_REQUESTCONTEXT_ENABLED "dom.requestcontext.enabled" +#define PREF_OFFSCREENCANVAS_ENABLED "gfx.offscreencanvas.enabled" namespace { @@ -1897,6 +1898,10 @@ RuntimeService::Init() WorkerPrefChanged, PREF_REQUESTCONTEXT_ENABLED, reinterpret_cast(WORKERPREF_REQUESTCONTEXT))) || + NS_FAILED(Preferences::RegisterCallbackAndCall( + WorkerPrefChanged, + PREF_OFFSCREENCANVAS_ENABLED, + reinterpret_cast(WORKERPREF_OFFSCREENCANVAS))) || NS_FAILED(Preferences::RegisterCallback(LoadRuntimeOptions, PREF_JS_OPTIONS_PREFIX, nullptr)) || @@ -2132,6 +2137,10 @@ RuntimeService::Cleanup() WorkerPrefChanged, PREF_REQUESTCONTEXT_ENABLED, reinterpret_cast(WORKERPREF_REQUESTCONTEXT))) || + NS_FAILED(Preferences::UnregisterCallback( + WorkerPrefChanged, + PREF_OFFSCREENCANVAS_ENABLED, + reinterpret_cast(WORKERPREF_OFFSCREENCANVAS))) || #if DUMP_CONTROLLED_BY_PREF NS_FAILED(Preferences::UnregisterCallback( WorkerPrefChanged, @@ -2678,6 +2687,7 @@ RuntimeService::WorkerPrefChanged(const char* aPrefName, void* aClosure) case WORKERPREF_SERVICEWORKERS_TESTING: case WORKERPREF_PUSH: case WORKERPREF_REQUESTCONTEXT: + case WORKERPREF_OFFSCREENCANVAS: sDefaultPreferences[key] = Preferences::GetBool(aPrefName, false); break; diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 481dc67d28..b8cc846838 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -1302,6 +1302,13 @@ public: return mPreferences[WORKERPREF_REQUESTCONTEXT]; } + bool + OffscreenCanvasEnabled() const + { + AssertIsOnWorkerThread(); + return mPreferences[WORKERPREF_OFFSCREENCANVAS]; + } + bool OnLine() const { diff --git a/dom/workers/Workers.h b/dom/workers/Workers.h index 2d7e85c4ce..ccfb177ff1 100644 --- a/dom/workers/Workers.h +++ b/dom/workers/Workers.h @@ -208,6 +208,7 @@ enum WorkerPreference WORKERPREF_PERFORMANCE_LOGGING_ENABLED, // dom.performance.enable_user_timing_logging WORKERPREF_PUSH, // dom.push.enabled WORKERPREF_REQUESTCONTEXT, // dom.requestcontext.enabled + WORKERPREF_OFFSCREENCANVAS, // gfx.offscreencanvas.enabled WORKERPREF_COUNT }; diff --git a/dom/workers/XMLHttpRequest.cpp b/dom/workers/XMLHttpRequest.cpp index 1dcc3e2044..38a63eeaf0 100644 --- a/dom/workers/XMLHttpRequest.cpp +++ b/dom/workers/XMLHttpRequest.cpp @@ -9,7 +9,6 @@ #include "nsIDOMEvent.h" #include "nsIDOMEventListener.h" #include "nsIRunnable.h" -#include "nsIVariant.h" #include "nsIXMLHttpRequest.h" #include "nsIXPConnect.h" @@ -24,6 +23,7 @@ #include "nsFormData.h" #include "nsJSUtils.h" #include "nsThreadUtils.h" +#include "nsVariant.h" #include "RuntimeService.h" #include "WorkerPrivate.h" @@ -1527,9 +1527,7 @@ SendRunnable::MainThreadRun() } } else { - nsCOMPtr wvariant = - do_CreateInstance(NS_VARIANT_CONTRACTID); - NS_ENSURE_TRUE(wvariant, NS_ERROR_UNEXPECTED); + RefPtr wvariant = new nsVariant(); if (NS_FAILED(wvariant->SetAsAString(mStringBody))) { MOZ_ASSERT(false, "This should never fail!"); diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js index f59ab99b53..17a4f1215e 100644 --- a/dom/workers/test/test_worker_interfaces.js +++ b/dom/workers/test/test_worker_interfaces.js @@ -151,6 +151,8 @@ var interfaceNamesInGlobalScope = "MessagePort", // IMPORTANT: Do not change this list without review from a DOM peer! "Notification", +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "OffscreenCanvas", disabled: true }, // IMPORTANT: Do not change this list without review from a DOM peer! "Performance", // IMPORTANT: Do not change this list without review from a DOM peer! @@ -185,6 +187,26 @@ var interfaceNamesInGlobalScope = "URL", // IMPORTANT: Do not change this list without review from a DOM peer! "URLSearchParams", +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLActiveInfo", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLBuffer", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLFramebuffer", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLProgram", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLRenderbuffer", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLRenderingContext", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLShader", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLShaderPrecisionFormat", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLTexture", disabled: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + { name: "WebGLUniformLocation", disabled: true }, // IMPORTANT: Do not change this list without review from a DOM peer! "WebSocket", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp index 011f268b0f..762d74a2c8 100644 --- a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp @@ -34,6 +34,7 @@ #include "nsIScriptSecurityManager.h" #include "nsJSUtils.h" #include "nsIXPConnect.h" +#include "nsVariant.h" #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/XSLTProcessorBinding.h" @@ -835,9 +836,7 @@ txMozillaXSLTProcessor::SetParameter(const nsAString & aNamespaceURI, rv = xpathResult->Clone(getter_AddRefs(clone)); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr variant = - do_CreateInstance("@mozilla.org/variant;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); + RefPtr variant = new nsVariant(); rv = variant->SetAsISupports(clone); NS_ENSURE_SUCCESS(rv, rv); diff --git a/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp b/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp index 52b049690b..06568dd949 100644 --- a/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp +++ b/dom/xul/templates/nsXULTemplateQueryProcessorStorage.cpp @@ -9,7 +9,7 @@ #include "nsUnicharUtils.h" #include "nsArrayUtils.h" -#include "nsIVariant.h" +#include "nsVariant.h" #include "nsAppDirectoryServiceDefs.h" #include "nsIURI.h" @@ -110,7 +110,7 @@ nsXULTemplateResultSetStorage::FillColumnValues(nsCOMArray& aArray) int32_t count = mColumnNames.Count(); for (int32_t c = 0; c < count; c++) { - nsCOMPtr value = do_CreateInstance("@mozilla.org/variant;1"); + RefPtr value = new nsVariant(); int32_t type; mStatement->GetTypeOfIndex(c, &type); diff --git a/editor/composer/nsEditorSpellCheck.cpp b/editor/composer/nsEditorSpellCheck.cpp index 36a1bad99b..420a2a3985 100644 --- a/editor/composer/nsEditorSpellCheck.cpp +++ b/editor/composer/nsEditorSpellCheck.cpp @@ -35,7 +35,7 @@ #include "nsITextServicesDocument.h" // for nsITextServicesDocument #include "nsITextServicesFilter.h" // for nsITextServicesFilter #include "nsIURI.h" // for nsIURI -#include "nsIVariant.h" // for nsIWritableVariant, etc +#include "nsVariant.h" // for nsIWritableVariant, etc #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc #include "nsMemory.h" // for nsMemory #include "nsRange.h" @@ -201,8 +201,7 @@ StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary) rv = docUri->GetSpec(docUriSpec); NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID); - NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY); + RefPtr prefValue = new nsVariant(); prefValue->SetAsAString(aDictionary); nsCOMPtr contentPrefService = diff --git a/gfx/gl/AndroidSurfaceTexture.cpp b/gfx/gl/AndroidSurfaceTexture.cpp index 8ed7350b05..9d2e285a4e 100644 --- a/gfx/gl/AndroidSurfaceTexture.cpp +++ b/gfx/gl/AndroidSurfaceTexture.cpp @@ -131,9 +131,12 @@ AndroidSurfaceTexture::UpdateCanDetach() // The API for attach/detach only exists on 16+, and PowerVR has some sort of // fencing issue. Additionally, attach/detach seems to be busted on at least some // Mali adapters (400MP2 for sure, bug 1131793) + bool canDetach = gfxPrefs::SurfaceTextureDetachEnabled(); + mCanDetach = AndroidBridge::Bridge()->GetAPIVersion() >= 16 && (!mAttachedContext || mAttachedContext->Vendor() != GLVendor::Imagination) && - (!mAttachedContext || mAttachedContext->Vendor() != GLVendor::ARM /* Mali */); + (!mAttachedContext || mAttachedContext->Vendor() != GLVendor::ARM /* Mali */) && + canDetach; } bool diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index fc7cf16620..4abcdc4187 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -2645,6 +2645,7 @@ GLContext::Readback(SharedSurface* src, gfx::DataSourceSurface* dest) } GLuint tempFB = 0; + GLuint tempTex = 0; { ScopedBindFramebuffer autoFB(this); @@ -2676,6 +2677,24 @@ GLContext::Readback(SharedSurface* src, gfx::DataSourceSurface* dest) MOZ_ASSERT(status == LOCAL_GL_FRAMEBUFFER_COMPLETE); } + if (src->NeedsIndirectReads()) { + fGenTextures(1, &tempTex); + { + ScopedBindTexture autoTex(this, tempTex); + + GLenum format = src->mHasAlpha ? LOCAL_GL_RGBA + : LOCAL_GL_RGB; + auto width = src->mSize.width; + auto height = src->mSize.height; + fCopyTexImage2D(LOCAL_GL_TEXTURE_2D, 0, format, 0, 0, width, + height, 0); + } + + fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, + LOCAL_GL_COLOR_ATTACHMENT0, + LOCAL_GL_TEXTURE_2D, tempTex, 0); + } + ReadPixelsIntoDataSurface(this, dest); src->ProducerReadRelease(); @@ -2684,6 +2703,10 @@ GLContext::Readback(SharedSurface* src, gfx::DataSourceSurface* dest) if (tempFB) fDeleteFramebuffers(1, &tempFB); + if (tempTex) { + fDeleteTextures(1, &tempTex); + } + if (needsSwap) { src->UnlockProd(); if (prev) diff --git a/gfx/gl/GLLibraryEGL.cpp b/gfx/gl/GLLibraryEGL.cpp index 9f830e5495..1c56ab5cb1 100644 --- a/gfx/gl/GLLibraryEGL.cpp +++ b/gfx/gl/GLLibraryEGL.cpp @@ -4,6 +4,8 @@ #include "GLLibraryEGL.h" +#include "gfxCrashReporterUtils.h" +#include "gfxUtils.h" #include "mozilla/Preferences.h" #include "mozilla/Assertions.h" #include "nsDirectoryServiceDefs.h" @@ -13,13 +15,17 @@ #ifdef XP_WIN #include "nsWindowsHelpers.h" #endif +#include "OGLShaderProgram.h" #include "prenv.h" #include "GLContext.h" +#include "GLContextProvider.h" #include "gfxPrefs.h" +#include "ScopedGLHelpers.h" namespace mozilla { namespace gl { +StaticMutex GLLibraryEGL::sMutex; GLLibraryEGL sEGLLibrary; #ifdef MOZ_B2G ThreadLocal GLLibraryEGL::sCurrentContext; @@ -45,6 +51,8 @@ static const char *sEGLExtensionNames[] = { static PRLibrary* LoadApitraceLibrary() { + // Initialization of gfx prefs here is only needed during the unit tests... + gfxPrefs::GetSingleton(); if (!gfxPrefs::UseApitrace()) { return nullptr; } @@ -125,7 +133,9 @@ static bool IsAccelAngleSupported(const nsCOMPtr& gfxInfo) { int32_t angleSupport; - gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_WEBGL_ANGLE, &angleSupport); + gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, + nsIGfxInfo::FEATURE_WEBGL_ANGLE, + &angleSupport); return (angleSupport == nsIGfxInfo::FEATURE_STATUS_OK); } @@ -142,6 +152,32 @@ GetAndInitDisplay(GLLibraryEGL& egl, void* displayType) return display; } +bool +GLLibraryEGL::ReadbackEGLImage(EGLImage image, gfx::DataSourceSurface* out_surface) +{ + StaticMutexAutoUnlock lock(sMutex); + if (!mReadbackGL) { + mReadbackGL = gl::GLContextProvider::CreateHeadless(gl::CreateContextFlags::NONE); + } + + ScopedTexture destTex(mReadbackGL); + const GLuint target = LOCAL_GL_TEXTURE_EXTERNAL; + ScopedBindTexture autoTex(mReadbackGL, destTex.Texture(), target); + mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); + mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); + mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST); + mReadbackGL->fTexParameteri(target, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST); + mReadbackGL->fEGLImageTargetTexture2D(target, image); + + ShaderConfigOGL config = ShaderConfigFromTargetAndFormat(target, + out_surface->GetFormat()); + int shaderConfig = config.mFeatures; + mReadbackGL->ReadTexImageHelper()->ReadTexImage(out_surface, 0, target, + out_surface->GetSize(), shaderConfig); + + return true; +} + bool GLLibraryEGL::EnsureInitialized(bool forceAccel) { @@ -149,6 +185,8 @@ GLLibraryEGL::EnsureInitialized(bool forceAccel) return true; } + mozilla::ScopedGfxFeatureReporter reporter("EGL"); + #ifdef MOZ_B2G if (!sCurrentContext.init()) MOZ_CRASH("Tls init failed"); @@ -483,6 +521,7 @@ GLLibraryEGL::EnsureInitialized(bool forceAccel) } mInitialized = true; + reporter.SetSuccessful(); return true; } diff --git a/gfx/gl/GLLibraryEGL.h b/gfx/gl/GLLibraryEGL.h index c4cd44b811..a3e0d5f154 100644 --- a/gfx/gl/GLLibraryEGL.h +++ b/gfx/gl/GLLibraryEGL.h @@ -10,6 +10,7 @@ #endif #include "GLLibraryLoader.h" +#include "mozilla/StaticMutex.h" #include "mozilla/ThreadLocal.h" #include "nsIFile.h" #include "GeckoProfiler.h" @@ -52,6 +53,11 @@ #endif namespace mozilla { + +namespace gfx { +class DataSourceSurface; +} + namespace gl { #undef BEFORE_GL_CALL @@ -94,6 +100,8 @@ namespace gl { #define AFTER_GL_CALL #endif +class GLContext; + class GLLibraryEGL { public: @@ -486,6 +494,8 @@ public: return IsExtensionSupported(EXT_create_context_robustness); } + bool ReadbackEGLImage(EGLImage image, gfx::DataSourceSurface* out_surface); + bool EnsureInitialized(bool forceAccel = false); void DumpEGLConfig(EGLConfig cfg); @@ -614,9 +624,11 @@ private: bool mInitialized; PRLibrary* mEGLLibrary; EGLDisplay mEGLDisplay; + RefPtr mReadbackGL; bool mIsANGLE; bool mIsWARP; + static StaticMutex sMutex; }; extern GLLibraryEGL sEGLLibrary; diff --git a/gfx/gl/GLReadTexImageHelper.cpp b/gfx/gl/GLReadTexImageHelper.cpp index d93b491493..92f8871813 100644 --- a/gfx/gl/GLReadTexImageHelper.cpp +++ b/gfx/gl/GLReadTexImageHelper.cpp @@ -214,7 +214,7 @@ GetActualReadFormats(GLContext* gl, } } -static void +void SwapRAndBComponents(DataSourceSurface* surf) { DataSourceSurface::MappedSurface map; @@ -533,8 +533,8 @@ ReadPixelsIntoDataSurface(GLContext* gl, DataSourceSurface* dest) #endif } -static already_AddRefed -YInvertImageSurface(DataSourceSurface* aSurf) +already_AddRefed +YInvertImageSurface(gfx::DataSourceSurface* aSurf) { RefPtr temp = Factory::CreateDataSourceSurfaceWithStride(aSurf->GetSize(), @@ -614,8 +614,7 @@ ReadBackSurface(GLContext* gl, GLuint aTexture, bool aYInvert, SurfaceFormat aFo #define CLEANUP_IF_GLERROR_OCCURRED(x) \ if (DidGLErrorOccur(x)) { \ - isurf = nullptr; \ - break; \ + return false; \ } already_AddRefed @@ -624,6 +623,31 @@ GLReadTexImageHelper::ReadTexImage(GLuint aTextureId, const gfx::IntSize& aSize, /* ShaderConfigOGL.mFeature */ int aConfig, bool aYInvert) +{ + /* Allocate resulting image surface */ + int32_t stride = aSize.width * BytesPerPixel(SurfaceFormat::R8G8B8A8); + RefPtr isurf = + Factory::CreateDataSourceSurfaceWithStride(aSize, + SurfaceFormat::R8G8B8A8, + stride); + if (NS_WARN_IF(!isurf)) { + return nullptr; + } + + if (!ReadTexImage(isurf, aTextureId, aTextureTarget, aSize, aConfig, aYInvert)) { + return nullptr; + } + + return isurf.forget(); +} + +bool +GLReadTexImageHelper::ReadTexImage(DataSourceSurface* aDest, + GLuint aTextureId, + GLenum aTextureTarget, + const gfx::IntSize& aSize, + /* ShaderConfigOGL.mFeature */ int aConfig, + bool aYInvert) { MOZ_ASSERT(aTextureTarget == LOCAL_GL_TEXTURE_2D || aTextureTarget == LOCAL_GL_TEXTURE_EXTERNAL || @@ -631,16 +655,6 @@ GLReadTexImageHelper::ReadTexImage(GLuint aTextureId, mGL->MakeCurrent(); - /* Allocate resulting image surface */ - int32_t stride = aSize.width * BytesPerPixel(SurfaceFormat::R8G8B8A8); - RefPtr isurf = - Factory::CreateDataSourceSurfaceWithStride(aSize, - SurfaceFormat::R8G8B8A8, - stride); - if (NS_WARN_IF(!isurf)) { - return nullptr; - } - GLint oldrb, oldfb, oldprog, oldTexUnit, oldTex; GLuint rb, fb; @@ -737,7 +751,7 @@ GLReadTexImageHelper::ReadTexImage(GLuint aTextureId, CLEANUP_IF_GLERROR_OCCURRED("when drawing texture"); /* Read-back draw results */ - ReadPixelsIntoDataSurface(mGL, isurf); + ReadPixelsIntoDataSurface(mGL, aDest); CLEANUP_IF_GLERROR_OCCURRED("when reading pixels into surface"); } while (false); @@ -756,7 +770,7 @@ GLReadTexImageHelper::ReadTexImage(GLuint aTextureId, if (oldTexUnit != LOCAL_GL_TEXTURE0) mGL->fActiveTexture(oldTexUnit); - return isurf.forget(); + return true; } #undef CLEANUP_IF_GLERROR_OCCURRED diff --git a/gfx/gl/GLReadTexImageHelper.h b/gfx/gl/GLReadTexImageHelper.h index 821c003a96..685afcf270 100644 --- a/gfx/gl/GLReadTexImageHelper.h +++ b/gfx/gl/GLReadTexImageHelper.h @@ -34,6 +34,12 @@ void ReadPixelsIntoDataSurface(GLContext* aGL, already_AddRefed ReadBackSurface(GLContext* gl, GLuint aTexture, bool aYInvert, gfx::SurfaceFormat aFormat); +already_AddRefed +YInvertImageSurface(gfx::DataSourceSurface* aSurf); + +void +SwapRAndBComponents(gfx::DataSourceSurface* surf); + class GLReadTexImageHelper final { // The GLContext is the sole owner of the GLBlitHelper. @@ -65,12 +71,17 @@ public: * passed as int to eliminate including LayerManagerOGLProgram.h here. */ already_AddRefed ReadTexImage(GLuint aTextureId, - GLenum aTextureTarget, - const gfx::IntSize& aSize, - /* ShaderProgramType */ int aShaderProgram, - bool aYInvert = false); - + GLenum aTextureTarget, + const gfx::IntSize& aSize, + /* ShaderProgramType */ int aShaderProgram, + bool aYInvert = false); + bool ReadTexImage(gfx::DataSourceSurface* aDest, + GLuint aTextureId, + GLenum aTextureTarget, + const gfx::IntSize& aSize, + int aShaderProgram, + bool aYInvert = false); }; } // namespace gl diff --git a/gfx/gl/GLScreenBuffer.cpp b/gfx/gl/GLScreenBuffer.cpp index f81926981c..997edd20db 100755 --- a/gfx/gl/GLScreenBuffer.cpp +++ b/gfx/gl/GLScreenBuffer.cpp @@ -10,18 +10,32 @@ #include "GLContext.h" #include "GLBlitHelper.h" #include "GLReadTexImageHelper.h" +#include "SharedSurfaceEGL.h" #include "SharedSurfaceGL.h" +#include "ScopedGLHelpers.h" +#include "gfx2DGlue.h" +#include "../layers/ipc/ShadowLayers.h" +#include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TextureClientSharedSurface.h" + +#ifdef XP_WIN +#include "SharedSurfaceANGLE.h" // for SurfaceFactory_ANGLEShareHandle +#include "gfxWindowsPlatform.h" +#endif + #ifdef MOZ_WIDGET_GONK #include "SharedSurfaceGralloc.h" #include "nsXULAppAPI.h" #endif + #ifdef XP_MACOSX #include "SharedSurfaceIO.h" #endif -#include "ScopedGLHelpers.h" -#include "gfx2DGlue.h" -#include "../layers/ipc/ShadowLayers.h" -#include "mozilla/layers/TextureClientSharedSurface.h" + +#ifdef GL_PROVIDER_GLX +#include "GLXLibrary.h" +#include "SharedSurfaceGLX.h" +#endif namespace mozilla { namespace gl { @@ -51,6 +65,53 @@ GLScreenBuffer::Create(GLContext* gl, return Move(ret); } +/* static */ UniquePtr +GLScreenBuffer::CreateFactory(GLContext* gl, + const SurfaceCaps& caps, + const RefPtr& forwarder, + const layers::TextureFlags& flags) +{ + UniquePtr factory = nullptr; + if (!gfxPrefs::WebGLForceLayersReadback()) { + switch (forwarder->GetCompositorBackendType()) { + case mozilla::layers::LayersBackend::LAYERS_OPENGL: { +#if defined(XP_MACOSX) + factory = SurfaceFactory_IOSurface::Create(gl, caps, forwarder, flags); +#elif defined(MOZ_WIDGET_GONK) + factory = MakeUnique(gl, caps, forwarder, flags); +#elif defined(GL_PROVIDER_GLX) + if (sGLXLibrary.UseSurfaceSharing()) + factory = SurfaceFactory_GLXDrawable::Create(gl, caps, forwarder, flags); +#else + if (gl->GetContextType() == GLContextType::EGL) { + if (XRE_IsParentProcess()) { + factory = SurfaceFactory_EGLImage::Create(gl, caps, forwarder, flags); + } + } +#endif + break; + } + case mozilla::layers::LayersBackend::LAYERS_D3D11: { +#ifdef XP_WIN + // Enable surface sharing only if ANGLE and compositing devices + // are both WARP or both not WARP + if (gl->IsANGLE() && + (gl->IsWARP() == gfxWindowsPlatform::GetPlatform()->IsWARP()) && + gfxWindowsPlatform::GetPlatform()->CompositorD3D11TextureSharingWorks()) + { + factory = SurfaceFactory_ANGLEShareHandle::Create(gl, caps, forwarder, flags); + } +#endif + break; + } + default: + break; + } + } + + return factory; +} + GLScreenBuffer::GLScreenBuffer(GLContext* gl, const SurfaceCaps& caps, UniquePtr factory) diff --git a/gfx/gl/GLScreenBuffer.h b/gfx/gl/GLScreenBuffer.h index 15edad6925..2b31500ceb 100644 --- a/gfx/gl/GLScreenBuffer.h +++ b/gfx/gl/GLScreenBuffer.h @@ -25,6 +25,7 @@ namespace mozilla { namespace layers { +class CompositableForwarder; class SharedSurfaceTextureClient; } // namespace layers @@ -133,6 +134,12 @@ public: const gfx::IntSize& size, const SurfaceCaps& caps); + static UniquePtr + CreateFactory(GLContext* gl, + const SurfaceCaps& caps, + const RefPtr& forwarder, + const layers::TextureFlags& flags); + protected: GLContext* const mGL; // Owns us. public: diff --git a/gfx/gl/SharedSurface.cpp b/gfx/gl/SharedSurface.cpp index 634cc794a7..5631be1772 100644 --- a/gfx/gl/SharedSurface.cpp +++ b/gfx/gl/SharedSurface.cpp @@ -311,6 +311,7 @@ SurfaceFactory::SurfaceFactory(SharedSurfaceType type, GLContext* gl, , mAllocator(allocator) , mFlags(flags) , mFormats(gl->ChooseGLFormats(caps)) + , mMutex("SurfaceFactor::mMutex") { ChooseBufferBits(mCaps, &mDrawCaps, &mReadCaps); } @@ -368,6 +369,7 @@ SurfaceFactory::StartRecycling(layers::SharedSurfaceTextureClient* tc) void SurfaceFactory::StopRecycling(layers::SharedSurfaceTextureClient* tc) { + MutexAutoLock autoLock(mMutex); // Must clear before releasing ref. tc->ClearRecycleCallback(); @@ -379,11 +381,8 @@ SurfaceFactory::StopRecycling(layers::SharedSurfaceTextureClient* tc) /*static*/ void SurfaceFactory::RecycleCallback(layers::TextureClient* rawTC, void* rawFactory) { - MOZ_ASSERT(NS_IsMainThread()); - RefPtr tc; tc = static_cast(rawTC); - SurfaceFactory* factory = static_cast(rawFactory); if (tc->mSurf->mCanRecycle) { @@ -399,6 +398,7 @@ bool SurfaceFactory::Recycle(layers::SharedSurfaceTextureClient* texClient) { MOZ_ASSERT(texClient); + MutexAutoLock autoLock(mMutex); if (mRecycleFreePool.size() >= 2) { return false; diff --git a/gfx/gl/SharedSurface.h b/gfx/gl/SharedSurface.h index 8c25ac7063..0e98689acf 100644 --- a/gfx/gl/SharedSurface.h +++ b/gfx/gl/SharedSurface.h @@ -24,6 +24,7 @@ #include "mozilla/Attributes.h" #include "mozilla/DebugOnly.h" #include "mozilla/gfx/Point.h" +#include "mozilla/Mutex.h" #include "mozilla/UniquePtr.h" #include "mozilla/WeakPtr.h" #include "ScopedGLHelpers.h" @@ -33,6 +34,7 @@ class nsIThread; namespace mozilla { namespace gfx { +class DataSourceSurface; class DrawTarget; } // namespace gfx @@ -199,6 +201,10 @@ public: } virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) = 0; + + virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) { + return false; + } }; template @@ -298,6 +304,7 @@ public: const RefPtr mAllocator; const layers::TextureFlags mFlags; const GLFormats mFormats; + Mutex mMutex; protected: SurfaceCaps mDrawCaps; SurfaceCaps mReadCaps; diff --git a/gfx/gl/SharedSurfaceANGLE.cpp b/gfx/gl/SharedSurfaceANGLE.cpp index 51e83c71af..8d6f63246e 100644 --- a/gfx/gl/SharedSurfaceANGLE.cpp +++ b/gfx/gl/SharedSurfaceANGLE.cpp @@ -275,6 +275,138 @@ SharedSurface_ANGLEShareHandle::ToSurfaceDescriptor(layers::SurfaceDescriptor* c return true; } +class ScopedLockTexture final +{ +public: + explicit ScopedLockTexture(ID3D11Texture2D* texture, bool* succeeded) + : mIsLocked(false) + , mTexture(texture) + { + MOZ_ASSERT(mTexture); + MOZ_ASSERT(succeeded); + *succeeded = false; + + HRESULT hr; + mTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mMutex)); + if (mMutex) { + hr = mMutex->AcquireSync(0, 10000); + if (hr == WAIT_TIMEOUT) { + MOZ_CRASH(); + } + + if (FAILED(hr)) { + NS_WARNING("Failed to lock the texture"); + return; + } + } + + ID3D11Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D11Device(); + device->GetImmediateContext(getter_AddRefs(mDeviceContext)); + + mTexture->GetDesc(&mDesc); + mDesc.BindFlags = 0; + mDesc.Usage = D3D11_USAGE_STAGING; + mDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + mDesc.MiscFlags = 0; + + hr = device->CreateTexture2D(&mDesc, nullptr, getter_AddRefs(mCopiedTexture)); + + if (FAILED(hr)) { + return; + } + + mDeviceContext->CopyResource(mCopiedTexture, mTexture); + + hr = mDeviceContext->Map(mCopiedTexture, 0, D3D11_MAP_READ, 0, &mSubresource); + if (FAILED(hr)) { + return; + } + + *succeeded = true; + mIsLocked = true; + } + + ~ScopedLockTexture() + { + mDeviceContext->Unmap(mCopiedTexture, 0); + if (mMutex) { + HRESULT hr = mMutex->ReleaseSync(0); + if (FAILED(hr)) { + NS_WARNING("Failed to unlock the texture"); + } + } + mIsLocked = false; + } + + bool mIsLocked; + RefPtr mTexture; + RefPtr mCopiedTexture; + RefPtr mMutex; + RefPtr mDeviceContext; + D3D11_TEXTURE2D_DESC mDesc; + D3D11_MAPPED_SUBRESOURCE mSubresource; +}; + +bool +SharedSurface_ANGLEShareHandle::ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) +{ + MOZ_ASSERT(out_surface); + RefPtr tex; + ID3D11Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D11Device(); + HRESULT hr = device->OpenSharedResource(mShareHandle, + __uuidof(ID3D11Texture2D), + (void**)(ID3D11Texture2D**)getter_AddRefs(tex)); + + if (FAILED(hr)) { + return false; + } + + bool succeeded = false; + ScopedLockTexture scopedLock(tex, &succeeded); + if (!succeeded) { + return false; + } + + const uint8_t* data = reinterpret_cast(scopedLock.mSubresource.pData); + uint32_t srcStride = scopedLock.mSubresource.RowPitch; + + gfx::DataSourceSurface::ScopedMap map(out_surface, gfx::DataSourceSurface::WRITE); + if (!map.IsMapped()) { + return false; + } + + if (map.GetStride() == srcStride) { + memcpy(map.GetData(), data, out_surface->GetSize().height * map.GetStride()); + } else { + const uint8_t bytesPerPixel = BytesPerPixel(out_surface->GetFormat()); + for (int32_t i = 0; i < out_surface->GetSize().height; i++) { + memcpy(map.GetData() + i * map.GetStride(), + data + i * srcStride, + bytesPerPixel * out_surface->GetSize().width); + } + } + + DXGI_FORMAT srcFormat = scopedLock.mDesc.Format; + MOZ_ASSERT(srcFormat == DXGI_FORMAT_B8G8R8A8_UNORM || + srcFormat == DXGI_FORMAT_B8G8R8X8_UNORM || + srcFormat == DXGI_FORMAT_R8G8B8A8_UNORM); + bool isSrcRGB = srcFormat == DXGI_FORMAT_R8G8B8A8_UNORM; + + gfx::SurfaceFormat destFormat = out_surface->GetFormat(); + MOZ_ASSERT(destFormat == gfx::SurfaceFormat::R8G8B8X8 || + destFormat == gfx::SurfaceFormat::R8G8B8A8 || + destFormat == gfx::SurfaceFormat::B8G8R8X8 || + destFormat == gfx::SurfaceFormat::B8G8R8A8); + bool isDestRGB = destFormat == gfx::SurfaceFormat::R8G8B8X8 || + destFormat == gfx::SurfaceFormat::R8G8B8A8; + + if (isSrcRGB != isDestRGB) { + SwapRAndBComponents(out_surface); + } + + return true; +} + //////////////////////////////////////////////////////////////////////////////// // Factory diff --git a/gfx/gl/SharedSurfaceANGLE.h b/gfx/gl/SharedSurfaceANGLE.h index 3dcab9d043..f37b50ed98 100644 --- a/gfx/gl/SharedSurfaceANGLE.h +++ b/gfx/gl/SharedSurfaceANGLE.h @@ -81,6 +81,8 @@ public: } virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) override; + + virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) override; }; diff --git a/gfx/gl/SharedSurfaceEGL.cpp b/gfx/gl/SharedSurfaceEGL.cpp index ca3f18dfb2..f4391cd0fb 100644 --- a/gfx/gl/SharedSurfaceEGL.cpp +++ b/gfx/gl/SharedSurfaceEGL.cpp @@ -10,7 +10,6 @@ #include "GLLibraryEGL.h" #include "GLReadTexImageHelper.h" #include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor, etc -#include "ScopedGLHelpers.h" #include "SharedSurface.h" #include "TextureGarbageBin.h" @@ -213,6 +212,14 @@ SharedSurface_EGLImage::ToSurfaceDescriptor(layers::SurfaceDescriptor* const out return true; } +bool +SharedSurface_EGLImage::ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) +{ + MOZ_ASSERT(out_surface); + MOZ_ASSERT(NS_IsMainThread()); + return sEGLLibrary.ReadbackEGLImage(mImage, out_surface); +} + //////////////////////////////////////////////////////////////////////// /*static*/ UniquePtr diff --git a/gfx/gl/SharedSurfaceEGL.h b/gfx/gl/SharedSurfaceEGL.h index 8b8c283cc8..5dba0fb520 100644 --- a/gfx/gl/SharedSurfaceEGL.h +++ b/gfx/gl/SharedSurfaceEGL.h @@ -79,6 +79,8 @@ public: void AcquireConsumerTexture(GLContext* consGL, GLuint* out_texture, GLuint* out_target); virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) override; + + virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) override; }; diff --git a/gfx/gl/SharedSurfaceGLX.cpp b/gfx/gl/SharedSurfaceGLX.cpp index ecf8320ff7..e91630c22e 100644 --- a/gfx/gl/SharedSurfaceGLX.cpp +++ b/gfx/gl/SharedSurfaceGLX.cpp @@ -9,6 +9,7 @@ #include "GLContextProvider.h" #include "GLContextGLX.h" #include "GLScreenBuffer.h" +#include "mozilla/gfx/SourceSurfaceCairo.h" #include "mozilla/layers/LayersSurfaces.h" #include "mozilla/layers/ShadowLayerUtilsX11.h" #include "mozilla/layers/ISurfaceAllocator.h" @@ -83,6 +84,38 @@ SharedSurface_GLXDrawable::ToSurfaceDescriptor(layers::SurfaceDescriptor* const return true; } +bool +SharedSurface_GLXDrawable::ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) +{ + MOZ_ASSERT(out_surface); + RefPtr dataSurf = + new gfx::DataSourceSurfaceCairo(mXlibSurface->CairoSurface()); + + gfx::DataSourceSurface::ScopedMap mapSrc(dataSurf, gfx::DataSourceSurface::READ); + if (!mapSrc.IsMapped()) { + return false; + } + + gfx::DataSourceSurface::ScopedMap mapDest(out_surface, gfx::DataSourceSurface::WRITE); + if (!mapDest.IsMapped()) { + return false; + } + + if (mapDest.GetStride() == mapSrc.GetStride()) { + memcpy(mapDest.GetData(), + mapSrc.GetData(), + out_surface->GetSize().height * mapDest.GetStride()); + } else { + for (int32_t i = 0; i < dataSurf->GetSize().height; i++) { + memcpy(mapDest.GetData() + i * mapDest.GetStride(), + mapSrc.GetData() + i * mapSrc.GetStride(), + std::min(mapSrc.GetStride(), mapDest.GetStride())); + } + } + + return true; +} + /* static */ UniquePtr SurfaceFactory_GLXDrawable::Create(GLContext* prodGL, diff --git a/gfx/gl/SharedSurfaceGLX.h b/gfx/gl/SharedSurfaceGLX.h index 1822fba4d7..528c223400 100644 --- a/gfx/gl/SharedSurfaceGLX.h +++ b/gfx/gl/SharedSurfaceGLX.h @@ -32,6 +32,8 @@ public: virtual void UnlockProdImpl() override; virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) override; + + virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) override; private: SharedSurface_GLXDrawable(GLContext* gl, const gfx::IntSize& size, diff --git a/gfx/gl/SharedSurfaceGralloc.cpp b/gfx/gl/SharedSurfaceGralloc.cpp index ba957c4012..87c285ca13 100644 --- a/gfx/gl/SharedSurfaceGralloc.cpp +++ b/gfx/gl/SharedSurfaceGralloc.cpp @@ -283,5 +283,58 @@ SharedSurface_Gralloc::ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_ return mTextureClient->ToSurfaceDescriptor(*out_descriptor); } +bool +SharedSurface_Gralloc::ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) +{ + MOZ_ASSERT(out_surface); + sp buffer = mTextureClient->GetGraphicBuffer(); + + const uint8_t* grallocData = nullptr; + auto result = buffer->lock( + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER, + const_cast(reinterpret_cast(&grallocData)) + ); + + if (result == BAD_VALUE) { + return false; + } + + gfx::DataSourceSurface::ScopedMap map(out_surface, gfx::DataSourceSurface::WRITE); + if (!map.IsMapped()) { + buffer->unlock(); + return false; + } + + uint32_t stride = buffer->getStride() * android::bytesPerPixel(buffer->getPixelFormat()); + uint32_t height = buffer->getHeight(); + uint32_t width = buffer->getWidth(); + for (uint32_t i = 0; i < height; i++) { + memcpy(map.GetData() + i * map.GetStride(), + grallocData + i * stride, width * 4); + } + + buffer->unlock(); + + android::PixelFormat srcFormat = buffer->getPixelFormat(); + MOZ_ASSERT(srcFormat == PIXEL_FORMAT_RGBA_8888 || + srcFormat == PIXEL_FORMAT_BGRA_8888 || + srcFormat == PIXEL_FORMAT_RGBX_8888); + bool isSrcRGB = srcFormat == PIXEL_FORMAT_RGBA_8888 || + srcFormat == PIXEL_FORMAT_RGBX_8888; + + gfx::SurfaceFormat destFormat = out_surface->GetFormat(); + MOZ_ASSERT(destFormat == gfx::SurfaceFormat::R8G8B8X8 || + destFormat == gfx::SurfaceFormat::R8G8B8A8 || + destFormat == gfx::SurfaceFormat::B8G8R8X8 || + destFormat == gfx::SurfaceFormat::B8G8R8A8); + bool isDestRGB = destFormat == gfx::SurfaceFormat::R8G8B8X8 || + destFormat == gfx::SurfaceFormat::R8G8B8A8; + + if (isSrcRGB != isDestRGB) { + SwapRAndBComponents(out_surface); + } + return true; +} + } // namespace gl } // namespace mozilla diff --git a/gfx/gl/SharedSurfaceGralloc.h b/gfx/gl/SharedSurfaceGralloc.h index 14097224cd..d19e33c461 100644 --- a/gfx/gl/SharedSurfaceGralloc.h +++ b/gfx/gl/SharedSurfaceGralloc.h @@ -75,6 +75,8 @@ public: } virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) override; + + virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) override; }; class SurfaceFactory_Gralloc diff --git a/gfx/gl/SharedSurfaceIO.cpp b/gfx/gl/SharedSurfaceIO.cpp index 79b5449bc2..41c84ef1d7 100644 --- a/gfx/gl/SharedSurfaceIO.cpp +++ b/gfx/gl/SharedSurfaceIO.cpp @@ -173,6 +173,31 @@ SharedSurface_IOSurface::ToSurfaceDescriptor(layers::SurfaceDescriptor* const ou return true; } +bool +SharedSurface_IOSurface::ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) +{ + MOZ_ASSERT(out_surface); + mIOSurf->Lock(); + size_t bytesPerRow = mIOSurf->GetBytesPerRow(); + size_t ioWidth = mIOSurf->GetDevicePixelWidth(); + size_t ioHeight = mIOSurf->GetDevicePixelHeight(); + + const unsigned char* ioData = (unsigned char*)mIOSurf->GetBaseAddress(); + gfx::DataSourceSurface::ScopedMap map(out_surface, gfx::DataSourceSurface::WRITE); + if (!map.IsMapped()) { + mIOSurf->Unlock(); + return false; + } + + for (size_t i = 0; i < ioHeight; i++) { + memcpy(map.GetData() + i * map.GetStride(), + ioData + i * bytesPerRow, ioWidth * 4); + } + + mIOSurf->Unlock(); + return true; +} + //////////////////////////////////////////////////////////////////////// // SurfaceFactory_IOSurface diff --git a/gfx/gl/SharedSurfaceIO.h b/gfx/gl/SharedSurfaceIO.h index 3976b5787f..fe787ef9cf 100644 --- a/gfx/gl/SharedSurfaceIO.h +++ b/gfx/gl/SharedSurfaceIO.h @@ -68,6 +68,8 @@ public: } virtual bool ToSurfaceDescriptor(layers::SurfaceDescriptor* const out_descriptor) override; + + virtual bool ReadbackBySharedHandle(gfx::DataSourceSurface* out_surface) override; }; class SurfaceFactory_IOSurface : public SurfaceFactory diff --git a/gfx/layers/AsyncCanvasRenderer.cpp b/gfx/layers/AsyncCanvasRenderer.cpp new file mode 100644 index 0000000000..ac1cb54e49 --- /dev/null +++ b/gfx/layers/AsyncCanvasRenderer.cpp @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AsyncCanvasRenderer.h" + +#include "gfxUtils.h" +#include "GLContext.h" +#include "GLReadTexImageHelper.h" +#include "GLScreenBuffer.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/layers/CanvasClient.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureClientSharedSurface.h" +#include "mozilla/ReentrantMonitor.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" + +namespace mozilla { +namespace layers { + +AsyncCanvasRenderer::AsyncCanvasRenderer() + : mHTMLCanvasElement(nullptr) + , mContext(nullptr) + , mGLContext(nullptr) + , mIsAlphaPremultiplied(true) + , mWidth(0) + , mHeight(0) + , mCanvasClientAsyncID(0) + , mCanvasClient(nullptr) + , mMutex("AsyncCanvasRenderer::mMutex") +{ + MOZ_COUNT_CTOR(AsyncCanvasRenderer); +} + +AsyncCanvasRenderer::~AsyncCanvasRenderer() +{ + MOZ_COUNT_DTOR(AsyncCanvasRenderer); +} + +void +AsyncCanvasRenderer::NotifyElementAboutAttributesChanged() +{ + class Runnable final : public nsRunnable + { + public: + explicit Runnable(AsyncCanvasRenderer* aRenderer) + : mRenderer(aRenderer) + {} + + NS_IMETHOD Run() + { + if (mRenderer) { + dom::HTMLCanvasElement::SetAttrFromAsyncCanvasRenderer(mRenderer); + } + + return NS_OK; + } + + void Revoke() + { + mRenderer = nullptr; + } + + private: + RefPtr mRenderer; + }; + + RefPtr runnable = new Runnable(this); + nsresult rv = NS_DispatchToMainThread(runnable); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch a runnable to the main-thread."); + } +} + +void +AsyncCanvasRenderer::NotifyElementAboutInvalidation() +{ + class Runnable final : public nsRunnable + { + public: + explicit Runnable(AsyncCanvasRenderer* aRenderer) + : mRenderer(aRenderer) + {} + + NS_IMETHOD Run() + { + if (mRenderer) { + dom::HTMLCanvasElement::InvalidateFromAsyncCanvasRenderer(mRenderer); + } + + return NS_OK; + } + + void Revoke() + { + mRenderer = nullptr; + } + + private: + RefPtr mRenderer; + }; + + RefPtr runnable = new Runnable(this); + nsresult rv = NS_DispatchToMainThread(runnable); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch a runnable to the main-thread."); + } +} + +void +AsyncCanvasRenderer::SetCanvasClient(CanvasClient* aClient) +{ + mCanvasClient = aClient; + if (aClient) { + mCanvasClientAsyncID = aClient->GetAsyncID(); + } else { + mCanvasClientAsyncID = 0; + } +} + +void +AsyncCanvasRenderer::SetActiveThread() +{ + MutexAutoLock lock(mMutex); + mActiveThread = NS_GetCurrentThread(); +} + +void +AsyncCanvasRenderer::ResetActiveThread() +{ + MutexAutoLock lock(mMutex); + mActiveThread = nullptr; +} + +already_AddRefed +AsyncCanvasRenderer::GetActiveThread() +{ + MutexAutoLock lock(mMutex); + nsCOMPtr result = mActiveThread; + return result.forget(); +} + +void +AsyncCanvasRenderer::CopyFromTextureClient(TextureClient* aTextureClient) +{ + MutexAutoLock lock(mMutex); + RefPtr buffer = static_cast(aTextureClient); + if (!buffer->Lock(layers::OpenMode::OPEN_READ)) { + return; + } + + const gfx::IntSize& size = aTextureClient->GetSize(); + // This buffer would be used later for content rendering. So we choose + // B8G8R8A8 format here. + const gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; + // Avoid to create buffer every time. + if (!mSurfaceForBasic || + size != mSurfaceForBasic->GetSize() || + format != mSurfaceForBasic->GetFormat()) + { + uint32_t stride = gfx::GetAlignedStride<8>(size.width * BytesPerPixel(format)); + mSurfaceForBasic = gfx::Factory::CreateDataSourceSurfaceWithStride(size, format, stride); + } + + const uint8_t* lockedBytes = buffer->GetLockedData(); + gfx::DataSourceSurface::ScopedMap map(mSurfaceForBasic, + gfx::DataSourceSurface::MapType::WRITE); + if (!map.IsMapped()) { + buffer->Unlock(); + return; + } + + memcpy(map.GetData(), lockedBytes, map.GetStride() * mSurfaceForBasic->GetSize().height); + buffer->Unlock(); + + if (mSurfaceForBasic->GetFormat() == gfx::SurfaceFormat::R8G8B8A8 || + mSurfaceForBasic->GetFormat() == gfx::SurfaceFormat::R8G8B8X8) { + gl::SwapRAndBComponents(mSurfaceForBasic); + } +} + +already_AddRefed +AsyncCanvasRenderer::UpdateTarget() +{ + if (!mGLContext) { + return nullptr; + } + + gl::SharedSurface* frontbuffer = nullptr; + gl::GLScreenBuffer* screen = mGLContext->Screen(); + const auto& front = screen->Front(); + if (front) { + frontbuffer = front->Surf(); + } + + if (!frontbuffer) { + return nullptr; + } + + if (frontbuffer->mType == gl::SharedSurfaceType::Basic) { + return nullptr; + } + + const gfx::IntSize& size = frontbuffer->mSize; + // This buffer would be used later for content rendering. So we choose + // B8G8R8A8 format here. + const gfx::SurfaceFormat format = gfx::SurfaceFormat::B8G8R8A8; + uint32_t stride = gfx::GetAlignedStride<8>(size.width * BytesPerPixel(format)); + RefPtr surface = + gfx::Factory::CreateDataSourceSurfaceWithStride(size, format, stride); + + + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + if (!frontbuffer->ReadbackBySharedHandle(surface)) { + return nullptr; + } + + bool needsPremult = frontbuffer->mHasAlpha && !mIsAlphaPremultiplied; + if (needsPremult) { + gfxUtils::PremultiplyDataSurface(surface, surface); + } + + return surface.forget(); +} + +already_AddRefed +AsyncCanvasRenderer::GetSurface() +{ + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mMutex); + if (mSurfaceForBasic) { + // Since SourceSurface isn't thread-safe, we need copy to a new SourceSurface. + RefPtr result = + gfx::Factory::CreateDataSourceSurfaceWithStride(mSurfaceForBasic->GetSize(), + mSurfaceForBasic->GetFormat(), + mSurfaceForBasic->Stride()); + + gfx::DataSourceSurface::ScopedMap srcMap(mSurfaceForBasic, gfx::DataSourceSurface::READ); + gfx::DataSourceSurface::ScopedMap dstMap(result, gfx::DataSourceSurface::WRITE); + + if (NS_WARN_IF(!srcMap.IsMapped()) || + NS_WARN_IF(!dstMap.IsMapped())) { + return nullptr; + } + + memcpy(dstMap.GetData(), + srcMap.GetData(), + srcMap.GetStride() * mSurfaceForBasic->GetSize().height); + return result.forget(); + } else { + return UpdateTarget(); + } +} + +nsresult +AsyncCanvasRenderer::GetInputStream(const char *aMimeType, + const char16_t *aEncoderOptions, + nsIInputStream **aStream) +{ + MOZ_ASSERT(NS_IsMainThread()); + RefPtr surface = GetSurface(); + if (!surface) { + return NS_ERROR_FAILURE; + } + + // Handle y flip. + RefPtr dataSurf = gl::YInvertImageSurface(surface); + + return gfxUtils::GetInputStream(dataSurf, false, aMimeType, aEncoderOptions, aStream); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/AsyncCanvasRenderer.h b/gfx/layers/AsyncCanvasRenderer.h new file mode 100644 index 0000000000..6a84b07035 --- /dev/null +++ b/gfx/layers/AsyncCanvasRenderer.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_LAYERS_ASYNCCANVASRENDERER_H_ +#define MOZILLA_LAYERS_ASYNCCANVASRENDERER_H_ + +#include "LayersTypes.h" +#include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" // for nsAutoPtr, nsRefPtr, etc +#include "nsCOMPtr.h" // for nsCOMPtr + +class nsICanvasRenderingContextInternal; +class nsIInputStream; +class nsIThread; + +namespace mozilla { + +namespace gfx { +class DataSourceSurface; +} + +namespace gl { +class GLContext; +} + +namespace dom { +class HTMLCanvasElement; +} + +namespace layers { + +class CanvasClient; +class TextureClient; + +/** + * Since HTMLCanvasElement and OffscreenCanvas are not thread-safe, we create + * AsyncCanvasRenderer which is thread-safe wrapper object for communicating + * among main, worker and ImageBridgeChild threads. + * + * Each HTMLCanvasElement object is responsible for creating + * AsyncCanvasRenderer object. Once Canvas is transfered to worker, + * OffscreenCanvas will keep reference pointer of this object. + * + * Sometimes main thread needs AsyncCanvasRenderer's result, such as layers + * fallback to BasicLayerManager or calling toDataURL in Javascript. Simply call + * GetSurface() in main thread will readback the result to mSurface. + * + * If layers backend is LAYERS_CLIENT, this object will pass to ImageBridgeChild + * for submitting frames to Compositor. + */ +class AsyncCanvasRenderer final +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncCanvasRenderer) + +public: + AsyncCanvasRenderer(); + + void NotifyElementAboutAttributesChanged(); + void NotifyElementAboutInvalidation(); + + void SetCanvasClient(CanvasClient* aClient); + + void SetWidth(uint32_t aWidth) + { + mWidth = aWidth; + } + + void SetHeight(uint32_t aHeight) + { + mHeight = aHeight; + } + + void SetIsAlphaPremultiplied(bool aIsAlphaPremultiplied) + { + mIsAlphaPremultiplied = aIsAlphaPremultiplied; + } + + // Active thread means the thread which spawns GLContext. + void SetActiveThread(); + void ResetActiveThread(); + + // This will readback surface and return the surface + // in the DataSourceSurface. + // Can be called in main thread only. + already_AddRefed GetSurface(); + + // For SharedSurface_Basic case, before the frame sending to the compositor, + // we readback it to a texture client because SharedSurface_Basic cannot shared. + // We don't want to readback it again here, so just copy the content of that + // texture client here to avoid readback again. + void CopyFromTextureClient(TextureClient *aClient); + + // Readback current WebGL's content and convert it to InputStream. This + // function called GetSurface implicitly and GetSurface handles only get + // called in the main thread. So this function can be called in main thread. + nsresult + GetInputStream(const char *aMimeType, + const char16_t *aEncoderOptions, + nsIInputStream **aStream); + + gfx::IntSize GetSize() const + { + return gfx::IntSize(mWidth, mHeight); + } + + uint64_t GetCanvasClientAsyncID() const + { + return mCanvasClientAsyncID; + } + + CanvasClient* GetCanvasClient() const + { + return mCanvasClient; + } + + already_AddRefed GetActiveThread(); + + // The lifetime is controllered by HTMLCanvasElement. + // Only accessed in main thread. + dom::HTMLCanvasElement* mHTMLCanvasElement; + + // Only accessed in active thread. + nsICanvasRenderingContextInternal* mContext; + + // We need to keep a reference to the context around here, otherwise the + // canvas' surface texture destructor will deref and destroy it too early + // Only accessed in active thread. + RefPtr mGLContext; +private: + + virtual ~AsyncCanvasRenderer(); + + // Readback current WebGL's content and return it as DataSourceSurface. + already_AddRefed UpdateTarget(); + + bool mIsAlphaPremultiplied; + + uint32_t mWidth; + uint32_t mHeight; + uint64_t mCanvasClientAsyncID; + + // The lifetime of this pointer is controlled by OffscreenCanvas + // Can be accessed in active thread and ImageBridge thread. + // But we never accessed it at the same time on both thread. So no + // need to protect this member. + CanvasClient* mCanvasClient; + + // When backend is LAYER_BASIC and SharedSurface type is Basic. + // CanvasClient will readback the GLContext to a TextureClient + // in order to send frame to compositor. To avoid readback again, + // we copy from this TextureClient to this mSurfaceForBasic directly + // by calling CopyFromTextureClient(). + RefPtr mSurfaceForBasic; + + // Protect non thread-safe objects. + Mutex mMutex; + + // Can be accessed in any thread, need protect by mutex. + nsCOMPtr mActiveThread; +}; + +} // namespace layers +} // namespace mozilla + +#endif // MOZILLA_LAYERS_ASYNCCANVASRENDERER_H_ diff --git a/gfx/layers/CopyableCanvasLayer.cpp b/gfx/layers/CopyableCanvasLayer.cpp index be171b4bcb..0fad0aa366 100644 --- a/gfx/layers/CopyableCanvasLayer.cpp +++ b/gfx/layers/CopyableCanvasLayer.cpp @@ -64,6 +64,9 @@ CopyableCanvasLayer::Initialize(const Data& aData) } } else if (aData.mBufferProvider) { mBufferProvider = aData.mBufferProvider; + } else if (aData.mRenderer) { + mAsyncRenderer = aData.mRenderer; + mOriginPos = gl::OriginPos::BottomLeft; } else { MOZ_CRASH("CanvasLayer created without mSurface, mDrawTarget or mGLContext?"); } @@ -80,7 +83,9 @@ CopyableCanvasLayer::IsDataValid(const Data& aData) void CopyableCanvasLayer::UpdateTarget(DrawTarget* aDestTarget) { - if (mBufferProvider) { + if (mAsyncRenderer) { + mSurface = mAsyncRenderer->GetSurface(); + } else if (mBufferProvider) { mSurface = mBufferProvider->GetSnapshot(); } @@ -95,7 +100,7 @@ CopyableCanvasLayer::UpdateTarget(DrawTarget* aDestTarget) return; } - if (mBufferProvider) { + if (mBufferProvider || mAsyncRenderer) { return; } diff --git a/gfx/layers/LayerTreeInvalidation.cpp b/gfx/layers/LayerTreeInvalidation.cpp index 7c1457b11b..ec6a224bae 100644 --- a/gfx/layers/LayerTreeInvalidation.cpp +++ b/gfx/layers/LayerTreeInvalidation.cpp @@ -384,7 +384,7 @@ struct ColorLayerProperties : public LayerPropertiesBase IntRect mBounds; }; -static ImageHost* GetImageHost(ImageLayer* aLayer) +static ImageHost* GetImageHost(Layer* aLayer) { LayerComposite* composite = aLayer->AsLayerComposite(); if (composite) { @@ -464,6 +464,34 @@ struct ImageLayerProperties : public LayerPropertiesBase bool mIsMask; }; +struct CanvasLayerProperties : public LayerPropertiesBase +{ + explicit CanvasLayerProperties(CanvasLayer* aCanvas) + : LayerPropertiesBase(aCanvas) + , mImageHost(GetImageHost(aCanvas)) + { + mFrameID = mImageHost ? mImageHost->GetFrameID() : -1; + } + + virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback, + bool& aGeometryChanged) + { + CanvasLayer* canvasLayer = static_cast(mLayer.get()); + + ImageHost* host = GetImageHost(canvasLayer); + if (host && host->GetFrameID() != mFrameID) { + aGeometryChanged = true; + + return NewTransformedBounds(); + } + + return IntRect(); + } + + RefPtr mImageHost; + int32_t mFrameID; +}; + UniquePtr CloneLayerTreePropertiesInternal(Layer* aRoot, bool aIsMask /* = false */) { @@ -482,6 +510,7 @@ CloneLayerTreePropertiesInternal(Layer* aRoot, bool aIsMask /* = false */) case Layer::TYPE_IMAGE: return MakeUnique(static_cast(aRoot), aIsMask); case Layer::TYPE_CANVAS: + return MakeUnique(static_cast(aRoot)); case Layer::TYPE_READBACK: case Layer::TYPE_SHADOW: case Layer::TYPE_PAINTED: diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index f8f54128ad..ae9e39800d 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -25,6 +25,7 @@ #include "mozilla/gfx/2D.h" // for DrawTarget #include "mozilla/gfx/BaseSize.h" // for BaseSize #include "mozilla/gfx/Matrix.h" // for Matrix4x4 +#include "mozilla/layers/AsyncCanvasRenderer.h" #include "mozilla/layers/CompositableClient.h" // for CompositableClient #include "mozilla/layers/Compositor.h" // for Compositor #include "mozilla/layers/CompositorTypes.h" @@ -2128,6 +2129,19 @@ ColorLayer::DumpPacket(layerscope::LayersPacket* aPacket, const void* aParent) layer->set_color(mColor.ToABGR()); } +CanvasLayer::CanvasLayer(LayerManager* aManager, void* aImplData) + : Layer(aManager, aImplData) + , mPreTransCallback(nullptr) + , mPreTransCallbackData(nullptr) + , mPostTransCallback(nullptr) + , mPostTransCallbackData(nullptr) + , mFilter(gfx::Filter::GOOD) + , mDirty(false) +{} + +CanvasLayer::~CanvasLayer() +{} + void CanvasLayer::PrintInfo(std::stringstream& aStream, const char* aPrefix) { diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index 29ad35fb00..71bde4c705 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -73,6 +73,7 @@ namespace layers { class Animation; class AnimationData; +class AsyncCanvasRenderer; class AsyncPanZoomController; class ClientLayerManager; class Layer; @@ -2272,15 +2273,17 @@ public: Data() : mBufferProvider(nullptr) , mGLContext(nullptr) + , mRenderer(nullptr) , mFrontbufferGLTex(0) , mSize(0,0) , mHasAlpha(false) , mIsGLAlphaPremult(true) { } - // One of these two must be specified for Canvas2D, but never both + // One of these three must be specified for Canvas2D, but never more than one PersistentBufferProvider* mBufferProvider; // A BufferProvider for the Canvas contents mozilla::gl::GLContext* mGLContext; // or this, for GL. + AsyncCanvasRenderer* mRenderer; // or this, for OffscreenCanvas // Frontbuffer override uint32_t mFrontbufferGLTex; @@ -2396,16 +2399,14 @@ public: ComputeEffectiveTransformForMaskLayers(aTransformToSurface); } + bool GetIsAsyncRenderer() const + { + return !!mAsyncRenderer; + } + protected: - CanvasLayer(LayerManager* aManager, void* aImplData) - : Layer(aManager, aImplData) - , mPreTransCallback(nullptr) - , mPreTransCallbackData(nullptr) - , mPostTransCallback(nullptr) - , mPostTransCallbackData(nullptr) - , mFilter(gfx::Filter::GOOD) - , mDirty(false) - {} + CanvasLayer(LayerManager* aManager, void* aImplData); + virtual ~CanvasLayer(); virtual void PrintInfo(std::stringstream& aStream, const char* aPrefix) override; @@ -2427,6 +2428,7 @@ protected: DidTransactionCallback mPostTransCallback; void* mPostTransCallbackData; gfx::Filter mFilter; + RefPtr mAsyncRenderer; private: /** diff --git a/gfx/layers/apz/src/AsyncPanZoomAnimation.h b/gfx/layers/apz/src/AsyncPanZoomAnimation.h index fd0574001a..27caa20da9 100644 --- a/gfx/layers/apz/src/AsyncPanZoomAnimation.h +++ b/gfx/layers/apz/src/AsyncPanZoomAnimation.h @@ -10,6 +10,7 @@ #include "base/message_loop.h" #include "mozilla/RefPtr.h" #include "mozilla/TimeStamp.h" +#include "mozilla/Vector.h" #include "FrameMetrics.h" #include "nsISupportsImpl.h" diff --git a/gfx/layers/apz/src/WheelScrollAnimation.h b/gfx/layers/apz/src/WheelScrollAnimation.h index 64f0e33057..07bc8e783e 100644 --- a/gfx/layers/apz/src/WheelScrollAnimation.h +++ b/gfx/layers/apz/src/WheelScrollAnimation.h @@ -8,7 +8,9 @@ #define mozilla_layers_WheelScrollAnimation_h_ #include "AsyncPanZoomAnimation.h" +#include "AsyncPanZoomController.h" #include "AsyncScrollBase.h" +#include "gfxPrefs.h" namespace mozilla { namespace layers { diff --git a/gfx/layers/basic/BasicCanvasLayer.cpp b/gfx/layers/basic/BasicCanvasLayer.cpp index 72ae95a037..ec9e94e555 100644 --- a/gfx/layers/basic/BasicCanvasLayer.cpp +++ b/gfx/layers/basic/BasicCanvasLayer.cpp @@ -4,6 +4,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BasicCanvasLayer.h" +#include "AsyncCanvasRenderer.h" #include "basic/BasicLayers.h" // for BasicLayerManager #include "basic/BasicLayersImpl.h" // for GetEffectiveOperator #include "mozilla/mozalloc.h" // for operator new diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp index cd2222c602..5b722c63aa 100644 --- a/gfx/layers/client/CanvasClient.cpp +++ b/gfx/layers/client/CanvasClient.cpp @@ -13,6 +13,7 @@ #include "gfxPlatform.h" // for gfxPlatform #include "GLReadTexImageHelper.h" #include "mozilla/gfx/BaseSize.h" // for BaseSize +#include "mozilla/layers/AsyncCanvasRenderer.h" #include "mozilla/layers/CompositableForwarder.h" #include "mozilla/layers/CompositorChild.h" // for CompositorChild #include "mozilla/layers/GrallocTextureClient.h" @@ -38,14 +39,32 @@ CanvasClient::CreateCanvasClient(CanvasClientType aType, switch (aType) { case CanvasClientTypeShSurf: return MakeAndAddRef(aForwarder, aFlags); - break; - + case CanvasClientAsync: + return MakeAndAddRef(aForwarder, aFlags); default: return MakeAndAddRef(aForwarder, aFlags); break; } } +void +CanvasClientBridge::UpdateAsync(AsyncCanvasRenderer* aRenderer) +{ + if (!GetForwarder() || !mLayer || !aRenderer || + !aRenderer->GetCanvasClient()) { + return; + } + + uint64_t asyncID = aRenderer->GetCanvasClientAsyncID(); + if (asyncID == 0 || mAsyncID == asyncID) { + return; + } + + static_cast(GetForwarder()) + ->AttachAsyncCompositable(asyncID, mLayer); + mAsyncID = asyncID; +} + void CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) { @@ -106,6 +125,7 @@ CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); t->mTextureClient = mBuffer; t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mBuffer->GetSize()); + t->mFrameID = mFrameID; GetForwarder()->UseTextures(this, textures); mBuffer->SyncWithObject(GetForwarder()->GetSyncObject()); } @@ -321,13 +341,38 @@ CloneSurface(gl::SharedSurface* src, gl::SurfaceFactory* factory) void CanvasClientSharedSurface::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) { - auto gl = aLayer->mGLContext; + Renderer renderer; + renderer.construct(aLayer); + UpdateRenderer(aSize, renderer); +} + +void +CanvasClientSharedSurface::UpdateAsync(AsyncCanvasRenderer* aRenderer) +{ + Renderer renderer; + renderer.construct(aRenderer); + UpdateRenderer(aRenderer->GetSize(), renderer); +} + +void +CanvasClientSharedSurface::UpdateRenderer(gfx::IntSize aSize, Renderer& aRenderer) +{ + GLContext* gl = nullptr; + ClientCanvasLayer* layer = nullptr; + AsyncCanvasRenderer* asyncRenderer = nullptr; + if (aRenderer.constructed()) { + layer = aRenderer.ref(); + gl = layer->mGLContext; + } else { + asyncRenderer = aRenderer.ref(); + gl = asyncRenderer->mGLContext; + } gl->MakeCurrent(); RefPtr newFront; - if (aLayer->mGLFrontbuffer) { - mShSurfClient = CloneSurface(aLayer->mGLFrontbuffer.get(), aLayer->mFactory.get()); + if (layer && layer->mGLFrontbuffer) { + mShSurfClient = CloneSurface(layer->mGLFrontbuffer.get(), layer->mFactory.get()); if (!mShSurfClient) { gfxCriticalError() << "Invalid canvas front buffer"; return; @@ -351,14 +396,30 @@ CanvasClientSharedSurface::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) bool needsReadback = (surf->mType == SharedSurfaceType::Basic); if (needsReadback) { - TextureFlags flags = aLayer->Flags() | - TextureFlags::IMMUTABLE; + TextureFlags flags = TextureFlags::IMMUTABLE; + + CompositableForwarder* shadowForwarder = nullptr; + if (layer) { + flags |= layer->Flags(); + shadowForwarder = layer->ClientManager()->AsShadowForwarder(); + } else { + MOZ_ASSERT(asyncRenderer); + flags |= mTextureFlags; + shadowForwarder = GetForwarder(); + } - auto manager = aLayer->ClientManager(); - auto shadowForwarder = manager->AsShadowForwarder(); auto layersBackend = shadowForwarder->GetCompositorBackendType(); mReadbackClient = TexClientFromReadback(surf, forwarder, flags, layersBackend); + if (asyncRenderer) { + // Above codes will readback the GLContext to mReadbackClient + // in order to send frame to compositor. We copy from this + // TextureClient directly by calling CopyFromTextureClient(). + // Therefore, if main-thread want the content of GLContext, + // it don't have to readback it again. + asyncRenderer->CopyFromTextureClient(mReadbackClient); + } + newFront = mReadbackClient; } else { mReadbackClient = nullptr; @@ -371,6 +432,14 @@ CanvasClientSharedSurface::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) return; } + mNewFront = newFront; +} + +void +CanvasClientSharedSurface::Updated() +{ + auto forwarder = GetForwarder(); + #ifndef MOZ_WIDGET_GONK if (mFront) { if (mFront->GetFlags() & TextureFlags::RECYCLE) { @@ -385,12 +454,13 @@ CanvasClientSharedSurface::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) // - Call RemoveTexture() after newFront's UseTextures() call. // It could improve performance of Host side's EGL handling on gonk AutoRemoveTexture autoRemove(this); - if (mFront && mFront != newFront) { + if (mFront && mFront != mNewFront) { autoRemove.mTexture = mFront; } #endif - mFront = newFront; + mFront = mNewFront; + mNewFront = nullptr; // Add the new TexClient. MOZ_ALWAYS_TRUE( AddTextureClient(mFront) ); @@ -399,6 +469,7 @@ CanvasClientSharedSurface::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) CompositableForwarder::TimedTextureClient* t = textures.AppendElement(); t->mTextureClient = mFront; t->mPictureRect = nsIntRect(nsIntPoint(0, 0), mFront->GetSize()); + t->mFrameID = mFrameID; forwarder->UseTextures(this, textures); } @@ -406,6 +477,7 @@ void CanvasClientSharedSurface::ClearSurfaces() { mFront = nullptr; + mNewFront = nullptr; mShSurfClient = nullptr; mReadbackClient = nullptr; } diff --git a/gfx/layers/client/CanvasClient.h b/gfx/layers/client/CanvasClient.h index 5bdeb7f3f2..7a8a174851 100644 --- a/gfx/layers/client/CanvasClient.h +++ b/gfx/layers/client/CanvasClient.h @@ -13,6 +13,11 @@ #include "mozilla/layers/CompositorTypes.h" // for TextureInfo, etc #include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor #include "mozilla/layers/TextureClient.h" // for TextureClient, etc + +// Fix X11 header brain damage that conflicts with MaybeOneOf::None +#undef None +#include "mozilla/MaybeOneOf.h" + #include "mozilla/mozalloc.h" // for operator delete #include "mozilla/gfx/Point.h" // for IntSize @@ -21,8 +26,10 @@ namespace mozilla { namespace layers { +class AsyncCanvasRenderer; class ClientCanvasLayer; class CompositableForwarder; +class ShadowableLayer; class SharedSurfaceTextureClient; /** @@ -31,6 +38,8 @@ class SharedSurfaceTextureClient; class CanvasClient : public CompositableClient { public: + typedef MaybeOneOf Renderer; + /** * Creates, configures, and returns a new canvas client. If necessary, a * message will be sent to the compositor to create a corresponding image @@ -40,6 +49,7 @@ public: CanvasClientSurface, CanvasClientGLContext, CanvasClientTypeShSurf, + CanvasClientAsync, // webgl on workers }; static already_AddRefed CreateCanvasClient(CanvasClientType aType, CompositableForwarder* aFwd, @@ -47,6 +57,7 @@ public: CanvasClient(CompositableForwarder* aFwd, TextureFlags aFlags) : CompositableClient(aFwd, aFlags) + , mFrameID(0) { mTextureFlags = aFlags; } @@ -57,7 +68,18 @@ public: virtual void Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) = 0; + virtual bool AddTextureClient(TextureClient* aTexture) override + { + ++mFrameID; + return CompositableClient::AddTextureClient(aTexture); + } + + virtual void UpdateAsync(AsyncCanvasRenderer* aRenderer) {} + virtual void Updated() { } + +protected: + int32_t mFrameID; }; // Used for 2D canvases and WebGL canvas on non-GL systems where readback is requried. @@ -85,7 +107,7 @@ public: virtual bool AddTextureClient(TextureClient* aTexture) override { MOZ_ASSERT((mTextureFlags & aTexture->GetFlags()) == mTextureFlags); - return CompositableClient::AddTextureClient(aTexture); + return CanvasClient::AddTextureClient(aTexture); } virtual void OnDetach() override @@ -111,6 +133,7 @@ private: RefPtr mShSurfClient; RefPtr mReadbackClient; RefPtr mFront; + RefPtr mNewFront; void ClearSurfaces(); @@ -130,12 +153,54 @@ public: virtual void Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) override; + void UpdateRenderer(gfx::IntSize aSize, Renderer& aRenderer); + + virtual void UpdateAsync(AsyncCanvasRenderer* aRenderer) override; + + virtual void Updated() override; virtual void OnDetach() override { ClearSurfaces(); } }; +/** + * Used for OMT uploads using the image bridge protocol. + * Actual CanvasClient is on the ImageBridgeChild thread, so we + * only forward its AsyncID here + */ +class CanvasClientBridge final : public CanvasClient +{ +public: + CanvasClientBridge(CompositableForwarder* aLayerForwarder, + TextureFlags aFlags) + : CanvasClient(aLayerForwarder, aFlags) + , mAsyncID(0) + , mLayer(nullptr) + { + } + + TextureInfo GetTextureInfo() const override + { + return TextureInfo(CompositableType::IMAGE); + } + + virtual void Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) override + { + } + + virtual void UpdateAsync(AsyncCanvasRenderer* aRenderer) override; + + void SetLayer(ShadowableLayer* aLayer) + { + mLayer = aLayer; + } + +protected: + uint64_t mAsyncID; + ShadowableLayer* mLayer; +}; + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/client/ClientCanvasLayer.cpp b/gfx/layers/client/ClientCanvasLayer.cpp index 6334debb98..1b2f2326bf 100644 --- a/gfx/layers/client/ClientCanvasLayer.cpp +++ b/gfx/layers/client/ClientCanvasLayer.cpp @@ -11,6 +11,7 @@ #include "SharedSurfaceGL.h" // for SurfaceFactory_GLTexture, etc #include "ClientLayerManager.h" // for ClientLayerManager, etc #include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/AsyncCanvasRenderer.h" #include "mozilla/layers/CompositorTypes.h" #include "mozilla/layers/LayersTypes.h" #include "nsCOMPtr.h" // for already_AddRefed @@ -19,24 +20,6 @@ #include "nsXULAppAPI.h" // for XRE_GetProcessType, etc #include "gfxPrefs.h" // for WebGLForceLayersReadback -#ifdef XP_WIN -#include "SharedSurfaceANGLE.h" // for SurfaceFactory_ANGLEShareHandle -#include "gfxWindowsPlatform.h" -#endif - -#ifdef MOZ_WIDGET_GONK -#include "SharedSurfaceGralloc.h" -#endif - -#ifdef XP_MACOSX -#include "SharedSurfaceIO.h" -#endif - -#ifdef GL_PROVIDER_GLX -#include "GLXLibrary.h" -#include "SharedSurfaceGLX.h" -#endif - using namespace mozilla::gfx; using namespace mozilla::gl; @@ -82,46 +65,7 @@ ClientCanvasLayer::Initialize(const Data& aData) mFlags |= TextureFlags::NON_PREMULTIPLIED; } - UniquePtr factory; - - if (!gfxPrefs::WebGLForceLayersReadback()) { - switch (forwarder->GetCompositorBackendType()) { - case mozilla::layers::LayersBackend::LAYERS_OPENGL: { -#if defined(XP_MACOSX) - factory = SurfaceFactory_IOSurface::Create(mGLContext, caps, forwarder, mFlags); -#elif defined(MOZ_WIDGET_GONK) - factory = MakeUnique(mGLContext, caps, forwarder, mFlags); -#elif defined(GL_PROVIDER_GLX) - if (sGLXLibrary.UseSurfaceSharing()) - factory = SurfaceFactory_GLXDrawable::Create(mGLContext, caps, forwarder, mFlags); -#else - if (mGLContext->GetContextType() == GLContextType::EGL) { - if (XRE_IsParentProcess()) { - factory = SurfaceFactory_EGLImage::Create(mGLContext, caps, forwarder, - mFlags); - } - } -#endif - break; - } - case mozilla::layers::LayersBackend::LAYERS_D3D11: { -#ifdef XP_WIN - // Enable surface sharing only if ANGLE and compositing devices - // are both WARP or both not WARP - if (mGLContext->IsANGLE() && - (mGLContext->IsWARP() == gfxWindowsPlatform::GetPlatform()->IsWARP()) && - gfxWindowsPlatform::GetPlatform()->CompositorD3D11TextureSharingWorks()) - { - factory = SurfaceFactory_ANGLEShareHandle::Create(mGLContext, caps, forwarder, - mFlags); - } -#endif - break; - } - default: - break; - } - } + UniquePtr factory = GLScreenBuffer::CreateFactory(mGLContext, caps, forwarder, mFlags); if (mGLFrontbuffer) { // We're using a source other than the one in the default screen. @@ -145,11 +89,6 @@ ClientCanvasLayer::RenderLayer() RenderMaskLayers(this); - if (!IsDirty()) { - return; - } - Painted(); - if (!mCanvasClient) { TextureFlags flags = TextureFlags::IMMEDIATE_UPLOAD; if (mOriginPos == gl::OriginPos::BottomLeft) { @@ -172,11 +111,24 @@ ClientCanvasLayer::RenderLayer() return; } if (HasShadow()) { - mCanvasClient->Connect(); - ClientManager()->AsShadowForwarder()->Attach(mCanvasClient, this); + if (mAsyncRenderer) { + static_cast(mCanvasClient.get())->SetLayer(this); + } else { + mCanvasClient->Connect(); + ClientManager()->AsShadowForwarder()->Attach(mCanvasClient, this); + } } } + if (mCanvasClient && mAsyncRenderer) { + mCanvasClient->UpdateAsync(mAsyncRenderer); + } + + if (!IsDirty()) { + return; + } + Painted(); + FirePreTransactionCallback(); mCanvasClient->Update(gfx::IntSize(mBounds.width, mBounds.height), this); @@ -189,6 +141,10 @@ ClientCanvasLayer::RenderLayer() CanvasClient::CanvasClientType ClientCanvasLayer::GetCanvasClientType() { + if (mAsyncRenderer) { + return CanvasClient::CanvasClientAsync; + } + if (mGLContext) { return CanvasClient::CanvasClientTypeShSurf; } diff --git a/gfx/layers/client/ClientCanvasLayer.h b/gfx/layers/client/ClientCanvasLayer.h index 9167a1d2b3..d2308197b1 100644 --- a/gfx/layers/client/ClientCanvasLayer.h +++ b/gfx/layers/client/ClientCanvasLayer.h @@ -97,7 +97,6 @@ protected: TextureFlags mFlags; - friend class DeprecatedCanvasClient2D; friend class CanvasClient2D; friend class CanvasClientSharedSurface; }; diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp index a51cd8976f..79e3574b34 100644 --- a/gfx/layers/client/TextureClient.cpp +++ b/gfx/layers/client/TextureClient.cpp @@ -169,6 +169,7 @@ TextureChild::ActorDestroy(ActorDestroyReason why) { if (mTextureClient) { mTextureClient->mActor = nullptr; + mTextureClient->mAllocator = nullptr; } mWaitForRecycle = nullptr; mKeep = nullptr; diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp index 42e9294b93..98874c57ec 100644 --- a/gfx/layers/composite/TextureHost.cpp +++ b/gfx/layers/composite/TextureHost.cpp @@ -6,6 +6,7 @@ #include "TextureHost.h" #include "CompositableHost.h" // for CompositableHost +#include "LayerScope.h" // for LayerScope::ContentChanged #include "LayersLogging.h" // for AppendToString #include "mozilla/gfx/2D.h" // for DataSourceSurface, Factory #include "mozilla/ipc/Shmem.h" // for Shmem diff --git a/gfx/layers/ipc/ImageBridgeChild.cpp b/gfx/layers/ipc/ImageBridgeChild.cpp index 643a7a1700..eb22d711ca 100644 --- a/gfx/layers/ipc/ImageBridgeChild.cpp +++ b/gfx/layers/ipc/ImageBridgeChild.cpp @@ -21,6 +21,7 @@ #include "mozilla/ipc/MessageChannel.h" // for MessageChannel, etc #include "mozilla/ipc/Transport.h" // for Transport #include "mozilla/gfx/Point.h" // for IntSize +#include "mozilla/layers/AsyncCanvasRenderer.h" #include "mozilla/media/MediaSystemResourceManager.h" // for MediaSystemResourceManager #include "mozilla/media/MediaSystemResourceManagerChild.h" // for MediaSystemResourceManagerChild #include "mozilla/layers/CompositableClient.h" // for CompositableChild, etc @@ -236,6 +237,19 @@ static void CreateImageClientSync(RefPtr* result, barrier->NotifyAll(); } +// dispatched function +static void CreateCanvasClientSync(ReentrantMonitor* aBarrier, + CanvasClient::CanvasClientType aType, + TextureFlags aFlags, + RefPtr* const outResult, + bool* aDone) +{ + ReentrantMonitorAutoEnter autoMon(*aBarrier); + *outResult = sImageBridgeChildSingleton->CreateCanvasClientNow(aType, aFlags); + *aDone = true; + aBarrier->NotifyAll(); +} + static void ConnectImageBridge(ImageBridgeChild * child, ImageBridgeParent * parent) { MessageLoop *parentMsgLoop = parent->GetMessageLoop(); @@ -274,9 +288,14 @@ ImageBridgeChild::Connect(CompositableClient* aCompositable, MOZ_ASSERT(aCompositable); MOZ_ASSERT(!mShuttingDown); uint64_t id = 0; + + PImageContainerChild* imageContainerChild = nullptr; + if (aImageContainer) + imageContainerChild = aImageContainer->GetPImageContainerChild(); + PCompositableChild* child = SendPCompositableConstructor(aCompositable->GetTextureInfo(), - aImageContainer->GetPImageContainerChild(), &id); + imageContainerChild, &id); MOZ_ASSERT(child); aCompositable->InitIPDLActor(child, id); } @@ -379,6 +398,35 @@ void ImageBridgeChild::DispatchReleaseImageClient(ImageClient* aClient, NewRunnableFunction(&ReleaseImageClientNow, aClient, aChild)); } +static void ReleaseCanvasClientNow(CanvasClient* aClient) +{ + MOZ_ASSERT(InImageBridgeChildThread()); + aClient->Release(); +} + +// static +void ImageBridgeChild::DispatchReleaseCanvasClient(CanvasClient* aClient) +{ + if (!aClient) { + return; + } + + if (!IsCreated()) { + // CompositableClient::Release should normally happen in the ImageBridgeChild + // thread because it usually generate some IPDL messages. + // However, if we take this branch it means that the ImageBridgeChild + // has already shut down, along with the CompositableChild, which means no + // message will be sent and it is safe to run this code from any thread. + MOZ_ASSERT(aClient->GetIPDLActor() == nullptr); + aClient->Release(); + return; + } + + sImageBridgeChildSingleton->GetMessageLoop()->PostTask( + FROM_HERE, + NewRunnableFunction(&ReleaseCanvasClientNow, aClient)); +} + static void ReleaseTextureClientNow(TextureClient* aClient) { MOZ_ASSERT(InImageBridgeChildThread()); @@ -417,7 +465,7 @@ static void UpdateImageClientNow(ImageClient* aClient, ImageContainer* aContaine sImageBridgeChildSingleton->EndTransaction(); } -//static +// static void ImageBridgeChild::DispatchImageClientUpdate(ImageClient* aClient, ImageContainer* aContainer) { @@ -437,6 +485,51 @@ void ImageBridgeChild::DispatchImageClientUpdate(ImageClient* aClient, RefPtr >(&UpdateImageClientNow, aClient, aContainer)); } +static void UpdateAsyncCanvasRendererSync(AsyncCanvasRenderer* aWrapper, + ReentrantMonitor* aBarrier, + bool* const outDone) +{ + ImageBridgeChild::UpdateAsyncCanvasRendererNow(aWrapper); + + ReentrantMonitorAutoEnter autoMon(*aBarrier); + *outDone = true; + aBarrier->NotifyAll(); +} + +// static +void ImageBridgeChild::UpdateAsyncCanvasRenderer(AsyncCanvasRenderer* aWrapper) +{ + aWrapper->GetCanvasClient()->UpdateAsync(aWrapper); + + if (InImageBridgeChildThread()) { + UpdateAsyncCanvasRendererNow(aWrapper); + return; + } + + ReentrantMonitor barrier("UpdateAsyncCanvasRenderer Lock"); + ReentrantMonitorAutoEnter autoMon(barrier); + bool done = false; + + sImageBridgeChildSingleton->GetMessageLoop()->PostTask( + FROM_HERE, + NewRunnableFunction(&UpdateAsyncCanvasRendererSync, aWrapper, &barrier, &done)); + + // should stop the thread until the CanvasClient has been created on + // the other thread + while (!done) { + barrier.Wait(); + } +} + +// static +void ImageBridgeChild::UpdateAsyncCanvasRendererNow(AsyncCanvasRenderer* aWrapper) +{ + MOZ_ASSERT(aWrapper); + sImageBridgeChildSingleton->BeginTransaction(); + aWrapper->GetCanvasClient()->Updated(); + sImageBridgeChildSingleton->EndTransaction(); +} + static void FlushAllImagesSync(ImageClient* aClient, ImageContainer* aContainer, AsyncTransactionWaiter* aWaiter) { @@ -454,7 +547,7 @@ static void FlushAllImagesSync(ImageClient* aClient, ImageContainer* aContainer, aWaiter->DecrementWaitCount(); } -//static +// static void ImageBridgeChild::FlushAllImages(ImageClient* aClient, ImageContainer* aContainer) { @@ -710,6 +803,42 @@ ImageBridgeChild::CreateImageClientNow(CompositableType aType, return client.forget(); } +already_AddRefed +ImageBridgeChild::CreateCanvasClient(CanvasClient::CanvasClientType aType, + TextureFlags aFlag) +{ + if (InImageBridgeChildThread()) { + return CreateCanvasClientNow(aType, aFlag); + } + ReentrantMonitor barrier("CreateCanvasClient Lock"); + ReentrantMonitorAutoEnter autoMon(barrier); + bool done = false; + + RefPtr result = nullptr; + GetMessageLoop()->PostTask(FROM_HERE, + NewRunnableFunction(&CreateCanvasClientSync, + &barrier, aType, aFlag, &result, &done)); + // should stop the thread until the CanvasClient has been created on the + // other thread + while (!done) { + barrier.Wait(); + } + return result.forget(); +} + +already_AddRefed +ImageBridgeChild::CreateCanvasClientNow(CanvasClient::CanvasClientType aType, + TextureFlags aFlag) +{ + RefPtr client + = CanvasClient::CreateCanvasClient(aType, this, aFlag); + MOZ_ASSERT(client, "failed to create CanvasClient"); + if (client) { + client->Connect(); + } + return client.forget(); +} + bool ImageBridgeChild::AllocUnsafeShmem(size_t aSize, ipc::SharedMemory::SharedMemoryType aType, diff --git a/gfx/layers/ipc/ImageBridgeChild.h b/gfx/layers/ipc/ImageBridgeChild.h index c57c40a9a5..4f834b7c78 100644 --- a/gfx/layers/ipc/ImageBridgeChild.h +++ b/gfx/layers/ipc/ImageBridgeChild.h @@ -12,6 +12,7 @@ #include "mozilla/RefPtr.h" // for already_AddRefed #include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc #include "mozilla/layers/AsyncTransactionTracker.h" // for AsyncTransactionTrackerHolder +#include "mozilla/layers/CanvasClient.h" #include "mozilla/layers/CompositableForwarder.h" #include "mozilla/layers/CompositorTypes.h" #include "mozilla/layers/PImageBridgeChild.h" @@ -32,6 +33,7 @@ class Shmem; namespace layers { +class AsyncCanvasRenderer; class AsyncTransactionTracker; class ImageClient; class ImageContainer; @@ -210,12 +212,20 @@ public: ImageContainer* aImageContainer); already_AddRefed CreateImageClientNow(CompositableType aType, ImageContainer* aImageContainer); + already_AddRefed CreateCanvasClient(CanvasClient::CanvasClientType aType, + TextureFlags aFlag); + already_AddRefed CreateCanvasClientNow(CanvasClient::CanvasClientType aType, + TextureFlags aFlag); static void DispatchReleaseImageClient(ImageClient* aClient, PImageContainerChild* aChild = nullptr); + static void DispatchReleaseCanvasClient(CanvasClient* aClient); static void DispatchReleaseTextureClient(TextureClient* aClient); static void DispatchImageClientUpdate(ImageClient* aClient, ImageContainer* aContainer); + static void UpdateAsyncCanvasRenderer(AsyncCanvasRenderer* aClient); + static void UpdateAsyncCanvasRendererNow(AsyncCanvasRenderer* aClient); + /** * Flush all Images sent to CompositableHost. */ diff --git a/gfx/layers/ipc/PImageBridge.ipdl b/gfx/layers/ipc/PImageBridge.ipdl index c23350f022..7032631000 100644 --- a/gfx/layers/ipc/PImageBridge.ipdl +++ b/gfx/layers/ipc/PImageBridge.ipdl @@ -58,7 +58,7 @@ parent: sync Stop(); sync PCompositable(TextureInfo aInfo, - PImageContainer aImageContainer) returns (uint64_t id); + nullable PImageContainer aImageContainer) returns (uint64_t id); async PTexture(SurfaceDescriptor aSharedData, LayersBackend aBackend, TextureFlags aTextureFlags); async PMediaSystemResourceManager(); async PImageContainer(); diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 99937af022..4c140544ff 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -105,6 +105,7 @@ EXPORTS.mozilla.layers += [ 'apz/util/ChromeProcessController.h', 'apz/util/DoubleTapToZoom.h', 'apz/util/InputAPZContext.h', + 'AsyncCanvasRenderer.h', 'AtomicRefCountedWithFinalize.h', 'AxisPhysicsModel.h', 'AxisPhysicsMSDModel.h', @@ -250,6 +251,7 @@ UNIFIED_SOURCES += [ 'apz/util/ChromeProcessController.cpp', 'apz/util/DoubleTapToZoom.cpp', 'apz/util/InputAPZContext.cpp', + 'AsyncCanvasRenderer.cpp', 'AxisPhysicsModel.cpp', 'AxisPhysicsMSDModel.cpp', 'basic/BasicCanvasLayer.cpp', diff --git a/gfx/src/gfxCrashReporterUtils.cpp b/gfx/src/gfxCrashReporterUtils.cpp index e25ab9a037..6286d5fc42 100644 --- a/gfx/src/gfxCrashReporterUtils.cpp +++ b/gfx/src/gfxCrashReporterUtils.cpp @@ -84,6 +84,22 @@ public: } }; +class AppendAppNotesRunnable : public nsCancelableRunnable { +public: + explicit AppendAppNotesRunnable(nsAutoCString aFeatureStr) + : mFeatureString(aFeatureStr) + { + } + + NS_IMETHOD Run() override { + CrashReporter::AppendAppNotesToCrashReport(mFeatureString); + return NS_OK; + } + +private: + nsCString mFeatureString; +}; + void ScopedGfxFeatureReporter::WriteAppNote(char statusChar) { @@ -102,7 +118,8 @@ ScopedGfxFeatureReporter::WriteAppNote(char statusChar) if (!gFeaturesAlreadyReported->Contains(featureString)) { gFeaturesAlreadyReported->AppendElement(featureString); - CrashReporter::AppendAppNotesToCrashReport(featureString); + nsCOMPtr r = new AppendAppNotesRunnable(featureString); + NS_DispatchToMainThread(r); } } diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index a4d86b4e94..7dfc1336b2 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -251,6 +251,7 @@ private: DECL_GFX_PREF(Once, "gfx.max-alloc-size", MaxAllocSize, int32_t, (int32_t)500000000); DECL_GFX_PREF(Once, "gfx.max-texture-size", MaxTextureSize, int32_t, (int32_t)32767); DECL_GFX_PREF(Live, "gfx.perf-warnings.enabled", PerfWarnings, bool, false); + DECL_GFX_PREF(Live, "gfx.SurfaceTexture.detach.enabled", SurfaceTextureDetachEnabled, bool, true); DECL_GFX_PREF(Live, "gfx.testing.device-reset", DeviceResetForTesting, int32_t, 0); DECL_GFX_PREF(Live, "gfx.testing.device-fail", DeviceFailForTesting, bool, false); DECL_GFX_PREF(Once, "gfx.touch.resample", TouchResampling, bool, false); @@ -399,12 +400,32 @@ private: DECL_GFX_PREF(Live, "test.mousescroll", MouseScrollTestingEnabled, bool, false); DECL_GFX_PREF(Live, "ui.click_hold_context_menus.delay", UiClickHoldContextMenusDelay, int32_t, 500); - DECL_GFX_PREF(Once, "webgl.angle.force-d3d11", WebGLANGLEForceD3D11, bool, false); - DECL_GFX_PREF(Once, "webgl.angle.try-d3d11", WebGLANGLETryD3D11, bool, false); + + // WebGL (for pref access from Worker threads) + DECL_GFX_PREF(Live, "webgl.all-angle-options", WebGLAllANGLEOptions, bool, false); + DECL_GFX_PREF(Live, "webgl.angle.force-d3d11", WebGLANGLEForceD3D11, bool, false); + DECL_GFX_PREF(Live, "webgl.angle.try-d3d11", WebGLANGLETryD3D11, bool, false); DECL_GFX_PREF(Once, "webgl.angle.force-warp", WebGLANGLEForceWARP, bool, false); + DECL_GFX_PREF(Live, "webgl.bypass-shader-validation", WebGLBypassShaderValidator, bool, true); + DECL_GFX_PREF(Live, "webgl.can-lose-context-in-foreground", WebGLCanLoseContextInForeground, bool, true); + DECL_GFX_PREF(Live, "webgl.default-no-alpha", WebGLDefaultNoAlpha, bool, false); + DECL_GFX_PREF(Live, "webgl.disable-angle", WebGLDisableANGLE, bool, false); + DECL_GFX_PREF(Live, "webgl.disable-extensions", WebGLDisableExtensions, bool, false); + DECL_GFX_PREF(Live, "webgl.disable-fail-if-major-performance-caveat", WebGLDisableFailIfMajorPerformanceCaveat, bool, false); + DECL_GFX_PREF(Live, "webgl.disabled", WebGLDisabled, bool, false); + + DECL_GFX_PREF(Live, "webgl.enable-draft-extensions", WebGLDraftExtensionsEnabled, bool, false); + DECL_GFX_PREF(Live, "webgl.enable-privileged-extensions", WebGLPrivilegedExtensionsEnabled, bool, false); + DECL_GFX_PREF(Live, "webgl.force-enabled", WebGLForceEnabled, bool, false); DECL_GFX_PREF(Once, "webgl.force-layers-readback", WebGLForceLayersReadback, bool, false); + DECL_GFX_PREF(Live, "webgl.lose-context-on-memory-pressure", WebGLLoseContextOnMemoryPressure, bool, false); + DECL_GFX_PREF(Live, "webgl.max-warnings-per-context", WebGLMaxWarningsPerContext, uint32_t, 32); + DECL_GFX_PREF(Live, "webgl.min_capability_mode", WebGLMinCapabilityMode, bool, false); + DECL_GFX_PREF(Live, "webgl.msaa-force", WebGLForceMSAA, bool, false); + DECL_GFX_PREF(Live, "webgl.prefer-16bpp", WebGLPrefer16bpp, bool, false); + DECL_GFX_PREF(Live, "webgl.restore-context-when-visible", WebGLRestoreWhenVisible, bool, true); // WARNING: // Please make sure that you've added your new preference to the list above in alphabetical order. diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index 6b92bbd498..f3e105361d 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -12,6 +12,9 @@ #include "gfxDrawable.h" #include "imgIEncoder.h" #include "mozilla/Base64.h" +#include "mozilla/dom/ImageEncoder.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/DataSurfaceHelpers.h" #include "mozilla/gfx/Logging.h" @@ -21,6 +24,7 @@ #include "nsComponentManagerUtils.h" #include "nsIClipboardHelper.h" #include "nsIFile.h" +#include "nsIGfxInfo.h" #include "nsIPresShell.h" #include "nsPresContext.h" #include "nsRegion.h" @@ -1543,6 +1547,125 @@ gfxUtils::CopyAsDataURI(DrawTarget* aDT) } } +/* static */ void +gfxUtils::GetImageBuffer(gfx::DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + uint8_t** outImageBuffer, + int32_t* outFormat) +{ + *outImageBuffer = nullptr; + *outFormat = 0; + + DataSourceSurface::MappedSurface map; + if (!aSurface->Map(DataSourceSurface::MapType::READ, &map)) + return; + + uint32_t bufferSize = aSurface->GetSize().width * aSurface->GetSize().height * 4; + uint8_t* imageBuffer = new (fallible) uint8_t[bufferSize]; + if (!imageBuffer) { + aSurface->Unmap(); + return; + } + memcpy(imageBuffer, map.mData, bufferSize); + + aSurface->Unmap(); + + int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; + if (!aIsAlphaPremultiplied) { + // We need to convert to INPUT_FORMAT_RGBA, otherwise + // we are automatically considered premult, and unpremult'd. + // Yes, it is THAT silly. + // Except for different lossy conversions by color, + // we could probably just change the label, and not change the data. + gfxUtils::ConvertBGRAtoRGBA(imageBuffer, bufferSize); + format = imgIEncoder::INPUT_FORMAT_RGBA; + } + + *outImageBuffer = imageBuffer; + *outFormat = format; +} + +/* static */ nsresult +gfxUtils::GetInputStream(gfx::DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + const char* aMimeType, + const char16_t* aEncoderOptions, + nsIInputStream** outStream) +{ + nsCString enccid("@mozilla.org/image/encoder;2?type="); + enccid += aMimeType; + nsCOMPtr encoder = do_CreateInstance(enccid.get()); + if (!encoder) + return NS_ERROR_FAILURE; + + nsAutoArrayPtr imageBuffer; + int32_t format = 0; + GetImageBuffer(aSurface, aIsAlphaPremultiplied, getter_Transfers(imageBuffer), &format); + if (!imageBuffer) + return NS_ERROR_FAILURE; + + return dom::ImageEncoder::GetInputStream(aSurface->GetSize().width, + aSurface->GetSize().height, + imageBuffer, format, + encoder, aEncoderOptions, outStream); +} + +class GetFeatureStatusRunnable final : public dom::workers::WorkerMainThreadRunnable +{ +public: + GetFeatureStatusRunnable(dom::workers::WorkerPrivate* workerPrivate, + const nsCOMPtr& gfxInfo, + int32_t feature, + int32_t* status) + : WorkerMainThreadRunnable(workerPrivate) + , mGfxInfo(gfxInfo) + , mFeature(feature) + , mStatus(status) + , mNSResult(NS_OK) + { + } + + bool MainThreadRun() override + { + if (mGfxInfo) { + mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mStatus); + } + return true; + } + + nsresult GetNSResult() const + { + return mNSResult; + } + +protected: + ~GetFeatureStatusRunnable() {} + +private: + nsCOMPtr mGfxInfo; + int32_t mFeature; + int32_t* mStatus; + nsresult mNSResult; +}; + +/* static */ nsresult +gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr& gfxInfo, + int32_t feature, int32_t* status) +{ + if (!NS_IsMainThread()) { + dom::workers::WorkerPrivate* workerPrivate = + dom::workers::GetCurrentThreadWorkerPrivate(); + RefPtr runnable = + new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, status); + + runnable->Dispatch(workerPrivate->GetJSContext()); + + return runnable->GetNSResult(); + } + + return gfxInfo->GetFeatureStatus(feature, status); +} + /* static */ bool gfxUtils::DumpDisplayList() { return gfxPrefs::LayoutDumpDisplayList(); diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index 090d31edf4..1058dd58cd 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -16,6 +16,8 @@ class gfxASurface; class gfxDrawable; +class nsIInputStream; +class nsIGfxInfo; class nsIntRegion; class nsIPresShell; @@ -280,6 +282,21 @@ public: static nsCString GetAsDataURI(DrawTarget* aDT); static nsCString GetAsLZ4Base64Str(DataSourceSurface* aSourceSurface); + static void GetImageBuffer(DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + uint8_t** outImageBuffer, + int32_t* outFormat); + + static nsresult GetInputStream(DataSourceSurface* aSurface, + bool aIsAlphaPremultiplied, + const char* aMimeType, + const char16_t* aEncoderOptions, + nsIInputStream** outStream); + + static nsresult ThreadSafeGetFeatureStatus(const nsCOMPtr& gfxInfo, + int32_t feature, + int32_t* status); + /** * Copy to the clipboard as a PNG encoded Data URL. */ diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build index e1cec9c13c..845a85092c 100644 --- a/gfx/thebes/moz.build +++ b/gfx/thebes/moz.build @@ -271,6 +271,7 @@ GENERATED_FILES = [ ] LOCAL_INCLUDES += [ + '/dom/workers', '/dom/xml', ] diff --git a/js/public/Class.h b/js/public/Class.h index 98a4982dc0..58f138dafa 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -46,8 +46,10 @@ template class AutoVectorRooter; typedef AutoVectorRooter AutoIdVector; -// The answer to a successful query as to whether an object is an Array per -// ES6's internal |IsArray| operation (as exposed by |Array.isArray|). +/** + * The answer to a successful query as to whether an object is an Array per + * ES6's internal |IsArray| operation (as exposed by |Array.isArray|). + */ enum class IsArrayAnswer { Array, @@ -55,29 +57,33 @@ enum class IsArrayAnswer RevokedProxy }; -// ES6 7.2.2. -// -// Returns false on failure, otherwise returns true and sets |*isArray| -// indicating whether the object passes ECMAScript's IsArray test. This is the -// same test performed by |Array.isArray|. -// -// This is NOT the same as asking whether |obj| is an Array or a wrapper around -// one. If |obj| is a proxy created by |Proxy.revocable()| and has been -// revoked, or if |obj| is a proxy whose target (at any number of hops) is a -// revoked proxy, this method throws a TypeError and returns false. +/** + * ES6 7.2.2. + * + * Returns false on failure, otherwise returns true and sets |*isArray| + * indicating whether the object passes ECMAScript's IsArray test. This is the + * same test performed by |Array.isArray|. + * + * This is NOT the same as asking whether |obj| is an Array or a wrapper around + * one. If |obj| is a proxy created by |Proxy.revocable()| and has been + * revoked, or if |obj| is a proxy whose target (at any number of hops) is a + * revoked proxy, this method throws a TypeError and returns false. + */ extern JS_PUBLIC_API(bool) IsArray(JSContext* cx, HandleObject obj, bool* isArray); -// Identical to IsArray above, but the nature of the object (if successfully -// determined) is communicated via |*answer|. In particular this method -// returns true and sets |*answer = IsArrayAnswer::RevokedProxy| when called on -// a revoked proxy. -// -// Most users will want the overload above, not this one. +/** + * Identical to IsArray above, but the nature of the object (if successfully + * determined) is communicated via |*answer|. In particular this method + * returns true and sets |*answer = IsArrayAnswer::RevokedProxy| when called on + * a revoked proxy. + * + * Most users will want the overload above, not this one. + */ extern JS_PUBLIC_API(bool) IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer); -/* +/** * Per ES6, the [[DefineOwnProperty]] internal method has three different * possible outcomes: * @@ -113,7 +119,7 @@ IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer); class ObjectOpResult { private: - /* + /** * code_ is either one of the special codes OkCode or Uninitialized, or * an error code. For now the error codes are private to the JS engine; * they're defined in js/src/js.msg. @@ -249,60 +255,70 @@ class ObjectOpResult // JSClass operation signatures. -// Get a property named by id in obj. Note the jsid id type -- id may -// be a string (Unicode property identifier) or an int (element index). The -// *vp out parameter, on success, is the new property value after the action. +/** + * Get a property named by id in obj. Note the jsid id type -- id may + * be a string (Unicode property identifier) or an int (element index). The + * *vp out parameter, on success, is the new property value after the action. + */ typedef bool (* JSGetterOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp); -// Add a property named by id to obj. +/** Add a property named by id to obj. */ typedef bool (* JSAddPropertyOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleValue v); -// Set a property named by id in obj, treating the assignment as strict -// mode code if strict is true. Note the jsid id type -- id may be a string -// (Unicode property identifier) or an int (element index). The *vp out -// parameter, on success, is the new property value after the -// set. +/** + * Set a property named by id in obj, treating the assignment as strict + * mode code if strict is true. Note the jsid id type -- id may be a string + * (Unicode property identifier) or an int (element index). The *vp out + * parameter, on success, is the new property value after the + * set. + */ typedef bool (* JSSetterOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp, JS::ObjectOpResult& result); -// Delete a property named by id in obj. -// -// If an error occurred, return false as per normal JSAPI error practice. -// -// If no error occurred, but the deletion attempt wasn't allowed (perhaps -// because the property was non-configurable), call result.fail() and -// return true. This will cause |delete obj[id]| to evaluate to false in -// non-strict mode code, and to throw a TypeError in strict mode code. -// -// If no error occurred and the deletion wasn't disallowed (this is *not* the -// same as saying that a deletion actually occurred -- deleting a non-existent -// property, or an inherited property, is allowed -- it's just pointless), -// call result.succeed() and return true. +/** + * Delete a property named by id in obj. + * + * If an error occurred, return false as per normal JSAPI error practice. + * + * If no error occurred, but the deletion attempt wasn't allowed (perhaps + * because the property was non-configurable), call result.fail() and + * return true. This will cause |delete obj[id]| to evaluate to false in + * non-strict mode code, and to throw a TypeError in strict mode code. + * + * If no error occurred and the deletion wasn't disallowed (this is *not* the + * same as saying that a deletion actually occurred -- deleting a non-existent + * property, or an inherited property, is allowed -- it's just pointless), + * call result.succeed() and return true. + */ typedef bool (* JSDeletePropertyOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::ObjectOpResult& result); -// The type of ObjectOps::enumerate. This callback overrides a portion of -// SpiderMonkey's default [[Enumerate]] internal method. When an ordinary object -// is enumerated, that object and each object on its prototype chain is tested -// for an enumerate op, and those ops are called in order. The properties each -// op adds to the 'properties' vector are added to the set of values the for-in -// loop will iterate over. All of this is nonstandard. -// -// An object is "enumerated" when it's the target of a for-in loop or -// JS_Enumerate(). The callback's job is to populate 'properties' with the -// object's property keys. If `enumerableOnly` is true, the callback should only -// add enumerable properties. +/** + * The type of ObjectOps::enumerate. This callback overrides a portion of + * SpiderMonkey's default [[Enumerate]] internal method. When an ordinary object + * is enumerated, that object and each object on its prototype chain is tested + * for an enumerate op, and those ops are called in order. The properties each + * op adds to the 'properties' vector are added to the set of values the for-in + * loop will iterate over. All of this is nonstandard. + * + * An object is "enumerated" when it's the target of a for-in loop or + * JS_Enumerate(). The callback's job is to populate 'properties' with the + * object's property keys. If `enumerableOnly` is true, the callback should only + * add enumerable properties. + */ typedef bool (* JSNewEnumerateOp)(JSContext* cx, JS::HandleObject obj, JS::AutoIdVector& properties, bool enumerableOnly); -// The old-style JSClass.enumerate op should define all lazy properties not -// yet reflected in obj. +/** + * The old-style JSClass.enumerate op should define all lazy properties not + * yet reflected in obj. + */ typedef bool (* JSEnumerateOp)(JSContext* cx, JS::HandleObject obj); @@ -314,60 +330,69 @@ typedef bool typedef JSString* (* JSFunToStringOp)(JSContext* cx, JS::HandleObject obj, unsigned indent); -// Resolve a lazy property named by id in obj by defining it directly in obj. -// Lazy properties are those reflected from some peer native property space -// (e.g., the DOM attributes for a given node reflected as obj) on demand. -// -// JS looks for a property in an object, and if not found, tries to resolve -// the given id. *resolvedp should be set to true iff the property was -// was defined on |obj|. -// +/** + * Resolve a lazy property named by id in obj by defining it directly in obj. + * Lazy properties are those reflected from some peer native property space + * (e.g., the DOM attributes for a given node reflected as obj) on demand. + * + * JS looks for a property in an object, and if not found, tries to resolve + * the given id. *resolvedp should be set to true iff the property was + * was defined on |obj|. + */ typedef bool (* JSResolveOp)(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolvedp); -// A class with a resolve hook can optionally have a mayResolve hook. This hook -// must have no side effects and must return true for a given id if the resolve -// hook may resolve this id. This is useful when we're doing a "pure" lookup: if -// mayResolve returns false, we know we don't have to call the effectful resolve -// hook. -// -// maybeObj, if non-null, is the object on which we're doing the lookup. This -// can be nullptr: during JIT compilation we sometimes know the Class but not -// the object. +/** + * A class with a resolve hook can optionally have a mayResolve hook. This hook + * must have no side effects and must return true for a given id if the resolve + * hook may resolve this id. This is useful when we're doing a "pure" lookup: if + * mayResolve returns false, we know we don't have to call the effectful resolve + * hook. + * + * maybeObj, if non-null, is the object on which we're doing the lookup. This + * can be nullptr: during JIT compilation we sometimes know the Class but not + * the object. + */ typedef bool (* JSMayResolveOp)(const JSAtomState& names, jsid id, JSObject* maybeObj); -// Finalize obj, which the garbage collector has determined to be unreachable -// from other live objects or from GC roots. Obviously, finalizers must never -// store a reference to obj. +/** + * Finalize obj, which the garbage collector has determined to be unreachable + * from other live objects or from GC roots. Obviously, finalizers must never + * store a reference to obj. + */ typedef void (* JSFinalizeOp)(JSFreeOp* fop, JSObject* obj); -// Finalizes external strings created by JS_NewExternalString. +/** Finalizes external strings created by JS_NewExternalString. */ struct JSStringFinalizer { void (*finalize)(const JSStringFinalizer* fin, char16_t* chars); }; -// Check whether v is an instance of obj. Return false on error or exception, -// true on success with true in *bp if v is an instance of obj, false in -// *bp otherwise. +/** + * Check whether v is an instance of obj. Return false on error or exception, + * true on success with true in *bp if v is an instance of obj, false in + * *bp otherwise. + */ typedef bool (* JSHasInstanceOp)(JSContext* cx, JS::HandleObject obj, JS::MutableHandleValue vp, bool* bp); -// Function type for trace operation of the class called to enumerate all -// traceable things reachable from obj's private data structure. For each such -// thing, a trace implementation must call one of the JS_Call*Tracer variants -// on the thing. -// -// JSTraceOp implementation can assume that no other threads mutates object -// state. It must not change state of the object or corresponding native -// structures. The only exception for this rule is the case when the embedding -// needs a tight integration with GC. In that case the embedding can check if -// the traversal is a part of the marking phase through calling -// JS_IsGCMarkingTracer and apply a special code like emptying caches or -// marking its native structures. +/** + * Function type for trace operation of the class called to enumerate all + * traceable things reachable from obj's private data structure. For each such + * thing, a trace implementation must call one of the JS_Call*Tracer variants + * on the thing. + * + * JSTraceOp implementation can assume that no other threads mutates object + * state. It must not change state of the object or corresponding native + * structures. The only exception for this rule is the case when the embedding + * needs a tight integration with GC. In that case the embedding can check if + * the traversal is a part of the marking phase through calling + * JS_IsGCMarkingTracer and apply a special code like emptying caches or + * marking its native structures. + */ typedef void (* JSTraceOp)(JSTracer* trc, JSObject* obj); @@ -448,12 +473,14 @@ typedef bool (* GetElementsOp)(JSContext* cx, JS::HandleObject obj, uint32_t begin, uint32_t end, ElementAdder* adder); -// A generic type for functions mapping an object to another object, or null -// if an error or exception was thrown on cx. +/** + * A generic type for functions mapping an object to another object, or null + * if an error or exception was thrown on cx. + */ typedef JSObject* (* ObjectOp)(JSContext* cx, JS::HandleObject obj); -// Hook to map an object to its inner object. Infallible. +/** Hook to map an object to its inner object. Infallible. */ typedef JSObject* (* InnerObjectOp)(JSObject* obj); @@ -478,10 +505,10 @@ typedef void JSNative construct; \ JSTraceOp trace -// Callback for the creation of constructor and prototype objects. +/** Callback for the creation of constructor and prototype objects. */ typedef JSObject* (*ClassObjectCreationOp)(JSContext* cx, JSProtoKey key); -// Callback for custom post-processing after class initialization via ClassSpec. +/** Callback for custom post-processing after class initialization via ClassSpec. */ typedef bool (*FinishClassInitOp)(JSContext* cx, JS::HandleObject ctor, JS::HandleObject proto); @@ -573,13 +600,13 @@ struct ClassExtension ObjectOp outerObject; InnerObjectOp innerObject; - /* + /** * isWrappedNative is true only if the class is an XPCWrappedNative. * WeakMaps use this to override the wrapper disposal optimization. */ bool isWrappedNative; - /* + /** * If an object is used as a key in a weakmap, it may be desirable for the * garbage collector to keep that object around longer than it otherwise * would. A common case is when the key is a wrapper around an object in @@ -592,7 +619,7 @@ struct ClassExtension */ JSWeakmapKeyDelegateOp weakmapKeyDelegateOp; - /* + /** * Optional hook called when an object is moved by a compacting GC. * * There may exist weak pointers to an object that are not traced through @@ -825,7 +852,7 @@ Valueify(const JSClass* c) return (const Class*)c; } -/* +/** * Enumeration describing possible values of the [[Class]] internal property * value of objects. */ @@ -834,7 +861,7 @@ enum ESClassValue { ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer, ESClass_Date, ESClass_Set, ESClass_Map, - // None of the above. + /** None of the above. */ ESClass_Other }; diff --git a/js/public/Conversions.h b/js/public/Conversions.h index 6c4e1187d0..c08da01346 100644 --- a/js/public/Conversions.h +++ b/js/public/Conversions.h @@ -75,7 +75,7 @@ namespace JS { namespace detail { #ifdef JS_DEBUG -/* +/** * Assert that we're not doing GC on cx, that we're in a request as * needed, and that the compartments for cx and v are correct. * Also check that GC would be safe at this point. @@ -89,7 +89,7 @@ inline void AssertArgumentsAreSane(JSContext* cx, HandleValue v) } // namespace detail -/* +/** * ES6 draft 20141224, 7.1.1, second algorithm. * * Most users shouldn't call this -- use JS::ToBoolean, ToNumber, or ToString diff --git a/js/public/GCAPI.h b/js/public/GCAPI.h index 4b36adf24d..794cda7584 100644 --- a/js/public/GCAPI.h +++ b/js/public/GCAPI.h @@ -22,20 +22,20 @@ struct Statistics; } // namespace js typedef enum JSGCMode { - /* Perform only global GCs. */ + /** Perform only global GCs. */ JSGC_MODE_GLOBAL = 0, - /* Perform per-compartment GCs until too much garbage has accumulated. */ + /** Perform per-compartment GCs until too much garbage has accumulated. */ JSGC_MODE_COMPARTMENT = 1, - /* + /** * Collect in short time slices rather than all at once. Implies * JSGC_MODE_COMPARTMENT. */ JSGC_MODE_INCREMENTAL = 2 } JSGCMode; -/* +/** * Kinds of js_GC invocation. */ typedef enum JSGCInvocationKind { @@ -138,19 +138,19 @@ enum Reason { * all zones. Failing to select any zone is an error. */ -/* +/** * Schedule the given zone to be collected as part of the next GC. */ extern JS_PUBLIC_API(void) PrepareZoneForGC(Zone* zone); -/* +/** * Schedule all zones to be collected in the next GC. */ extern JS_PUBLIC_API(void) PrepareForFullGC(JSRuntime* rt); -/* +/** * When performing an incremental GC, the zones that were selected for the * previous incremental slice must be selected in subsequent slices as well. * This function selects those slices automatically. @@ -158,14 +158,14 @@ PrepareForFullGC(JSRuntime* rt); extern JS_PUBLIC_API(void) PrepareForIncrementalGC(JSRuntime* rt); -/* +/** * Returns true if any zone in the system has been scheduled for GC with one of * the functions above or by the JS engine. */ extern JS_PUBLIC_API(bool) IsGCScheduled(JSRuntime* rt); -/* +/** * Undoes the effect of the Prepare methods above. The given zone will not be * collected in the next GC. */ @@ -178,7 +178,7 @@ SkipZoneForGC(Zone* zone); * The following functions perform a non-incremental GC. */ -/* +/** * Performs a non-incremental collection of all selected zones. * * If the gckind argument is GC_NORMAL, then some objects that are unreachable @@ -210,7 +210,7 @@ GCForReason(JSRuntime* rt, JSGCInvocationKind gckind, gcreason::Reason reason); * non-incremental collections can still happen when low on memory. */ -/* +/** * Begin an incremental collection and perform one slice worth of work. When * this function returns, the collection may not be complete. * IncrementalGCSlice() must be called repeatedly until @@ -223,7 +223,7 @@ extern JS_PUBLIC_API(void) StartIncrementalGC(JSRuntime* rt, JSGCInvocationKind gckind, gcreason::Reason reason, int64_t millis = 0); -/* +/** * Perform a slice of an ongoing incremental collection. When this function * returns, the collection may not be complete. It must be called repeatedly * until !IsIncrementalGCInProgress(rt). @@ -234,7 +234,7 @@ StartIncrementalGC(JSRuntime* rt, JSGCInvocationKind gckind, gcreason::Reason re extern JS_PUBLIC_API(void) IncrementalGCSlice(JSRuntime* rt, gcreason::Reason reason, int64_t millis = 0); -/* +/** * If IsIncrementalGCInProgress(rt), this call finishes the ongoing collection * by performing an arbitrarily long slice. If !IsIncrementalGCInProgress(rt), * this is equivalent to GCForReason. When this function returns, @@ -243,7 +243,7 @@ IncrementalGCSlice(JSRuntime* rt, gcreason::Reason reason, int64_t millis = 0); extern JS_PUBLIC_API(void) FinishIncrementalGC(JSRuntime* rt, gcreason::Reason reason); -/* +/** * If IsIncrementalGCInProgress(rt), this call aborts the ongoing collection and * performs whatever work needs to be done to return the collector to its idle * state. This may take an arbitrarily long time. When this function returns, @@ -337,7 +337,7 @@ struct JS_PUBLIC_API(GCDescription) { typedef void (* GCSliceCallback)(JSRuntime* rt, GCProgress progress, const GCDescription& desc); -/* +/** * The GC slice callback is called at the beginning and end of each slice. This * callback may be used for GC notifications as well as to perform additional * marking. @@ -345,7 +345,7 @@ typedef void extern JS_PUBLIC_API(GCSliceCallback) SetGCSliceCallback(JSRuntime* rt, GCSliceCallback callback); -/* +/** * Incremental GC defaults to enabled, but may be disabled for testing or in * embeddings that have not yet implemented barriers on their native classes. * There is not currently a way to re-enable incremental GC once it has been @@ -354,7 +354,7 @@ SetGCSliceCallback(JSRuntime* rt, GCSliceCallback callback); extern JS_PUBLIC_API(void) DisableIncrementalGC(JSRuntime* rt); -/* +/** * Returns true if incremental GC is enabled. Simply having incremental GC * enabled is not sufficient to ensure incremental collections are happening. * See the comment "Incremental GC" above for reasons why incremental GC may be @@ -365,7 +365,7 @@ DisableIncrementalGC(JSRuntime* rt); extern JS_PUBLIC_API(bool) IsIncrementalGCEnabled(JSRuntime* rt); -/* +/** * Returns true while an incremental GC is ongoing, both when actively * collecting and between slices. */ @@ -396,7 +396,7 @@ IncrementalValueBarrier(const Value& v); extern JS_PUBLIC_API(void) IncrementalObjectBarrier(JSObject* obj); -/* +/** * Returns true if the most recent GC ran incrementally. */ extern JS_PUBLIC_API(bool) @@ -410,7 +410,7 @@ WasIncrementalGC(JSRuntime* rt); * --enable-gcgenerational. */ -/* Ensure that generational GC is disabled within some scope. */ +/** Ensure that generational GC is disabled within some scope. */ class JS_PUBLIC_API(AutoDisableGenerationalGC) { js::gc::GCRuntime* gc; @@ -420,14 +420,14 @@ class JS_PUBLIC_API(AutoDisableGenerationalGC) ~AutoDisableGenerationalGC(); }; -/* +/** * Returns true if generational allocation and collection is currently enabled * on the given runtime. */ extern JS_PUBLIC_API(bool) IsGenerationalGCEnabled(JSRuntime* rt); -/* +/** * Returns the GC's "number". This does not correspond directly to the number * of GCs that have been run, but is guaranteed to be monotonically increasing * with GC activity. @@ -435,7 +435,7 @@ IsGenerationalGCEnabled(JSRuntime* rt); extern JS_PUBLIC_API(size_t) GetGCNumber(); -/* +/** * The GC does not immediately return the unused memory freed by a collection * back to the system incase it is needed soon afterwards. This call forces the * GC to return this memory immediately. @@ -443,7 +443,7 @@ GetGCNumber(); extern JS_PUBLIC_API(void) ShrinkGCBuffers(JSRuntime* rt); -/* +/** * Assert if a GC occurs while this class is live. This class does not disable * the static rooting hazard analysis. */ @@ -469,7 +469,7 @@ class JS_PUBLIC_API(AutoAssertOnGC) #endif }; -/* +/** * Assert if an allocation of a GC thing occurs while this class is live. This * class does not disable the static rooting hazard analysis. */ @@ -491,7 +491,7 @@ class JS_PUBLIC_API(AutoAssertNoAlloc) #endif }; -/* +/** * Disable the static rooting hazard analysis in the live region and assert if * any allocation that could potentially trigger a GC occurs while this guard * object is live. This is most useful to help the exact rooting hazard analysis @@ -512,7 +512,7 @@ class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertNoAlloc explicit AutoSuppressGCAnalysis(JSRuntime* rt) : AutoAssertNoAlloc(rt) {} }; -/* +/** * Assert that code is only ever called from a GC callback, disable the static * rooting hazard analysis and assert if any allocation that could potentially * trigger a GC occurs while this guard object is live. @@ -526,7 +526,7 @@ class JS_PUBLIC_API(AutoAssertGCCallback) : public AutoSuppressGCAnalysis explicit AutoAssertGCCallback(JSObject* obj); }; -/* +/** * Place AutoCheckCannotGC in scopes that you believe can never GC. These * annotations will be verified both dynamically via AutoAssertOnGC, and * statically with the rooting hazard analysis (implemented by making the @@ -543,7 +543,7 @@ class JS_PUBLIC_API(AutoCheckCannotGC) : public AutoAssertOnGC explicit AutoCheckCannotGC(JSRuntime* rt) : AutoAssertOnGC(rt) {} }; -/* +/** * Unsets the gray bit for anything reachable from |thing|. |kind| should not be * JS::TraceKind::Shape. |thing| should be non-null. */ diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h index 016d9f1953..ba9fcbcf0c 100644 --- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -149,11 +149,13 @@ struct Zone } /* namespace shadow */ -// A GC pointer, tagged with the trace kind. -// -// In general, a GC pointer should be stored with an exact type. This class -// is for use when that is not possible because a single pointer must point -// to several kinds of GC thing. +/** + * A GC pointer, tagged with the trace kind. + * + * In general, a GC pointer should be stored with an exact type. This class + * is for use when that is not possible because a single pointer must point + * to several kinds of GC thing. + */ class JS_FRIEND_API(GCCellPtr) { public: @@ -405,7 +407,7 @@ IsIncrementalBarrierNeededOnTenuredGCThing(JS::shadow::Runtime* rt, const JS::GC return JS::shadow::Zone::asShadowZone(zone)->needsIncrementalBarrier(); } -/* +/** * Create an object providing access to the garbage collector's internal notion * of the current state of memory (both GC heap memory and GCthing-controlled * malloc memory. diff --git a/js/public/Id.h b/js/public/Id.h index 51c8518350..a62b2c8dd0 100644 --- a/js/public/Id.h +++ b/js/public/Id.h @@ -58,7 +58,7 @@ JSID_TO_STRING(jsid id) return (JSString*)JSID_BITS(id); } -/* +/** * Only JSStrings that have been interned via the JSAPI can be turned into * jsids by API clients. * diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 7fae00000a..7427f4aa56 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -55,8 +55,7 @@ struct TabSizes size_t other; }; -// These are the measurements used by Servo. It's important that this is a POD -// struct so that Servo can have a parallel |repr(C)| Rust equivalent. +/** These are the measurements used by Servo. */ struct ServoSizes { enum Kind { @@ -96,18 +95,22 @@ struct ServoSizes namespace js { -// In memory reporting, we have concept of "sundries", line items which are too -// small to be worth reporting individually. Under some circumstances, a memory -// reporter gets tossed into the sundries bucket if it's smaller than -// MemoryReportingSundriesThreshold() bytes. -// -// We need to define this value here, rather than in the code which actually -// generates the memory reports, because NotableStringInfo uses this value. +/** + * In memory reporting, we have concept of "sundries", line items which are too + * small to be worth reporting individually. Under some circumstances, a memory + * reporter gets tossed into the sundries bucket if it's smaller than + * MemoryReportingSundriesThreshold() bytes. + * + * We need to define this value here, rather than in the code which actually + * generates the memory reports, because NotableStringInfo uses this value. + */ JS_FRIEND_API(size_t) MemoryReportingSundriesThreshold(); -// This hash policy avoids flattening ropes (which perturbs the site being -// measured and requires a JSContext) at the expense of doing a FULL ROPE COPY -// on every hash and match! Beware. +/** + * This hash policy avoids flattening ropes (which perturbs the site being + * measured and requires a JSContext) at the expense of doing a FULL ROPE COPY + * on every hash and match! Beware. + */ struct InefficientNonFlatteningStringHashPolicy { typedef JSString* Lookup; @@ -221,12 +224,14 @@ struct ClassInfo #undef FOR_EACH_SIZE }; -// Holds data about a notable class (one whose combined object and shape -// instances use more than a certain amount of memory) so we can report it -// individually. -// -// The only difference between this class and ClassInfo is that this class -// holds a copy of the filename. +/** + * Holds data about a notable class (one whose combined object and shape + * instances use more than a certain amount of memory) so we can report it + * individually. + * + * The only difference between this class and ClassInfo is that this class + * holds a copy of the filename. + */ struct NotableClassInfo : public ClassInfo { NotableClassInfo(); @@ -244,7 +249,7 @@ struct NotableClassInfo : public ClassInfo NotableClassInfo(const NotableClassInfo& info) = delete; }; -// Data for tracking JIT-code memory usage. +/** Data for tracking JIT-code memory usage. */ struct CodeSizes { #define FOR_EACH_SIZE(macro) \ @@ -269,7 +274,7 @@ struct CodeSizes #undef FOR_EACH_SIZE }; -// Data for tracking GC memory usage. +/** Data for tracking GC memory usage. */ struct GCSizes { // |nurseryDecommitted| is marked as NonHeap rather than GCHeapDecommitted @@ -300,11 +305,13 @@ struct GCSizes #undef FOR_EACH_SIZE }; -// This class holds information about the memory taken up by identical copies of -// a particular string. Multiple JSStrings may have their sizes aggregated -// together into one StringInfo object. Note that two strings with identical -// chars will not be aggregated together if one is a short string and the other -// is not. +/** + * This class holds information about the memory taken up by identical copies of + * a particular string. Multiple JSStrings may have their sizes aggregated + * together into one StringInfo object. Note that two strings with identical + * chars will not be aggregated together if one is a short string and the other + * is not. + */ struct StringInfo { #define FOR_EACH_SIZE(macro) \ @@ -355,11 +362,13 @@ struct StringInfo #undef FOR_EACH_SIZE }; -// Holds data about a notable string (one which, counting all duplicates, uses -// more than a certain amount of memory) so we can report it individually. -// -// The only difference between this class and StringInfo is that -// NotableStringInfo holds a copy of some or all of the string's chars. +/** + * Holds data about a notable string (one which, counting all duplicates, uses + * more than a certain amount of memory) so we can report it individually. + * + * The only difference between this class and StringInfo is that + * NotableStringInfo holds a copy of some or all of the string's chars. + */ struct NotableStringInfo : public StringInfo { static const size_t MAX_SAVED_CHARS = 1024; @@ -380,8 +389,10 @@ struct NotableStringInfo : public StringInfo NotableStringInfo(const NotableStringInfo& info) = delete; }; -// This class holds information about the memory taken up by script sources -// from a particular file. +/** + * This class holds information about the memory taken up by script sources + * from a particular file. + */ struct ScriptSourceInfo { #define FOR_EACH_SIZE(macro) \ @@ -422,12 +433,14 @@ struct ScriptSourceInfo #undef FOR_EACH_SIZE }; -// Holds data about a notable script source file (one whose combined -// script sources use more than a certain amount of memory) so we can report it -// individually. -// -// The only difference between this class and ScriptSourceInfo is that this -// class holds a copy of the filename. +/** + * Holds data about a notable script source file (one whose combined + * script sources use more than a certain amount of memory) so we can report it + * individually. + * + * The only difference between this class and ScriptSourceInfo is that this + * class holds a copy of the filename. + */ struct NotableScriptSourceInfo : public ScriptSourceInfo { NotableScriptSourceInfo(); @@ -445,8 +458,10 @@ struct NotableScriptSourceInfo : public ScriptSourceInfo NotableScriptSourceInfo(const NotableScriptSourceInfo& info) = delete; }; -// These measurements relate directly to the JSRuntime, and not to zones and -// compartments within it. +/** + * These measurements relate directly to the JSRuntime, and not to zones and + * compartments within it. + */ struct RuntimeSizes { #define FOR_EACH_SIZE(macro) \ diff --git a/js/public/RootingAPI.h b/js/public/RootingAPI.h index 47d43eeeee..0fd28b68e5 100644 --- a/js/public/RootingAPI.h +++ b/js/public/RootingAPI.h @@ -180,7 +180,7 @@ JS_FRIEND_API(bool) isGCEnabled(); JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next); #ifdef JS_DEBUG -/* +/** * For generational GC, assert that an object is in the tenured generation as * opposed to being in the nursery. */ @@ -195,7 +195,7 @@ inline void AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell) {} #endif -/* +/** * The Heap class is a heap-stored reference to a JS GC thing. All members of * heap classes that refer to GC things should use Heap (or possibly * TenuredHeap, described below). @@ -278,7 +278,7 @@ class MOZ_NON_MEMMOVABLE Heap : public js::HeapBase T ptr; }; -/* +/** * The TenuredHeap class is similar to the Heap class above in that it * encapsulates the GC concerns of an on-heap reference to a JS object. However, * it has two important differences: @@ -368,7 +368,7 @@ class TenuredHeap : public js::HeapBase uintptr_t bits; }; -/* +/** * Reference to a T that has been rooted elsewhere. This is most useful * as a parameter type, which guarantees that the T lvalue is properly * rooted. See "Move GC Stack Rooting" above. @@ -457,7 +457,7 @@ class MOZ_NONHEAP_CLASS Handle : public js::HandleBase const T* ptr; }; -/* +/** * Similar to a handle, but the underlying storage can be changed. This is * useful for outparams. * @@ -509,7 +509,7 @@ class MOZ_STACK_CLASS MutableHandle : public js::MutableHandleBase namespace js { -/* +/** * By default, things should use the inheritance hierarchy to find their * ThingRootKind. Some pointer types are explicitly set in jspubtd.h so that * Rooted may be used without the class definition being available. @@ -645,7 +645,7 @@ RootListsForRootingContext(js::PerThreadDataFriendFields* pt) namespace JS { -/* +/** * Local variable of type T whose value is always rooted. This is typically * used for local variables, or for non-rooted values being passed to a * function that requires a handle, e.g. Foo(Root(cx, x)). @@ -733,7 +733,7 @@ class MOZ_RAII Rooted : public js::RootedBase namespace js { -/* +/** * Augment the generic Rooted interface when T = JSObject* with * class-querying and downcasting operations. * @@ -751,7 +751,7 @@ class RootedBase JS::Handle as() const; }; -/* +/** * Augment the generic Handle interface when T = JSObject* with * downcasting operations. * @@ -769,7 +769,7 @@ class HandleBase JS::Handle as() const; }; -/* Interface substitute for Rooted which does not root the variable's memory. */ +/** Interface substitute for Rooted which does not root the variable's memory. */ template class MOZ_RAII FakeRooted : public RootedBase { @@ -796,7 +796,7 @@ class MOZ_RAII FakeRooted : public RootedBase FakeRooted(const FakeRooted&) = delete; }; -/* Interface substitute for MutableHandle which is not required to point to rooted memory. */ +/** Interface substitute for MutableHandle which is not required to point to rooted memory. */ template class FakeMutableHandle : public js::MutableHandleBase { @@ -824,7 +824,7 @@ class FakeMutableHandle : public js::MutableHandleBase T* ptr; }; -/* +/** * Types for a variable that either should or shouldn't be rooted, depending on * the template parameter allowGC. Used for implementing functions that can * operate on either rooted or unrooted data. @@ -930,7 +930,7 @@ MutableHandle::MutableHandle(PersistentRooted* root) ptr = root->address(); } -/* +/** * A copyable, assignable global GC root type with arbitrary lifetime, an * infallible constructor, and automatic unrooting on destruction. * diff --git a/js/public/StructuredClone.h b/js/public/StructuredClone.h index 252114c775..87f3ad15fc 100644 --- a/js/public/StructuredClone.h +++ b/js/public/StructuredClone.h @@ -23,85 +23,96 @@ struct JSStructuredCloneWriter; namespace JS { enum TransferableOwnership { - // Transferable data has not been filled in yet + /** Transferable data has not been filled in yet */ SCTAG_TMO_UNFILLED = 0, - // Structured clone buffer does not yet own the data + /** Structured clone buffer does not yet own the data */ SCTAG_TMO_UNOWNED = 1, - // All values at least this large are owned by the clone buffer + /** All values at least this large are owned by the clone buffer */ SCTAG_TMO_FIRST_OWNED = 2, - // Data is a pointer that can be freed + /** Data is a pointer that can be freed */ SCTAG_TMO_ALLOC_DATA = 2, - // Data is a SharedArrayBufferObject's buffer + /** Data is a SharedArrayBufferObject's buffer */ SCTAG_TMO_SHARED_BUFFER = 3, - // Data is a memory mapped pointer + /** Data is a memory mapped pointer */ SCTAG_TMO_MAPPED_DATA = 4, - // Data is embedding-specific. The engine can free it by calling the - // freeTransfer op. The embedding can also use SCTAG_TMO_USER_MIN and - // greater, up to 32 bits, to distinguish specific ownership variants. + /** + * Data is embedding-specific. The engine can free it by calling the + * freeTransfer op. The embedding can also use SCTAG_TMO_USER_MIN and + * greater, up to 32 bits, to distinguish specific ownership variants. + */ SCTAG_TMO_CUSTOM = 5, SCTAG_TMO_USER_MIN }; } /* namespace JS */ -// Read structured data from the reader r. This hook is used to read a value -// previously serialized by a call to the WriteStructuredCloneOp hook. -// -// tag and data are the pair of uint32_t values from the header. The callback -// may use the JS_Read* APIs to read any other relevant parts of the object -// from the reader r. closure is any value passed to the JS_ReadStructuredClone -// function. Return the new object on success, nullptr on error/exception. +/** + * Read structured data from the reader r. This hook is used to read a value + * previously serialized by a call to the WriteStructuredCloneOp hook. + * + * tag and data are the pair of uint32_t values from the header. The callback + * may use the JS_Read* APIs to read any other relevant parts of the object + * from the reader r. closure is any value passed to the JS_ReadStructuredClone + * function. Return the new object on success, nullptr on error/exception. + */ typedef JSObject* (*ReadStructuredCloneOp)(JSContext* cx, JSStructuredCloneReader* r, uint32_t tag, uint32_t data, void* closure); -// Structured data serialization hook. The engine can write primitive values, -// Objects, Arrays, Dates, RegExps, TypedArrays, ArrayBuffers, Sets, Maps, -// and SharedTypedArrays. Any other type of object requires application support. -// This callback must first use the JS_WriteUint32Pair API to write an object -// header, passing a value greater than JS_SCTAG_USER to the tag parameter. -// Then it can use the JS_Write* APIs to write any other relevant parts of -// the value v to the writer w. closure is any value passed to the -// JS_WriteStructuredClone function. -// -// Return true on success, false on error/exception. +/** + * Structured data serialization hook. The engine can write primitive values, + * Objects, Arrays, Dates, RegExps, TypedArrays, ArrayBuffers, Sets, Maps, + * and SharedTypedArrays. Any other type of object requires application support. + * This callback must first use the JS_WriteUint32Pair API to write an object + * header, passing a value greater than JS_SCTAG_USER to the tag parameter. + * Then it can use the JS_Write* APIs to write any other relevant parts of + * the value v to the writer w. closure is any value passed to the + * JS_WriteStructuredClone function. + * + * Return true on success, false on error/exception. + */ typedef bool (*WriteStructuredCloneOp)(JSContext* cx, JSStructuredCloneWriter* w, JS::HandleObject obj, void* closure); -// This is called when JS_WriteStructuredClone is given an invalid transferable. -// To follow HTML5, the application must throw a DATA_CLONE_ERR DOMException -// with error set to one of the JS_SCERR_* values. +/** + * This is called when JS_WriteStructuredClone is given an invalid transferable. + * To follow HTML5, the application must throw a DATA_CLONE_ERR DOMException + * with error set to one of the JS_SCERR_* values. + */ typedef void (*StructuredCloneErrorOp)(JSContext* cx, uint32_t errorid); -// This is called when JS_ReadStructuredClone receives a transferable object -// not known to the engine. If this hook does not exist or returns false, the -// JS engine calls the reportError op if set, otherwise it throws a -// DATA_CLONE_ERR DOM Exception. This method is called before any other -// callback and must return a non-null object in returnObject on success. +/** + * This is called when JS_ReadStructuredClone receives a transferable object + * not known to the engine. If this hook does not exist or returns false, the + * JS engine calls the reportError op if set, otherwise it throws a + * DATA_CLONE_ERR DOM Exception. This method is called before any other + * callback and must return a non-null object in returnObject on success. + */ typedef bool (*ReadTransferStructuredCloneOp)(JSContext* cx, JSStructuredCloneReader* r, uint32_t tag, void* content, uint64_t extraData, void* closure, JS::MutableHandleObject returnObject); -// Called when JS_WriteStructuredClone receives a transferable object not -// handled by the engine. If this hook does not exist or returns false, the JS -// engine will call the reportError hook or fall back to throwing a -// DATA_CLONE_ERR DOM Exception. This method is called before any other -// callback. -// -// tag: indicates what type of transferable this is. Must be greater than -// 0xFFFF0201 (value of the internal SCTAG_TRANSFER_MAP_PENDING_ENTRY) -// -// ownership: see TransferableOwnership, above. Used to communicate any needed -// ownership info to the FreeTransferStructuredCloneOp. -// -// content, extraData: what the ReadTransferStructuredCloneOp will receive -// +/** + * Called when JS_WriteStructuredClone receives a transferable object not + * handled by the engine. If this hook does not exist or returns false, the JS + * engine will call the reportError hook or fall back to throwing a + * DATA_CLONE_ERR DOM Exception. This method is called before any other + * callback. + * + * tag: indicates what type of transferable this is. Must be greater than + * 0xFFFF0201 (value of the internal SCTAG_TRANSFER_MAP_PENDING_ENTRY) + * + * ownership: see TransferableOwnership, above. Used to communicate any needed + * ownership info to the FreeTransferStructuredCloneOp. + * + * content, extraData: what the ReadTransferStructuredCloneOp will receive + */ typedef bool (*TransferStructuredCloneOp)(JSContext* cx, JS::Handle obj, void* closure, @@ -111,9 +122,11 @@ typedef bool (*TransferStructuredCloneOp)(JSContext* cx, void** content, uint64_t* extraData); -// Called when JS_ClearStructuredClone has to free an unknown transferable -// object. Note that it should never trigger a garbage collection (and will -// assert in a debug build if it does.) +/** + * Called when JS_ClearStructuredClone has to free an unknown transferable + * object. Note that it should never trigger a garbage collection (and will + * assert in a debug build if it does.) + */ typedef void (*FreeTransferStructuredCloneOp)(uint32_t tag, JS::TransferableOwnership ownership, void* content, uint64_t extraData, void* closure); @@ -132,14 +145,16 @@ struct JSStructuredCloneCallbacks { FreeTransferStructuredCloneOp freeTransfer; }; -// Note: if the *data contains transferable objects, it can be read only once. +/** Note: if the *data contains transferable objects, it can be read only once. */ JS_PUBLIC_API(bool) JS_ReadStructuredClone(JSContext* cx, uint64_t* data, size_t nbytes, uint32_t version, JS::MutableHandleValue vp, const JSStructuredCloneCallbacks* optionalCallbacks, void* closure); -// Note: On success, the caller is responsible for calling -// JS_ClearStructuredClone(*datap, nbytes, optionalCallbacks, closure). +/** + * Note: On success, the caller is responsible for calling + * JS_ClearStructuredClone(*datap, nbytes, optionalCallbacks, closure). + */ JS_PUBLIC_API(bool) JS_WriteStructuredClone(JSContext* cx, JS::HandleValue v, uint64_t** datap, size_t* nbytesp, const JSStructuredCloneCallbacks* optionalCallbacks, @@ -157,7 +172,7 @@ JS_PUBLIC_API(bool) JS_StructuredClone(JSContext* cx, JS::HandleValue v, JS::MutableHandleValue vp, const JSStructuredCloneCallbacks* optionalCallbacks, void* closure); -// RAII sugar for JS_WriteStructuredClone. +/** RAII sugar for JS_WriteStructuredClone. */ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { uint64_t* data_; size_t nbytes_; @@ -194,23 +209,32 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { void clear(const JSStructuredCloneCallbacks* optionalCallbacks=nullptr, void* closure=nullptr); - // Copy some memory. It will be automatically freed by the destructor. - bool copy(const uint64_t* data, size_t nbytes, uint32_t version=JS_STRUCTURED_CLONE_VERSION); + /** Copy some memory. It will be automatically freed by the destructor. */ + bool copy(const uint64_t* data, size_t nbytes, uint32_t version=JS_STRUCTURED_CLONE_VERSION, + const JSStructuredCloneCallbacks* callbacks=nullptr, void* closure=nullptr); - // Adopt some memory. It will be automatically freed by the destructor. - // data must have been allocated by the JS engine (e.g., extracted via - // JSAutoStructuredCloneBuffer::steal). - void adopt(uint64_t* data, size_t nbytes, uint32_t version=JS_STRUCTURED_CLONE_VERSION); + /** + * Adopt some memory. It will be automatically freed by the destructor. + * data must have been allocated by the JS engine (e.g., extracted via + * JSAutoStructuredCloneBuffer::steal). + */ + void adopt(uint64_t* data, size_t nbytes, uint32_t version=JS_STRUCTURED_CLONE_VERSION, + const JSStructuredCloneCallbacks* callbacks=nullptr, void* closure=nullptr); - // Release the buffer and transfer ownership to the caller. The caller is - // responsible for calling JS_ClearStructuredClone or feeding the memory - // back to JSAutoStructuredCloneBuffer::adopt. - void steal(uint64_t** datap, size_t* nbytesp, uint32_t* versionp=nullptr); + /** + * Release the buffer and transfer ownership to the caller. The caller is + * responsible for calling JS_ClearStructuredClone or feeding the memory + * back to JSAutoStructuredCloneBuffer::adopt. + */ + void steal(uint64_t** datap, size_t* nbytesp, uint32_t* versionp=nullptr, + const JSStructuredCloneCallbacks** callbacks=nullptr, void** closure=nullptr); - // Abandon ownership of any transferable objects stored in the buffer, - // without freeing the buffer itself. Useful when copying the data out into - // an external container, though note that you will need to use adopt() or - // JS_ClearStructuredClone to properly release that data eventually. + /** + * Abandon ownership of any transferable objects stored in the buffer, + * without freeing the buffer itself. Useful when copying the data out into + * an external container, though note that you will need to use adopt() or + * JS_ClearStructuredClone to properly release that data eventually. + */ void abandon() { ownTransferables_ = IgnoreTransferablesIfAny; } bool read(JSContext* cx, JS::MutableHandleValue vp, diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index f5f0fed2ee..1484f53072 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -20,27 +20,33 @@ class JS_PUBLIC_API(CallbackTracer); template class Heap; template class TenuredHeap; -// Returns a static string equivalent of |kind|. +/** Returns a static string equivalent of |kind|. */ JS_FRIEND_API(const char*) GCTraceKindToAscii(JS::TraceKind kind); } // namespace JS enum WeakMapTraceKind { - // Do true ephemeron marking with an iterative weak marking phase. + /** Do true ephemeron marking with an iterative weak marking phase. */ DoNotTraceWeakMaps, - // Do true ephemeron marking with a weak key lookup marking phase. This is - // expected to be constant for the lifetime of a JSTracer; it does not - // change when switching from "plain" marking to weak marking. + /** + * Do true ephemeron marking with a weak key lookup marking phase. This is + * expected to be constant for the lifetime of a JSTracer; it does not + * change when switching from "plain" marking to weak marking. + */ ExpandWeakMaps, - // Trace through to all values, irrespective of whether the keys are live - // or not. Used for non-marking tracers. + /** + * Trace through to all values, irrespective of whether the keys are live + * or not. Used for non-marking tracers. + */ TraceWeakMapValues, - // Trace through to all keys and values, irrespective of whether the keys - // are live or not. Used for non-marking tracers. + /** + * Trace through to all keys and values, irrespective of whether the keys + * are live or not. Used for non-marking tracers. + */ TraceWeakMapKeysValues }; @@ -333,8 +339,10 @@ JS_CallHashSetObjectTracer(JSTracer* trc, HashSetEnum& e, JSObject* const& key, e.rekeyFront(updated); } -// Trace an object that is known to always be tenured. No post barriers are -// required in this case. +/** + * Trace an object that is known to always be tenured. No post barriers are + * required in this case. + */ extern JS_PUBLIC_API(void) JS_CallTenuredObjectTracer(JSTracer* trc, JS::TenuredHeap* objp, const char* name); @@ -348,8 +356,10 @@ TraceChildren(JSTracer* trc, GCCellPtr thing); typedef js::HashSet, js::SystemAllocPolicy> ZoneSet; } // namespace JS -// Trace every value within |zones| that is wrapped by a cross-compartment -// wrapper from a zone that is not an element of |zones|. +/** + * Trace every value within |zones| that is wrapped by a cross-compartment + * wrapper from a zone that is not an element of |zones|. + */ extern JS_PUBLIC_API(void) JS_TraceIncomingCCWs(JSTracer* trc, const JS::ZoneSet& zones); diff --git a/js/public/Value.h b/js/public/Value.h index 4fc2afb85c..20a441ad32 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -237,25 +237,60 @@ typedef uint64_t JSValueShiftedTag; typedef enum JSWhyMagic { - JS_ELEMENTS_HOLE, /* a hole in a native object's elements */ - JS_NO_ITER_VALUE, /* there is not a pending iterator value */ - JS_GENERATOR_CLOSING, /* exception value thrown when closing a generator */ - JS_NO_CONSTANT, /* compiler sentinel value */ - JS_THIS_POISON, /* used in debug builds to catch tracing errors */ - JS_ARG_POISON, /* used in debug builds to catch tracing errors */ - JS_SERIALIZE_NO_NODE, /* an empty subnode in the AST serializer */ - JS_LAZY_ARGUMENTS, /* lazy arguments value on the stack */ - JS_OPTIMIZED_ARGUMENTS, /* optimized-away 'arguments' value */ - JS_IS_CONSTRUCTING, /* magic value passed to natives to indicate construction */ - JS_OVERWRITTEN_CALLEE, /* arguments.callee has been overwritten */ - 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, /* missing recover instruction result */ - JS_OPTIMIZED_OUT, /* optimized out slot */ - JS_UNINITIALIZED_LEXICAL, /* uninitialized lexical bindings that produce ReferenceError - * on touch. */ - JS_GENERIC_MAGIC, /* for local use */ + /** a hole in a native object's elements */ + JS_ELEMENTS_HOLE, + + /** there is not a pending iterator value */ + JS_NO_ITER_VALUE, + + /** exception value thrown when closing a generator */ + JS_GENERATOR_CLOSING, + + /** compiler sentinel value */ + JS_NO_CONSTANT, + + /** used in debug builds to catch tracing errors */ + JS_THIS_POISON, + + /** used in debug builds to catch tracing errors */ + JS_ARG_POISON, + + /** an empty subnode in the AST serializer */ + JS_SERIALIZE_NO_NODE, + + /** lazy arguments value on the stack */ + JS_LAZY_ARGUMENTS, + + /** optimized-away 'arguments' value */ + JS_OPTIMIZED_ARGUMENTS, + + /** magic value passed to natives to indicate construction */ + JS_IS_CONSTRUCTING, + + /** arguments.callee has been overwritten */ + JS_OVERWRITTEN_CALLEE, + + /** value of static block object slot */ + JS_BLOCK_NEEDS_CLONE, + + /** see class js::HashableValue */ + JS_HASH_KEY_EMPTY, + + /** error while running Ion code */ + JS_ION_ERROR, + + /** missing recover instruction result */ + JS_ION_BAILOUT, + + /** optimized out slot */ + JS_OPTIMIZED_OUT, + + /** uninitialized lexical bindings that produce ReferenceError on touch. */ + JS_UNINITIALIZED_LEXICAL, + + /** for local use */ + JS_GENERIC_MAGIC, + JS_WHY_MAGIC_COUNT } JSWhyMagic; @@ -967,7 +1002,7 @@ CanonicalizeNaN(double d) # pragma optimize("", on) #endif -/* +/** * JS::Value is the interface for a single JavaScript Engine value. A few * general notes on JS::Value: * @@ -1682,7 +1717,7 @@ template <> struct GCMethods template class MutableValueOperations; -/* +/** * A class designed for CRTP use in implementing the non-mutating parts of the * Value interface in Value-like classes. Outer must be a class inheriting * ValueOperations with a visible get() method returning a const @@ -1736,7 +1771,7 @@ class ValueOperations uint32_t magicUint32() const { return value().magicUint32(); } }; -/* +/** * A class designed for CRTP use in implementing all the mutating parts of the * Value interface in Value-like classes. Outer must be a class inheriting * MutableValueOperations with visible get() methods returning const and diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index 0d090b625c..62434d2356 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -3111,13 +3111,20 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_NEW: case PNK_TAGGED_TEMPLATE: case PNK_CALL: + case PNK_SUPERCALL: { ParseNode* next = pn->pn_head; MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos)); RootedValue callee(cx); - if (!expression(next, &callee)) - return false; + if (pn->isKind(PNK_SUPERCALL)) { + MOZ_ASSERT(next->isKind(PNK_POSHOLDER)); + if (!builder.super(&next->pn_pos, &callee)) + return false; + } else { + if (!expression(next, &callee)) + return false; + } NodeVector args(cx); if (!args.reserve(pn->pn_count - 1)) @@ -3135,6 +3142,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) if (pn->getKind() == PNK_TAGGED_TEMPLATE) return builder.taggedTemplate(callee, args, &pn->pn_pos, dst); + // SUPERCALL is Call(super, args) return pn->isKind(PNK_NEW) ? builder.newExpression(callee, args, &pn->pn_pos, dst) diff --git a/js/src/ds/InlineMap.h b/js/src/ds/InlineMap.h index 53f46c3d66..1d0502a474 100644 --- a/js/src/ds/InlineMap.h +++ b/js/src/ds/InlineMap.h @@ -45,6 +45,13 @@ class InlineMap static_assert(ZeroIsReserved::result, "zero as tombstone requires that zero keys be invalid"); +#ifdef DEBUG + bool keyNonZero(const K& key) { + // Zero as tombstone means zero keys are invalid. + return !!key; + } +#endif + bool usingMap() const { return inlNext > InlineElems; } @@ -209,6 +216,8 @@ class InlineMap MOZ_ALWAYS_INLINE Ptr lookup(const K& key) { + MOZ_ASSERT(keyNonZero(key)); + if (usingMap()) return Ptr(map.lookup(key)); @@ -223,6 +232,8 @@ class InlineMap MOZ_ALWAYS_INLINE AddPtr lookupForAdd(const K& key) { + MOZ_ASSERT(keyNonZero(key)); + if (usingMap()) return AddPtr(map.lookupForAdd(key)); @@ -243,6 +254,7 @@ class InlineMap MOZ_ALWAYS_INLINE bool add(AddPtr& p, const K& key, const V& value) { MOZ_ASSERT(!p); + MOZ_ASSERT(keyNonZero(key)); if (p.isInlinePtr) { InlineElem* addPtr = p.inlAddPtr; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 98f7b53ab6..44545fecec 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1034,6 +1034,7 @@ BytecodeEmitter::emitIndexOp(JSOp op, uint32_t index) bool BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op) { + MOZ_ASSERT(atom); MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); // .generator and .genrval lookups should be emitted as JSOP_GETALIASEDVAR @@ -1934,7 +1935,6 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_TRUE: case PNK_FALSE: case PNK_NULL: - case PNK_THIS: case PNK_ELISION: case PNK_GENERATOR: case PNK_NUMBER: @@ -1943,6 +1943,12 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) *answer = false; return true; + // |this| can throw in derived class constructors. + case PNK_THIS: + MOZ_ASSERT(pn->isArity(PN_NULLARY)); + *answer = sc->isFunctionBox() && sc->asFunctionBox()->isDerivedClassConstructor(); + return true; + // Trivial binary nodes with more token pos holders. case PNK_NEWTARGET: MOZ_ASSERT(pn->isArity(PN_BINARY)); @@ -2185,6 +2191,7 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_NEW: case PNK_CALL: case PNK_TAGGED_TEMPLATE: + case PNK_SUPERCALL: MOZ_ASSERT(pn->isArity(PN_LIST)); *answer = true; return true; @@ -6727,6 +6734,12 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) } callop = false; break; + case PNK_POSHOLDER: + MOZ_ASSERT(pn->isKind(PNK_SUPERCALL)); + MOZ_ASSERT(parser->handler.isSuperBase(pn2, cx)); + if (!emit1(JSOP_SUPERFUN)) + return false; + break; default: if (!emitTree(pn2)) return false; @@ -6739,7 +6752,8 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) return false; } - bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW; + bool isNewOp = pn->getOp() == JSOP_NEW || pn->getOp() == JSOP_SPREADNEW || + pn->getOp() == JSOP_SUPERCALL || pn->getOp() == JSOP_SPREADSUPERCALL;; /* * Emit code for each argument in order, then emit the JSOP_*CALL or @@ -6755,17 +6769,27 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) } if (isNewOp) { - // Repush the callee as new.target - if (!emitDupAt(argc + 1)) - return false; + if (pn->isKind(PNK_SUPERCALL)) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + // Repush the callee as new.target + if (!emitDupAt(argc + 1)) + return false; + } } } else { if (!emitArray(pn2->pn_next, argc, JSOP_SPREADCALLARRAY)) return false; if (isNewOp) { - if (!emitDupAt(2)) - return false; + if (pn->isKind(PNK_SUPERCALL)) { + if (!emit1(JSOP_NEWTARGET)) + return false; + } else { + if (!emitDupAt(2)) + return false; + } } } emittingForInit = oldEmittingForInit; @@ -6791,6 +6815,9 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_BAD_LEFTSIDE_OF_ASS)) return false; } + + if (pn->isKind(PNK_SUPERCALL) && !emit1(JSOP_SETTHIS)) + return false; return true; } @@ -7425,7 +7452,6 @@ BytecodeEmitter::emitClass(ParseNode* pn) break; } } - MOZ_ASSERT(constructor, "For now, no default constructors"); bool savedStrictness = sc->setLocalStrictMode(true); @@ -7457,12 +7483,22 @@ BytecodeEmitter::emitClass(ParseNode* pn) return false; } - if (!emitFunction(constructor, !!heritageExpression)) - return false; - - if (constructor->pn_funbox->needsHomeObject()) { - if (!emit2(JSOP_INITHOMEOBJECT, 0)) + if (constructor) { + if (!emitFunction(constructor, !!heritageExpression)) return false; + if (constructor->pn_funbox->needsHomeObject()) { + if (!emit2(JSOP_INITHOMEOBJECT, 0)) + return false; + } + } else { + JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty; + if (heritageExpression) { + if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) + return false; + } else { + if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) + return false; + } } if (!emit1(JSOP_SWAP)) @@ -7847,6 +7883,7 @@ BytecodeEmitter::emitTree(ParseNode* pn) case PNK_TAGGED_TEMPLATE: case PNK_CALL: case PNK_GENEXP: + case PNK_SUPERCALL: ok = emitCallOrNew(pn); break; diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index c46b07b36e..e1fe96bc69 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -414,6 +414,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_CLASSNAMES: case PNK_NEWTARGET: case PNK_POSHOLDER: + case PNK_SUPERCALL: MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on " "some parent node without recurring to test this node"); @@ -1579,7 +1580,8 @@ static bool FoldCall(ExclusiveContext* cx, ParseNode* node, Parser& parser, bool inGenexpLambda) { - MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_TAGGED_TEMPLATE)); + MOZ_ASSERT(node->isKind(PNK_CALL) || node->isKind(PNK_SUPERCALL) || + node->isKind(PNK_TAGGED_TEMPLATE)); MOZ_ASSERT(node->isArity(PN_LIST)); // Don't fold a parenthesized callable component in an invocation, as this @@ -1876,6 +1878,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo return FoldAdd(cx, pnp, parser, inGenexpLambda); case PNK_CALL: + case PNK_SUPERCALL: case PNK_TAGGED_TEMPLATE: return FoldCall(cx, pn, parser, inGenexpLambda); diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 388ddd9802..c05ad2a7be 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -376,6 +376,7 @@ class NameResolver case PNK_EXPORT_BATCH_SPEC: case PNK_FRESHENBLOCK: case PNK_OBJECT_PROPERTY_NAME: + case PNK_POSHOLDER: MOZ_ASSERT(cur->isArity(PN_NULLARY)); break; @@ -673,6 +674,7 @@ class NameResolver case PNK_COMMA: case PNK_NEW: case PNK_CALL: + case PNK_SUPERCALL: case PNK_GENEXP: case PNK_ARRAY: case PNK_STATEMENTLIST: @@ -796,7 +798,6 @@ class NameResolver case PNK_EXPORT_SPEC: // by PNK_EXPORT_SPEC_LIST case PNK_CALLSITEOBJ: // by PNK_TAGGED_TEMPLATE case PNK_CLASSNAMES: // by PNK_CLASS - case PNK_POSHOLDER: // by PNK_NEWTARGET, PNK_DOT MOZ_CRASH("should have been handled by a parent node"); case PNK_LIMIT: // invalid sentinel value diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index fd8e897a5a..07c667fd06 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -489,6 +489,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_COMMA: case PNK_NEW: case PNK_CALL: + case PNK_SUPERCALL: case PNK_GENEXP: case PNK_ARRAY: case PNK_OBJECT: diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 5023e4ade9..879a5d63df 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -175,6 +175,7 @@ class PackedScopeCoordinate F(CLASSNAMES) \ F(NEWTARGET) \ F(POSHOLDER) \ + F(SUPERCALL) \ \ /* Unary operators. */ \ F(TYPEOFNAME) \ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 1fbd6290fb..8988d139a4 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -6598,12 +6598,6 @@ Parser::classDefinition(YieldHandling yieldHandling, return null(); } - // Default constructors not yet implemented. See bug 1105463 - if (!seenConstructor) { - report(ParseError, false, null(), JSMSG_NO_CLASS_CONSTRUCTOR); - return null(); - } - ParseNode* nameNode = null(); ParseNode* methodsOrBlock = classMethods; if (name) { @@ -8559,9 +8553,31 @@ Parser::memberExpr(YieldHandling yieldHandling, TokenKind tt, bool tt == TOK_NO_SUBS_TEMPLATE) { if (handler.isSuperBase(lhs, context)) { - // For now... - report(ParseError, false, null(), JSMSG_BAD_SUPER); - return null(); + if (!pc->sc->isFunctionBox() || !pc->sc->asFunctionBox()->isDerivedClassConstructor()) { + report(ParseError, false, null(), JSMSG_BAD_SUPERCALL); + return null(); + } + + if (tt != TOK_LP) { + report(ParseError, false, null(), JSMSG_BAD_SUPER); + return null(); + } + + nextMember = handler.newList(PNK_SUPERCALL, lhs, JSOP_SUPERCALL); + if (!nextMember) + return null(); + + // Despite the fact that it's impossible to have |super()| is a + // generator, we still inherity the yieldHandling of the + // memberExpression, per spec. Curious. + bool isSpread = false; + if (!argumentList(yieldHandling, nextMember, &isSpread)) + return null(); + + if (isSpread) + handler.setOp(nextMember, JSOP_SPREADSUPERCALL); + + return nextMember; } nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate(); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index affebcd395..560105d338 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -390,6 +390,7 @@ class FunctionBox : public ObjectBox, public SharedContext hasExtensibleScope() || needsDeclEnvObject() || needsHomeObject() || + isDerivedClassConstructor() || isGenerator(); } }; diff --git a/js/src/jit-test/tests/auto-regress/bug759306.js b/js/src/jit-test/tests/auto-regress/bug759306.js index 6db04ed34e..32032e84ed 100644 --- a/js/src/jit-test/tests/auto-regress/bug759306.js +++ b/js/src/jit-test/tests/auto-regress/bug759306.js @@ -1,4 +1,4 @@ -// |jit-test| error:InternalError +// |jit-test| error:TypeError // Binary: cache/js-dbg-32-4ce3983a43f4-linux // Flags: diff --git a/js/src/jit-test/tests/auto-regress/bug776484.js b/js/src/jit-test/tests/auto-regress/bug776484.js deleted file mode 100644 index 971cea966f..0000000000 --- a/js/src/jit-test/tests/auto-regress/bug776484.js +++ /dev/null @@ -1,4 +0,0 @@ -// Binary: cache/js-dbg-64-462106f027af-linux -// Flags: -// -decompileBody(function () { }); diff --git a/js/src/jit-test/tests/basic/function-tosource-constructor.js b/js/src/jit-test/tests/basic/function-tosource-constructor.js index 5ee9ff6db4..e1d1443640 100644 --- a/js/src/jit-test/tests/basic/function-tosource-constructor.js +++ b/js/src/jit-test/tests/basic/function-tosource-constructor.js @@ -2,12 +2,10 @@ var f = Function("a", "b", "return a + b;"); assertEq(f.toString(), "function anonymous(a, b) {\nreturn a + b;\n}"); assertEq(f.toSource(), "(function anonymous(a, b) {\nreturn a + b;\n})"); assertEq(decompileFunction(f), f.toString()); -assertEq(decompileBody(f), "return a + b;"); f = Function("a", "...rest", "return rest[42] + b;"); assertEq(f.toString(), "function anonymous(a, ...rest) {\nreturn rest[42] + b;\n}"); assertEq(f.toSource(), "(function anonymous(a, ...rest) {\nreturn rest[42] + b;\n})") assertEq(decompileFunction(f), f.toString()); -assertEq(decompileBody(f), "return rest[42] + b;"); f = Function(""); assertEq(f.toString(), "function anonymous() {\n\n}"); f = Function("", "(abc)"); diff --git a/js/src/jit-test/tests/basic/function-tosource-exprbody.js b/js/src/jit-test/tests/basic/function-tosource-exprbody.js index ebd608b4b9..b78e06e049 100644 --- a/js/src/jit-test/tests/basic/function-tosource-exprbody.js +++ b/js/src/jit-test/tests/basic/function-tosource-exprbody.js @@ -2,7 +2,6 @@ function f1(foo, bar) foo + bar; assertEq(f1.toString(), "function f1(foo, bar) foo + bar"); assertEq(f1.toString(), f1.toSource()); assertEq(decompileFunction(f1), f1.toString()); -assertEq(decompileBody(f1), "foo + bar;"); // No semicolon on purpose function f2(foo, bar) foo + bar assertEq(f2.toString(), "function f2(foo, bar) foo + bar"); diff --git a/js/src/jit-test/tests/basic/function-tosource-genexpr.js b/js/src/jit-test/tests/basic/function-tosource-genexpr.js index 684de81db8..b98f8767dc 100644 --- a/js/src/jit-test/tests/basic/function-tosource-genexpr.js +++ b/js/src/jit-test/tests/basic/function-tosource-genexpr.js @@ -4,4 +4,4 @@ function getgen() { var gen; (getgen() for (x of [1])).next(); assertEq(gen.toSource(), "function genexp() {\n [generator expression]\n}"); -assertEq(decompileBody(gen), "\n [generator expression]\n"); +assertEq(gen.toString(), gen.toSource()); diff --git a/js/src/jit-test/tests/basic/function-tosource-lambda.js b/js/src/jit-test/tests/basic/function-tosource-lambda.js index b2571411fb..5c013fcba7 100644 --- a/js/src/jit-test/tests/basic/function-tosource-lambda.js +++ b/js/src/jit-test/tests/basic/function-tosource-lambda.js @@ -2,14 +2,11 @@ var f1 = function f0(a, b) { return a + b; } assertEq(f1.toSource(), "(function f0(a, b) { return a + b; })"); assertEq(f1.toString(), "function f0(a, b) { return a + b; }"); assertEq(decompileFunction(f1), f1.toString()); -assertEq(decompileBody(f1), " return a + b; "); var f2 = function (a, b) { return a + b; }; assertEq(f2.toSource(), "(function (a, b) { return a + b; })"); assertEq(f2.toString(), "function (a, b) { return a + b; }"); assertEq(decompileFunction(f2), f2.toString()); -assertEq(decompileBody(f2), " return a + b; "); var f3 = (function (a, b) { return a + b; }); assertEq(f3.toSource(), "(function (a, b) { return a + b; })"); assertEq(f3.toString(), "function (a, b) { return a + b; }"); assertEq(decompileFunction(f3), f3.toString()); -assertEq(decompileBody(f3), " return a + b; "); diff --git a/js/src/jit-test/tests/basic/function-tosource-statement.js b/js/src/jit-test/tests/basic/function-tosource-statement.js index df8b9a5733..c0c990e4f1 100644 --- a/js/src/jit-test/tests/basic/function-tosource-statement.js +++ b/js/src/jit-test/tests/basic/function-tosource-statement.js @@ -7,7 +7,5 @@ function f2(a, /* ))))pernicious comment */ b, c, // another comment(( d) {} assertEq(f2.toString(), "function f2(a, /* ))))pernicious comment */ b,\n c, // another comment((\n d) {}"); -assertEq(decompileBody(f2), ""); function f3() { } assertEq(f3.toString(), "function f3() { }"); -assertEq(decompileBody(f3), " "); diff --git a/js/src/jit-test/tests/basic/testProxyConstructors.js b/js/src/jit-test/tests/basic/testProxyConstructors.js index 5bf689bf9a..f002b069f3 100644 --- a/js/src/jit-test/tests/basic/testProxyConstructors.js +++ b/js/src/jit-test/tests/basic/testProxyConstructors.js @@ -1,13 +1,15 @@ // |jit-test| error: ExitCleanly -assertEq((new (Proxy.createFunction({}, +var handler = { getPropertyDescriptor() { return undefined; } } + +assertEq((new (Proxy.createFunction(handler, function(){ this.x = 1 }, function(){ this.x = 2 }))).x, 2); // proxies can return the callee -var x = Proxy.createFunction({}, function (q) { return q; }); +var x = Proxy.createFunction(handler, function (q) { return q; }); assertEq(new x(x), x); try { - var x = (Proxy.createFunction({}, "".indexOf)); + var x = (Proxy.createFunction(handler, "".indexOf)); new x; throw "Should not be reached" } diff --git a/js/src/jit-test/tests/ion/bug825705.js b/js/src/jit-test/tests/ion/bug825705.js index 05689f6ede..ebd30c21c8 100644 --- a/js/src/jit-test/tests/ion/bug825705.js +++ b/js/src/jit-test/tests/ion/bug825705.js @@ -6,18 +6,3 @@ evalcx("\ f();\ f();\ ", newGlobal()); - -// Test 2: Don't take the prototype of proxy's to create |this|, -// as this will throw... Not expected behaviour. -var O = new Proxy(function() {}, { - get: function() { - throw "get trap"; - } -}); - -function f() { - new O(); -} - -f(); -f(); diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index d6bdea5b28..fa106a2c66 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -980,7 +980,7 @@ InitFromBailout(JSContext* cx, HandleScript caller, jsbytecode* callerPC, #endif bool pushedNewTarget = op == JSOP_NEW; - + // If this was the last inline frame, or we are bailing out to a catch or // finally block in this frame, then unpacking is almost done. if (!iter.moreFrames() || catchingException) { @@ -1877,6 +1877,8 @@ jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo) case Bailout_NonSimdFloat32x4Input: case Bailout_InitialState: case Bailout_Debugger: + case Bailout_UninitializedThis: + case Bailout_BadDerivedConstructorReturn: // Do nothing. break; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 1b838ba07b..ebb64ba548 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1273,6 +1273,30 @@ BaselineCompiler::emit_JSOP_NULL() return true; } +typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame); +static const VMFunction ThrowUninitializedThisInfo = + FunctionInfo(BaselineThrowUninitializedThis); + +bool +BaselineCompiler::emitCheckThis() +{ + frame.assertSyncedStack(); + + Label thisOK; + masm.branchTestMagic(Assembler::NotEqual, frame.addressOfThis(), &thisOK); + + prepareVMCall(); + + masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); + pushArg(R0.scratchReg()); + + if (!callVM(ThrowUninitializedThisInfo)) + return false; + + masm.bind(&thisOK); + return true; +} + bool BaselineCompiler::emit_JSOP_THIS() { @@ -1287,6 +1311,12 @@ BaselineCompiler::emit_JSOP_THIS() return true; } + if (script->isDerivedClassConstructor()) { + frame.syncStack(0); + if (!emitCheckThis()) + return false; + } + // Keep this value in R0 frame.pushThis(); @@ -2861,7 +2891,7 @@ BaselineCompiler::emitCall() { MOZ_ASSERT(IsCallPC(pc)); - bool construct = JSOp(*pc) == JSOP_NEW; + bool construct = JSOp(*pc) == JSOP_NEW || JSOp(*pc) == JSOP_SUPERCALL; uint32_t argc = GET_ARGC(pc); frame.syncStack(0); @@ -2888,13 +2918,13 @@ BaselineCompiler::emitSpreadCall() masm.move32(Imm32(1), R0.scratchReg()); // Call IC - ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ JSOp(*pc) == JSOP_SPREADNEW, + bool construct = JSOp(*pc) == JSOP_SPREADNEW || JSOp(*pc) == JSOP_SPREADSUPERCALL; + ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, /* isSpread = */ true); if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) return false; // Update FrameInfo. - bool construct = JSOp(*pc) == JSOP_SPREADNEW; frame.popn(3 + construct); frame.push(R0); return true; @@ -2912,6 +2942,12 @@ BaselineCompiler::emit_JSOP_NEW() return emitCall(); } +bool +BaselineCompiler::emit_JSOP_SUPERCALL() +{ + return emitCall(); +} + bool BaselineCompiler::emit_JSOP_FUNCALL() { @@ -2948,6 +2984,12 @@ BaselineCompiler::emit_JSOP_SPREADNEW() return emitSpreadCall(); } +bool +BaselineCompiler::emit_JSOP_SPREADSUPERCALL() +{ + return emitSpreadCall(); +} + bool BaselineCompiler::emit_JSOP_SPREADEVAL() { @@ -3278,6 +3320,10 @@ BaselineCompiler::emit_JSOP_DEBUGGER() return true; } +typedef bool (*ThrowBadDerivedReturnFn)(JSContext*, HandleValue); +static const VMFunction ThrowBadDerivedReturnInfo = + FunctionInfo(jit::ThrowBadDerivedReturn); + typedef bool (*DebugEpilogueFn)(JSContext*, BaselineFrame*, jsbytecode*); static const VMFunction DebugEpilogueInfo = FunctionInfo(jit::DebugEpilogueOnBaselineReturn); @@ -3285,6 +3331,29 @@ static const VMFunction DebugEpilogueInfo = bool BaselineCompiler::emitReturn() { + if (script->isDerivedClassConstructor()) { + frame.syncStack(0); + + Label derivedDone, returnOK; + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &derivedDone); + masm.branchTestUndefined(Assembler::Equal, JSReturnOperand, &returnOK); + + // This is going to smash JSReturnOperand, but we don't care, because it's + // also going to throw unconditionally. + prepareVMCall(); + pushArg(JSReturnOperand); + if (!callVM(ThrowBadDerivedReturnInfo)) + return false; + masm.assumeUnreachable("Should throw on bad derived constructor return"); + + masm.bind(&returnOK); + + if (!emitCheckThis()) + return false; + + masm.bind(&derivedDone); + } + if (compileDebugInstrumentation_) { // Move return value into the frame's rval slot. masm.storeValue(JSReturnOperand, frame.addressOfReturnValue()); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 6a4eb2c430..0e4dafba48 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -201,7 +201,9 @@ namespace jit { _(JSOP_SETRVAL) \ _(JSOP_RETRVAL) \ _(JSOP_RETURN) \ - _(JSOP_NEWTARGET) + _(JSOP_NEWTARGET) \ + _(JSOP_SUPERCALL) \ + _(JSOP_SPREADSUPERCALL) class BaselineCompiler : public BaselineCompilerSpecific { @@ -305,6 +307,7 @@ class BaselineCompiler : public BaselineCompilerSpecific bool emitFormalArgAccess(uint32_t arg, bool get); bool emitUninitializedLexicalCheck(const ValueOperand& val); + bool emitCheckThis(); bool addPCMappingEntry(bool addIndexEntry); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 15fb8227bf..73d919d194 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -395,6 +395,8 @@ ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext* cx, JSScript* script, } if (val.isPrimitive()) { + if (val.isMagic(JS_UNINITIALIZED_LEXICAL)) + return true; MOZ_ASSERT(!val.isMagic()); JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType(); @@ -503,10 +505,15 @@ DoTypeMonitorFallback(JSContext* cx, BaselineFrame* frame, ICTypeMonitor_Fallbac { // It's possible that we arrived here from bailing out of Ion, and that // Ion proved that the value is dead and optimized out. In such cases, do - // nothing. - if (value.isMagic(JS_OPTIMIZED_OUT)) { - res.set(value); - return true; + // nothing. However, it's also possible that we have an uninitialized this, + // in which case we should not look for other magic values. + if (stub->monitorsThis()) { + MOZ_ASSERT_IF(value.isMagic(), value.isMagic(JS_UNINITIALIZED_LEXICAL)); + } else { + if (value.isMagic(JS_OPTIMIZED_OUT)) { + res.set(value); + return true; + } } RootedScript script(cx, frame->script()); @@ -516,7 +523,10 @@ DoTypeMonitorFallback(JSContext* cx, BaselineFrame* frame, ICTypeMonitor_Fallbac uint32_t argument; if (stub->monitorsThis()) { MOZ_ASSERT(pc == script->code()); - TypeScript::SetThis(cx, script, value); + if (value.isMagic(JS_UNINITIALIZED_LEXICAL)) + TypeScript::SetThis(cx, script, TypeSet::UnknownType()); + else + TypeScript::SetThis(cx, script, value); } else if (stub->monitorsArgument(&argument)) { MOZ_ASSERT(pc == script->code()); TypeScript::SetArgument(cx, script, argument, value); @@ -8648,6 +8658,8 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb JSOp op, uint32_t argc, Value* vp, bool constructing, bool isSpread, bool createSingleton, bool* handled) { + bool isSuper = op == JSOP_SUPERCALL || op == JSOP_SPREADSUPERCALL; + if (createSingleton || op == JSOP_EVAL || op == JSOP_STRICTEVAL) return true; @@ -8755,9 +8767,11 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb EnsureTrackPropertyTypes(cx, fun, NameToId(cx->names().prototype)); // Remember the template object associated with any script being called - // as a constructor, for later use during Ion compilation. + // as a constructor, for later use during Ion compilation. This is unsound + // for super(), as a single callsite can have multiple possible prototype object + // created (via different newTargets) RootedObject templateObject(cx); - if (constructing) { + if (constructing && !isSuper) { // If we are calling a constructor for which the new script // properties analysis has not been performed yet, don't attach a // stub. After the analysis is performed, CreateThisForFunction may @@ -8766,15 +8780,16 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb // Only attach a stub if the function already has a prototype and // we can look it up without causing side effects. + RootedObject newTarget(cx, &vp[2 + argc].toObject()); RootedValue protov(cx); - if (!GetPropertyPure(cx, fun, NameToId(cx->names().prototype), protov.address())) { + if (!GetPropertyPure(cx, newTarget, NameToId(cx->names().prototype), protov.address())) { JitSpew(JitSpew_BaselineIC, " Can't purely lookup function prototype"); return true; } if (protov.isObject()) { TaggedProto proto(&protov.toObject()); - ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, nullptr, proto, fun); + ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, nullptr, proto, newTarget); if (!group) return false; @@ -8788,7 +8803,7 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb } } - JSObject* thisObject = CreateThisForFunction(cx, fun, TenuredObject); + JSObject* thisObject = CreateThisForFunction(cx, fun, newTarget, TenuredObject); if (!thisObject) return false; @@ -8856,7 +8871,7 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb } RootedObject templateObject(cx); - if (MOZ_LIKELY(!isSpread)) { + if (MOZ_LIKELY(!isSpread && !isSuper)) { bool skipAttach = false; CallArgs args = CallArgsFromVp(argc, vp); if (!GetTemplateObjectForNative(cx, fun->native(), args, &templateObject, &skipAttach)) @@ -9546,7 +9561,8 @@ ICCall_Fallback::Compiler::postGenerateStubCode(MacroAssembler& masm, Handle(CreateThis); bool @@ -9640,24 +9656,31 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler& masm) // Stack now looks like: // [..., Callee, ThisV, Arg0V, ..., ArgNV, NewTarget, StubFrameHeader, ArgC ] + masm.loadValue(Address(masm.getStackPointer(), STUB_FRAME_SIZE + sizeof(size_t)), R1); + masm.push(masm.extractObject(R1, ExtractTemp0)); + if (isSpread_) { masm.loadValue(Address(masm.getStackPointer(), - 3 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)), R1); + 3 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t) + + sizeof(JSObject*)), + R1); } else { BaseValueIndex calleeSlot2(masm.getStackPointer(), argcReg, - 2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t)); + 2 * sizeof(Value) + STUB_FRAME_SIZE + sizeof(size_t) + + sizeof(JSObject*)); masm.loadValue(calleeSlot2, R1); } masm.push(masm.extractObject(R1, ExtractTemp0)); if (!callVM(CreateThisInfoBaseline, masm)) return false; - // Return of CreateThis must be an object. + // Return of CreateThis must be an object or uninitialized. #ifdef DEBUG - Label createdThisIsObject; - masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisIsObject); - masm.assumeUnreachable("The return of CreateThis must be an object."); - masm.bind(&createdThisIsObject); + Label createdThisOK; + masm.branchTestObject(Assembler::Equal, JSReturnOperand, &createdThisOK); + masm.branchTestMagic(Assembler::Equal, JSReturnOperand, &createdThisOK); + masm.assumeUnreachable("The return of CreateThis must be an object or uninitialized."); + masm.bind(&createdThisOK); #endif // Reset the register set from here on in. diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 826fd83ad7..910925c807 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -111,7 +111,8 @@ EnterBaseline(JSContext* cx, EnterJitData& data) EnterJitCode enter = cx->runtime()->jitRuntime()->enterBaseline(); // Caller must construct |this| before invoking the Ion function. - MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject()); + MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject() || + data.maxArgv[0].isMagic(JS_UNINITIALIZED_LEXICAL)); data.result.setInt32(data.numActualArgs); { @@ -131,9 +132,12 @@ EnterBaseline(JSContext* cx, EnterJitData& data) MOZ_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride()); - // Jit callers wrap primitive constructor return. - if (!data.result.isMagic() && data.constructing && data.result.isPrimitive()) + // Jit callers wrap primitive constructor return, except for derived + // class constructors, which are forced to do it themselves. + if (!data.result.isMagic() && data.constructing && data.result.isPrimitive()) { + MOZ_ASSERT(data.maxArgv[0].isObject()); data.result = data.maxArgv[0]; + } // Release temporary buffer used for OSR into Ion. cx->runtime()->getJitRuntime(cx)->freeOsrTempData(); @@ -211,7 +215,7 @@ jit::EnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, jsbytecode* pc) return JitExec_Aborted; vals.infallibleAppend(thisv); - + if (fp->isFunctionFrame()) vals.infallibleAppend(fp->newTarget()); else @@ -306,15 +310,6 @@ CanEnterBaselineJIT(JSContext* cx, HandleScript script, InterpreterFrame* osrFra MethodStatus jit::CanEnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, bool newType) { - // If constructing, allocate a new |this| object. - if (fp->isConstructing() && fp->functionThis().isPrimitive()) { - RootedObject callee(cx, &fp->callee()); - RootedObject obj(cx, CreateThisForFunction(cx, callee, newType ? SingletonObject : GenericObject)); - if (!obj) - return Method_Skipped; - fp->functionThis().setObject(*obj); - } - if (!CheckFrame(fp)) return Method_CantCompile; @@ -356,8 +351,13 @@ jit::CanEnterBaselineMethod(JSContext* cx, RunState& state) return Method_CantCompile; } - if (!state.maybeCreateThisForConstructor(cx)) - return Method_Skipped; + if (!state.maybeCreateThisForConstructor(cx)) { + if (cx->isThrowingOutOfMemory()) { + cx->recoverFromOutOfMemory(); + return Method_Skipped; + } + return Method_Error; + } } else { MOZ_ASSERT(state.isExecute()); ExecuteType type = state.asExecute()->type(); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 98ef39910a..0d1a7ac0da 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4928,13 +4928,19 @@ CodeGenerator::visitInitPropGetterSetter(LInitPropGetterSetter* lir) callVM(InitPropGetterSetterInfo, lir); } -typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee, MutableHandleValue rval); +typedef bool (*CreateThisFn)(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval); static const VMFunction CreateThisInfoCodeGen = FunctionInfo(CreateThis); void CodeGenerator::visitCreateThis(LCreateThis* lir) { const LAllocation* callee = lir->getCallee(); + const LAllocation* newTarget = lir->getNewTarget(); + + if (newTarget->isConstant()) + pushArg(ImmGCPtr(&newTarget->toConstant()->toObject())); + else + pushArg(ToRegister(newTarget)); if (callee->isConstant()) pushArg(ImmGCPtr(&callee->toConstant()->toObject())); @@ -4945,12 +4951,14 @@ CodeGenerator::visitCreateThis(LCreateThis* lir) } static JSObject* -CreateThisForFunctionWithProtoWrapper(JSContext* cx, js::HandleObject callee, HandleObject proto) +CreateThisForFunctionWithProtoWrapper(JSContext* cx, HandleObject callee, HandleObject newTarget, + HandleObject proto) { - return CreateThisForFunctionWithProto(cx, callee, proto); + return CreateThisForFunctionWithProto(cx, callee, newTarget, proto); } -typedef JSObject* (*CreateThisWithProtoFn)(JSContext* cx, HandleObject callee, HandleObject proto); +typedef JSObject* (*CreateThisWithProtoFn)(JSContext* cx, HandleObject callee, + HandleObject newTarget, HandleObject proto); static const VMFunction CreateThisWithProtoInfo = FunctionInfo(CreateThisForFunctionWithProtoWrapper); @@ -4958,6 +4966,7 @@ void CodeGenerator::visitCreateThisWithProto(LCreateThisWithProto* lir) { const LAllocation* callee = lir->getCallee(); + const LAllocation* newTarget = lir->getNewTarget(); const LAllocation* proto = lir->getPrototype(); if (proto->isConstant()) @@ -4965,6 +4974,11 @@ CodeGenerator::visitCreateThisWithProto(LCreateThisWithProto* lir) else pushArg(ToRegister(proto)); + if (newTarget->isConstant()) + pushArg(ImmGCPtr(&newTarget->toConstant()->toObject())); + else + pushArg(ToRegister(newTarget)); + if (callee->isConstant()) pushArg(ImmGCPtr(&callee->toConstant()->toObject())); else @@ -10324,6 +10338,19 @@ CodeGenerator::visitNewTarget(LNewTarget *ins) masm.bind(&done); } +void +CodeGenerator::visitCheckReturn(LCheckReturn* ins) +{ + ValueOperand returnValue = ToValue(ins, LCheckReturn::ReturnValue); + ValueOperand thisValue = ToValue(ins, LCheckReturn::ThisValue); + Label bail, noChecks; + masm.branchTestObject(Assembler::Equal, returnValue, &noChecks); + masm.branchTestUndefined(Assembler::NotEqual, returnValue, &bail); + masm.branchTestMagicValue(Assembler::Equal, thisValue, JS_UNINITIALIZED_LEXICAL, &bail); + bailoutFrom(&bail, ins->snapshot()); + masm.bind(&noChecks); +} + // Out-of-line math_random_no_outparam call for LRandom. class OutOfLineRandom : public OutOfLineCodeBase { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 7dc9e70add..d1669456ff 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -335,6 +335,7 @@ class CodeGenerator : public CodeGeneratorSpecific void visitDebugger(LDebugger* ins); void visitNewTarget(LNewTarget* ins); void visitArrowNewTarget(LArrowNewTarget* ins); + void visitCheckReturn(LCheckReturn* ins); void visitCheckOverRecursed(LCheckOverRecursed* lir); void visitCheckOverRecursedFailure(CheckOverRecursedFailure* ool); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index da510665ac..e300353c5e 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2549,8 +2549,11 @@ jit::CanEnter(JSContext* cx, RunState& state) } if (!state.maybeCreateThisForConstructor(cx)) { - cx->recoverFromOutOfMemory(); - return Method_Skipped; + if (cx->isThrowingOutOfMemory()) { + cx->recoverFromOutOfMemory(); + return Method_Skipped; + } + return Method_Error; } } @@ -2661,7 +2664,8 @@ EnterIon(JSContext* cx, EnterJitData& data) EnterJitCode enter = cx->runtime()->jitRuntime()->enterIon(); // Caller must construct |this| before invoking the Ion function. - MOZ_ASSERT_IF(data.constructing, data.maxArgv[0].isObject()); + MOZ_ASSERT_IF(data.constructing, + data.maxArgv[0].isObject() || data.maxArgv[0].isMagic(JS_UNINITIALIZED_LEXICAL)); data.result.setInt32(data.numActualArgs); { @@ -2674,9 +2678,13 @@ EnterIon(JSContext* cx, EnterJitData& data) MOZ_ASSERT(!cx->runtime()->jitRuntime()->hasIonReturnOverride()); - // Jit callers wrap primitive constructor return. - if (!data.result.isMagic() && data.constructing && data.result.isPrimitive()) + // Jit callers wrap primitive constructor return, except for derived class constructors. + if (!data.result.isMagic() && data.constructing && + data.result.isPrimitive()) + { + MOZ_ASSERT(data.maxArgv[0].isObject()); data.result = data.maxArgv[0]; + } // Release temporary buffer used for OSR into Ion. cx->runtime()->getJitRuntime(cx)->freeOsrTempData(); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index c9d754198d..d75ffed62c 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1875,7 +1875,8 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CALL: case JSOP_NEW: - return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW); + case JSOP_SUPERCALL: + return jsop_call(GET_ARGC(pc), (JSOp)*pc == JSOP_NEW || (JSOp)*pc == JSOP_SUPERCALL); case JSOP_EVAL: case JSOP_STRICTEVAL: @@ -4488,6 +4489,15 @@ IonBuilder::processReturn(JSOp op) MOZ_CRASH("unknown return op"); } + if (script()->isDerivedClassConstructor() && + def->type() != MIRType_Object) + { + MOZ_ASSERT(info().funMaybeLazy() && info().funMaybeLazy()->isClassConstructor()); + MCheckReturn* checkRet = MCheckReturn::New(alloc(), def, current->getSlot(info().thisSlot())); + current->add(checkRet); + def = checkRet; + } + MReturn* ret = MReturn::New(alloc(), def); current->end(ret); @@ -4963,7 +4973,7 @@ IonBuilder::inlineScriptedCall(CallInfo& callInfo, JSFunction* target) // Create new |this| on the caller-side for inlined constructors. if (callInfo.constructing()) { - MDefinition* thisDefn = createThis(target, callInfo.fun()); + MDefinition* thisDefn = createThis(target, callInfo.fun(), callInfo.getNewTarget()); if (!thisDefn) return false; callInfo.setThis(thisDefn); @@ -6058,7 +6068,7 @@ IonBuilder::createCallObject(MDefinition* callee, MDefinition* scope) } MDefinition* -IonBuilder::createThisScripted(MDefinition* callee) +IonBuilder::createThisScripted(MDefinition* callee, MDefinition* newTarget) { // Get callee.prototype. // @@ -6073,12 +6083,12 @@ IonBuilder::createThisScripted(MDefinition* callee) // and thus invalidation. MInstruction* getProto; if (!invalidatedIdempotentCache()) { - MGetPropertyCache* getPropCache = MGetPropertyCache::New(alloc(), callee, names().prototype, + MGetPropertyCache* getPropCache = MGetPropertyCache::New(alloc(), newTarget, names().prototype, /* monitored = */ false); getPropCache->setIdempotent(); getProto = getPropCache; } else { - MCallGetProperty* callGetProp = MCallGetProperty::New(alloc(), callee, names().prototype, + MCallGetProperty* callGetProp = MCallGetProperty::New(alloc(), newTarget, names().prototype, /* callprop = */ false); callGetProp->setIdempotent(); getProto = callGetProp; @@ -6086,7 +6096,7 @@ IonBuilder::createThisScripted(MDefinition* callee) current->add(getProto); // Create this from prototype - MCreateThisWithProto* createThis = MCreateThisWithProto::New(alloc(), callee, getProto); + MCreateThisWithProto* createThis = MCreateThisWithProto::New(alloc(), callee, newTarget, getProto); current->add(createThis); return createThis; @@ -6154,6 +6164,8 @@ IonBuilder::createThisScriptedBaseline(MDefinition* callee) return nullptr; JSObject* templateObject = inspector->getTemplateObject(pc); + if (!templateObject) + return nullptr; if (!templateObject->is() && !templateObject->is()) return nullptr; @@ -6204,14 +6216,14 @@ IonBuilder::createThisScriptedBaseline(MDefinition* callee) } MDefinition* -IonBuilder::createThis(JSFunction* target, MDefinition* callee) +IonBuilder::createThis(JSFunction* target, MDefinition* callee, MDefinition* newTarget) { // Create |this| for unknown target. if (!target) { if (MDefinition* createThis = createThisScriptedBaseline(callee)) return createThis; - MCreateThis* createThis = MCreateThis::New(alloc(), callee); + MCreateThis* createThis = MCreateThis::New(alloc(), callee, newTarget); current->add(createThis); return createThis; } @@ -6226,6 +6238,11 @@ IonBuilder::createThis(JSFunction* target, MDefinition* callee) return magic; } + if (target->isDerivedClassConstructor()) { + MOZ_ASSERT(target->isClassConstructor()); + return constant(MagicValue(JS_UNINITIALIZED_LEXICAL)); + } + // Try baking in the prototype. if (MDefinition* createThis = createThisScriptedSingleton(target, callee)) return createThis; @@ -6233,7 +6250,7 @@ IonBuilder::createThis(JSFunction* target, MDefinition* callee) if (MDefinition* createThis = createThisScriptedBaseline(callee)) return createThis; - return createThisScripted(callee); + return createThisScripted(callee, newTarget); } bool @@ -6623,7 +6640,7 @@ IonBuilder::makeCallHelper(JSFunction* target, CallInfo& callInfo) // Inline the constructor on the caller-side. if (callInfo.constructing()) { - MDefinition* create = createThis(target, callInfo.fun()); + MDefinition* create = createThis(target, callInfo.fun(), callInfo.getNewTarget()); if (!create) { abort("Failure inlining constructor for call."); return nullptr; @@ -12754,10 +12771,22 @@ IonBuilder::jsop_this() if (script()->strict() || info().funMaybeLazy()->isSelfHostedBuiltin()) { // No need to wrap primitive |this| in strict mode or self-hosted code. - current->pushSlot(info().thisSlot()); + MDefinition* thisVal = current->getSlot(info().thisSlot()); + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(info().funMaybeLazy()->isClassConstructor()); + MOZ_ASSERT(script()->strict()); + + MLexicalCheck* checkThis = MLexicalCheck::New(alloc(), thisVal, Bailout_UninitializedThis); + current->add(checkThis); + thisVal = checkThis; + } + + current->push(thisVal); return true; } + MOZ_ASSERT(!info().funMaybeLazy()->isClassConstructor()); + if (thisTypes && (thisTypes->getKnownMIRType() == MIRType_Object || (thisTypes->empty() && baselineFrame_ && baselineFrame_->thisType.isSomeObject()))) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index bb851bda66..9ab64f0aa5 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -380,10 +380,10 @@ class IonBuilder JSObject* getSingletonPrototype(JSFunction* target); - MDefinition* createThisScripted(MDefinition* callee); + MDefinition* createThisScripted(MDefinition* callee, MDefinition* newTarget); MDefinition* createThisScriptedSingleton(JSFunction* target, MDefinition* callee); MDefinition* createThisScriptedBaseline(MDefinition* callee); - MDefinition* createThis(JSFunction* target, MDefinition* callee); + MDefinition* createThis(JSFunction* target, MDefinition* callee, MDefinition* newTarget); MInstruction* createDeclEnvObject(MDefinition* callee, MDefinition* scopeObj); MInstruction* createCallObject(MDefinition* callee, MDefinition* scopeObj); diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index 4a4a463acd..099e3a1970 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -111,8 +111,13 @@ enum BailoutKind // We hit a |debugger;| statement. Bailout_Debugger, - // END Normal bailouts + // |this| used uninitialized in a derived constructor + Bailout_UninitializedThis, + // Derived constructors must return object or undefined + Bailout_BadDerivedConstructorReturn, + + // END Normal bailouts // Bailouts caused by invalid assumptions based on Baseline code. // Causes immediate invalidation. @@ -151,7 +156,7 @@ enum BailoutKind Bailout_UninitializedLexical, // A bailout to baseline from Ion on exception to handle Debugger hooks. - Bailout_IonExceptionDebugMode, + Bailout_IonExceptionDebugMode }; inline const char* @@ -209,6 +214,10 @@ BailoutKindString(BailoutKind kind) return "Bailout_InitialState"; case Bailout_Debugger: return "Bailout_Debugger"; + case Bailout_UninitializedThis: + return "Bailout_UninitializedThis"; + case Bailout_BadDerivedConstructorReturn: + return "Bailout_BadDerivedConstructorReturn"; // Bailouts caused by invalid assumptions. case Bailout_OverflowInvalidate: diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 0397572b5d..7a76293016 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -328,6 +328,7 @@ LIRGenerator::visitCreateThisWithProto(MCreateThisWithProto* ins) { LCreateThisWithProto* lir = new(alloc()) LCreateThisWithProto(useRegisterOrConstantAtStart(ins->getCallee()), + useRegisterOrConstantAtStart(ins->getNewTarget()), useRegisterOrConstantAtStart(ins->getPrototype())); defineReturn(lir, ins); assignSafepoint(lir, ins); @@ -336,7 +337,8 @@ LIRGenerator::visitCreateThisWithProto(MCreateThisWithProto* ins) void LIRGenerator::visitCreateThis(MCreateThis* ins) { - LCreateThis* lir = new(alloc()) LCreateThis(useRegisterOrConstantAtStart(ins->getCallee())); + LCreateThis* lir = new(alloc()) LCreateThis(useRegisterOrConstantAtStart(ins->getCallee()), + useRegisterOrConstantAtStart(ins->getNewTarget())); defineReturn(lir, ins); assignSafepoint(lir, ins); } @@ -4258,7 +4260,7 @@ LIRGenerator::visitLexicalCheck(MLexicalCheck* ins) MOZ_ASSERT(input->type() == MIRType_Value); LLexicalCheck* lir = new(alloc()) LLexicalCheck(); useBox(lir, LLexicalCheck::Input, input); - assignSnapshot(lir, Bailout_UninitializedLexical); + assignSnapshot(lir, ins->bailoutKind()); add(lir, ins); redefine(ins, input); } @@ -4293,6 +4295,22 @@ LIRGenerator::visitAtomicIsLockFree(MAtomicIsLockFree* ins) define(new(alloc()) LAtomicIsLockFree(useRegister(ins->input())), ins); } +void +LIRGenerator::visitCheckReturn(MCheckReturn* ins) +{ + MDefinition* retVal = ins->returnValue(); + MDefinition* thisVal = ins->thisValue(); + MOZ_ASSERT(retVal->type() == MIRType_Value); + MOZ_ASSERT(thisVal->type() == MIRType_Value); + + LCheckReturn* lir = new(alloc()) LCheckReturn(); + useBoxAtStart(lir, LCheckReturn::ReturnValue, retVal); + useBoxAtStart(lir, LCheckReturn::ThisValue, thisVal); + assignSnapshot(lir, Bailout_BadDerivedConstructorReturn); + add(lir, ins); + redefine(ins, retVal); +} + static void SpewResumePoint(MBasicBlock* block, MInstruction* ins, MResumePoint* resumePoint) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index addf1536b9..aaed7195d7 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -306,6 +306,7 @@ class LIRGenerator : public LIRGeneratorSpecific void visitNewTarget(MNewTarget* ins); void visitArrowNewTarget(MArrowNewTarget* ins); void visitAtomicIsLockFree(MAtomicIsLockFree* ins); + void visitCheckReturn(MCheckReturn* ins); }; } // namespace jit diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 356e56e134..39073b5dac 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -4614,11 +4614,11 @@ class MCreateThisWithTemplate // Caller-side allocation of |this| for |new|: // Given a prototype operand, construct |this| for JSOP_NEW. class MCreateThisWithProto - : public MBinaryInstruction, - public MixPolicy, ObjectPolicy<1> >::Data + : public MTernaryInstruction, + public Mix3Policy, ObjectPolicy<1>, ObjectPolicy<2> >::Data { - MCreateThisWithProto(MDefinition* callee, MDefinition* prototype) - : MBinaryInstruction(callee, prototype) + MCreateThisWithProto(MDefinition* callee, MDefinition* newTarget, MDefinition* prototype) + : MTernaryInstruction(callee, newTarget, prototype) { setResultType(MIRType_Object); } @@ -4626,17 +4626,20 @@ class MCreateThisWithProto public: INSTRUCTION_HEADER(CreateThisWithProto) static MCreateThisWithProto* New(TempAllocator& alloc, MDefinition* callee, - MDefinition* prototype) + MDefinition* newTarget, MDefinition* prototype) { - return new(alloc) MCreateThisWithProto(callee, prototype); + return new(alloc) MCreateThisWithProto(callee, newTarget, prototype); } MDefinition* getCallee() const { return getOperand(0); } - MDefinition* getPrototype() const { + MDefinition* getNewTarget() const { return getOperand(1); } + MDefinition* getPrototype() const { + return getOperand(2); + } // Although creation of |this| modifies global state, it is safely repeatable. AliasSet getAliasSet() const override { @@ -4650,25 +4653,28 @@ class MCreateThisWithProto // Caller-side allocation of |this| for |new|: // Constructs |this| when possible, else MagicValue(JS_IS_CONSTRUCTING). class MCreateThis - : public MUnaryInstruction, - public ObjectPolicy<0>::Data + : public MBinaryInstruction, + public MixPolicy, ObjectPolicy<1> >::Data { - explicit MCreateThis(MDefinition* callee) - : MUnaryInstruction(callee) + explicit MCreateThis(MDefinition* callee, MDefinition* newTarget) + : MBinaryInstruction(callee, newTarget) { setResultType(MIRType_Value); } public: INSTRUCTION_HEADER(CreateThis) - static MCreateThis* New(TempAllocator& alloc, MDefinition* callee) + static MCreateThis* New(TempAllocator& alloc, MDefinition* callee, MDefinition* newTarget) { - return new(alloc) MCreateThis(callee); + return new(alloc) MCreateThis(callee, newTarget); } MDefinition* getCallee() const { return getOperand(0); } + MDefinition* getNewTarget() const { + return getOperand(0); + } // Although creation of |this| modifies global state, it is safely repeatable. AliasSet getAliasSet() const override { @@ -7231,8 +7237,10 @@ class MLexicalCheck : public MUnaryInstruction, public BoxPolicy<0>::Data { - explicit MLexicalCheck(MDefinition* input) - : MUnaryInstruction(input) + BailoutKind kind_; + explicit MLexicalCheck(MDefinition* input, BailoutKind kind) + : MUnaryInstruction(input), + kind_(kind) { setResultType(MIRType_Value); setResultTypeSet(input->resultTypeSet()); @@ -7243,8 +7251,9 @@ class MLexicalCheck public: INSTRUCTION_HEADER(LexicalCheck) - static MLexicalCheck* New(TempAllocator& alloc, MDefinition* input) { - return new(alloc) MLexicalCheck(input); + static MLexicalCheck* New(TempAllocator& alloc, MDefinition* input, + BailoutKind kind = Bailout_UninitializedLexical) { + return new(alloc) MLexicalCheck(input, kind); } AliasSet getAliasSet() const override { @@ -7255,6 +7264,10 @@ class MLexicalCheck return getOperand(0); } + BailoutKind bailoutKind() const { + return kind_; + } + bool congruentTo(const MDefinition* ins) const override { return congruentIfOperandsEqual(ins); } @@ -12931,6 +12944,33 @@ class MHasClass } }; +class MCheckReturn + : public MBinaryInstruction, + public BoxInputsPolicy::Data +{ + explicit MCheckReturn(MDefinition* retVal, MDefinition* thisVal) + : MBinaryInstruction(retVal, thisVal) + { + setGuard(); + setResultType(MIRType_Value); + setResultTypeSet(retVal->resultTypeSet()); + } + + public: + INSTRUCTION_HEADER(CheckReturn) + + static MCheckReturn* New(TempAllocator& alloc, MDefinition* retVal, MDefinition* thisVal) { + return new (alloc) MCheckReturn(retVal, thisVal); + } + + MDefinition* returnValue() const { + return getOperand(0); + } + MDefinition* thisValue() const { + return getOperand(1); + } +}; + // Increase the warm-up counter of the provided script upon execution and test if // the warm-up counter surpasses the threshold. Upon hit it will recompile the // outermost script (i.e. not the inlined script). diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 6eaef4158c..8c4b8514cd 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -277,7 +277,8 @@ namespace jit { _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ - _(ArrowNewTarget) + _(ArrowNewTarget) \ + _(CheckReturn) // Forward declarations of MIR types. #define FORWARD_DECLARE(op) class M##op; diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index bdcf34fa9c..07d9491f2d 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -1196,6 +1196,7 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) _(Mix3Policy, IntPolicy<1>, IntPolicy<2> >) \ _(Mix3Policy, IntPolicy<1>, TruncateToInt32Policy<2> >) \ _(Mix3Policy, ObjectPolicy<1>, IntPolicy<2> >) \ + _(Mix3Policy, ObjectPolicy<1>, ObjectPolicy<2> >) \ _(Mix3Policy, IntPolicy<1>, IntPolicy<2>>) \ _(Mix3Policy, ObjectPolicy<1>, StringPolicy<2> >) \ _(Mix3Policy, StringPolicy<1>, StringPolicy<2> >) \ diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index e156a6f791..2b0ec91788 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -565,7 +565,7 @@ GetIntrinsicValue(JSContext* cx, HandlePropertyName name, MutableHandleValue rva } bool -CreateThis(JSContext* cx, HandleObject callee, MutableHandleValue rval) +CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval) { rval.set(MagicValue(JS_IS_CONSTRUCTING)); @@ -575,10 +575,14 @@ CreateThis(JSContext* cx, HandleObject callee, MutableHandleValue rval) JSScript* script = fun->getOrCreateScript(cx); if (!script || !script->ensureHasTypes(cx)) return false; - JSObject* thisObj = CreateThisForFunction(cx, callee, GenericObject); - if (!thisObj) - return false; - rval.set(ObjectValue(*thisObj)); + if (script->isDerivedClassConstructor()) { + rval.set(MagicValue(JS_UNINITIALIZED_LEXICAL)); + } else { + JSObject* thisObj = CreateThisForFunction(cx, callee, newTarget, GenericObject); + if (!thisObj) + return false; + rval.set(ObjectValue(*thisObj)); + } } } @@ -1291,5 +1295,18 @@ ThrowUninitializedLexical(JSContext* cx) return false; } +bool +ThrowBadDerivedReturn(JSContext* cx, HandleValue v) +{ + ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, v, nullptr); + return false; +} + +bool +BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame) +{ + return ThrowUninitializedThis(cx, frame); +} + } // namespace jit } // namespace js diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 944ded9d15..0e344ac15c 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -634,7 +634,7 @@ bool OperatorInI(JSContext* cx, uint32_t index, HandleObject obj, bool* out); bool GetIntrinsicValue(JSContext* cx, HandlePropertyName name, MutableHandleValue rval); -bool CreateThis(JSContext* cx, HandleObject callee, MutableHandleValue rval); +bool CreateThis(JSContext* cx, HandleObject callee, HandleObject newTarget, MutableHandleValue rval); void GetDynamicName(JSContext* cx, JSObject* scopeChain, JSString* str, Value* vp); @@ -734,6 +734,8 @@ IonMarkFunction(MIRType type) bool ObjectIsCallable(JSObject* obj); bool ThrowUninitializedLexical(JSContext* cx); +bool BaselineThrowUninitializedThis(JSContext* cx, BaselineFrame* frame); +bool ThrowBadDerivedReturn(JSContext* cx, HandleValue v); } // namespace jit } // namespace js diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 178bc8d243..fafb2e0624 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -1300,19 +1300,23 @@ class LToIdV : public LInstructionHelper // Allocate an object for |new| on the caller-side, // when there is no templateObject or prototype known -class LCreateThis : public LCallInstructionHelper +class LCreateThis : public LCallInstructionHelper { public: LIR_HEADER(CreateThis) - explicit LCreateThis(const LAllocation& callee) + LCreateThis(const LAllocation& callee, const LAllocation& newTarget) { setOperand(0, callee); + setOperand(1, newTarget); } const LAllocation* getCallee() { return getOperand(0); } + const LAllocation* getNewTarget() { + return getOperand(1); + } MCreateThis* mir() const { return mir_->toCreateThis(); @@ -1321,23 +1325,28 @@ class LCreateThis : public LCallInstructionHelper // Allocate an object for |new| on the caller-side, // when the prototype is known. -class LCreateThisWithProto : public LCallInstructionHelper<1, 2, 0> +class LCreateThisWithProto : public LCallInstructionHelper<1, 3, 0> { public: LIR_HEADER(CreateThisWithProto) - LCreateThisWithProto(const LAllocation& callee, const LAllocation& prototype) + LCreateThisWithProto(const LAllocation& callee, const LAllocation& newTarget, + const LAllocation& prototype) { setOperand(0, callee); - setOperand(1, prototype); + setOperand(1, newTarget); + setOperand(2, prototype); } const LAllocation* getCallee() { return getOperand(0); } - const LAllocation* getPrototype() { + const LAllocation* getNewTarget() { return getOperand(1); } + const LAllocation* getPrototype() { + return getOperand(2); + } MCreateThis* mir() const { return mir_->toCreateThis(); @@ -7247,6 +7256,15 @@ class LRandom : public LInstructionHelper<1, 0, LRANDOM_NUM_TEMPS> } }; +class LCheckReturn : public LCallInstructionHelper +{ + public: + static const size_t ReturnValue = 0; + static const size_t ThisValue = BOX_PIECES; + + LIR_HEADER(CheckReturn) +}; + } // namespace jit } // namespace js diff --git a/js/src/jit/shared/LOpcodes-shared.h b/js/src/jit/shared/LOpcodes-shared.h index 6393fa00f3..aaede6dbcd 100644 --- a/js/src/jit/shared/LOpcodes-shared.h +++ b/js/src/jit/shared/LOpcodes-shared.h @@ -368,6 +368,7 @@ _(GlobalNameConflictsCheck) \ _(Debugger) \ _(NewTarget) \ - _(ArrowNewTarget) + _(ArrowNewTarget) \ + _(CheckReturn) #endif /* jit_shared_LOpcodes_shared_h */ diff --git a/js/src/js.msg b/js/src/js.msg index c5eeb78d38..21b4430a6b 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -106,6 +106,8 @@ MSG_DEF(JSMSG_TERMINATED, 1, JSEXN_ERR, "Script terminated by timeo MSG_DEF(JSMSG_PROTO_NOT_OBJORNULL, 1, JSEXN_TYPEERR, "{0}.prototype is not an object or null") MSG_DEF(JSMSG_CANT_CALL_CLASS_CONSTRUCTOR, 0, JSEXN_TYPEERR, "class constructors must be invoked with |new|") MSG_DEF(JSMSG_DISABLED_DERIVED_CLASS, 1, JSEXN_INTERNALERR, "{0} temporarily disallowed in derived class constructors") +MSG_DEF(JSMSG_UNINITIALIZED_THIS, 1, JSEXN_REFERENCEERR, "|this| used uninitialized in {0} class constructor") +MSG_DEF(JSMSG_BAD_DERIVED_RETURN, 1, JSEXN_TYPEERR, "derived class constructor returned invalid value {0}") // JSON MSG_DEF(JSMSG_JSON_BAD_PARSE, 3, JSEXN_SYNTAXERR, "JSON.parse: {0} at line {1} column {2} of the JSON data") @@ -212,6 +214,7 @@ MSG_DEF(JSMSG_BAD_STRICT_ASSIGN, 1, JSEXN_SYNTAXERR, "can't assign to {0} MSG_DEF(JSMSG_BAD_SWITCH, 0, JSEXN_SYNTAXERR, "invalid switch statement") MSG_DEF(JSMSG_BAD_SUPER, 0, JSEXN_SYNTAXERR, "invalid use of keyword 'super'") MSG_DEF(JSMSG_BAD_SUPERPROP, 1, JSEXN_SYNTAXERR, "use of super {0} accesses only valid within methods or eval code within methods") +MSG_DEF(JSMSG_BAD_SUPERCALL, 0, JSEXN_SYNTAXERR, "super() is only valid in derived class constructors") MSG_DEF(JSMSG_BRACKET_AFTER_ARRAY_COMPREHENSION, 0, JSEXN_SYNTAXERR, "missing ] after array comprehension") MSG_DEF(JSMSG_BRACKET_AFTER_LIST, 0, JSEXN_SYNTAXERR, "missing ] after element list") MSG_DEF(JSMSG_BRACKET_IN_INDEX, 0, JSEXN_SYNTAXERR, "missing ] in index expression") @@ -506,6 +509,7 @@ MSG_DEF(JSMSG_NO_INDEXED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have an // Super MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involving 'super'") +MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in derived class constructor") // Modules MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *") diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 591c861c0a..77180dd75a 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -300,94 +300,6 @@ JS_GetEmptyString(JSRuntime* rt) namespace js { -JS_PUBLIC_API(bool) -IterPerformanceStats(JSContext* cx, - PerformanceStatsWalker walker, - PerformanceData* processStats, - void* closure) -{ - // As a PerformanceGroup is typically associated to several - // compartments, use a HashSet to make sure that we only report - // each PerformanceGroup once. - typedef HashSet, - js::SystemAllocPolicy> Set; - Set set; - if (!set.init(100)) { - return false; - } - - JSRuntime* rt = JS_GetRuntime(cx); - - // First report the shared groups - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - JSCompartment* compartment = c.get(); - if (!c->principals()) { - // Compartments without principals could show up here, but - // reporting them doesn't really make sense. - continue; - } - if (!c->performanceMonitoring.hasSharedGroup()) { - // Don't report compartments that do not even have a PerformanceGroup. - continue; - } - js::AutoCompartment autoCompartment(cx, compartment); - RefPtr group = compartment->performanceMonitoring.getSharedGroup(cx); - if (group->data.ticks == 0) { - // Don't report compartments that have never been used. - continue; - } - - Set::AddPtr ptr = set.lookupForAdd(group); - if (ptr) { - // Don't report the same group twice. - continue; - } - - if (!(*walker)(cx, - group->data, group->uid, nullptr, - closure)) { - // Issue in callback - return false; - } - if (!set.add(ptr, group)) { - // Memory issue - return false; - } - } - - // Then report the own groups - for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { - JSCompartment* compartment = c.get(); - if (!c->principals()) { - // Compartments without principals could show up here, but - // reporting them doesn't really make sense. - continue; - } - if (!c->performanceMonitoring.hasOwnGroup()) { - // Don't report compartments that do not even have a PerformanceGroup. - continue; - } - js::AutoCompartment autoCompartment(cx, compartment); - RefPtr ownGroup = compartment->performanceMonitoring.getOwnGroup(); - if (ownGroup->data.ticks == 0) { - // Don't report compartments that have never been used. - continue; - } - RefPtr sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx); - if (!(*walker)(cx, - ownGroup->data, ownGroup->uid, &sharedGroup->uid, - closure)) { - // Issue in callback - return false; - } - } - - // Finally, report the process stats - *processStats = rt->stopwatch.performance.getOwnGroup()->data; - return true; -} - void AssertHeapIsIdle(JSRuntime* rt) { @@ -4492,17 +4404,7 @@ JS_DecompileFunction(JSContext* cx, HandleFunction fun, unsigned indent) AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, fun); - return FunctionToString(cx, fun, false, !(indent & JS_DONT_PRETTY_PRINT)); -} - -JS_PUBLIC_API(JSString*) -JS_DecompileFunctionBody(JSContext* cx, HandleFunction fun, unsigned indent) -{ - MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); - AssertHeapIsIdle(cx); - CHECK_REQUEST(cx); - assertSameCompartment(cx, fun); - return FunctionToString(cx, fun, true, !(indent & JS_DONT_PRETTY_PRINT)); + return FunctionToString(cx, fun, !(indent & JS_DONT_PRETTY_PRINT)); } MOZ_NEVER_INLINE static bool diff --git a/js/src/jsapi.h b/js/src/jsapi.h index fa41b45f54..b1e6c6e994 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -9,6 +9,7 @@ #ifndef jsapi_h #define jsapi_h +#include "mozilla/AlreadyAddRefed.h" #include "mozilla/FloatingPoint.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Range.h" @@ -65,7 +66,7 @@ class JS_PUBLIC_API(AutoCheckRequestDepth) #endif /* JS_DEBUG */ -/* AutoValueArray roots an internal fixed-size array of Values. */ +/** AutoValueArray roots an internal fixed-size array of Values. */ template class MOZ_RAII AutoValueArray : public AutoGCRooter { @@ -449,7 +450,7 @@ class MOZ_RAII AutoHashSetRooter : protected AutoGCRooter MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -/* +/** * Custom rooting behavior for internal and external clients. */ class MOZ_RAII JS_PUBLIC_API(CustomAutoRooter) : private AutoGCRooter @@ -465,14 +466,14 @@ class MOZ_RAII JS_PUBLIC_API(CustomAutoRooter) : private AutoGCRooter friend void AutoGCRooter::trace(JSTracer* trc); protected: - /* Supplied by derived class to trace roots. */ + /** Supplied by derived class to trace roots. */ virtual void trace(JSTracer* trc) = 0; private: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -/* A handle to an array of rooted values. */ +/** A handle to an array of rooted values. */ class HandleValueArray { const size_t length_; @@ -489,10 +490,10 @@ class HandleValueArray template MOZ_IMPLICIT HandleValueArray(const AutoValueArray& values) : length_(N), elements_(values.begin()) {} - /* CallArgs must already be rooted somewhere up the stack. */ + /** CallArgs must already be rooted somewhere up the stack. */ MOZ_IMPLICIT HandleValueArray(const JS::CallArgs& args) : length_(args.length()), elements_(args.array()) {} - /* Use with care! Only call this if the data is guaranteed to be marked. */ + /** Use with care! Only call this if the data is guaranteed to be marked. */ static HandleValueArray fromMarkedLocation(size_t len, const Value* elements) { return HandleValueArray(len, elements); } @@ -542,7 +543,7 @@ typedef enum JSContextOp { JSCONTEXT_DESTROY } JSContextOp; -/* +/** * The possible values for contextOp when the runtime calls the callback are: * JSCONTEXT_NEW JS_NewContext successfully created a new JSContext * instance. The callback can initialize the instance as @@ -567,14 +568,14 @@ typedef void (* JSGCCallback)(JSRuntime* rt, JSGCStatus status, void* data); typedef enum JSFinalizeStatus { - /* + /** * Called when preparing to sweep a group of compartments, before anything * has been swept. The collector will not yield to the mutator before * calling the callback with JSFINALIZE_GROUP_END status. */ JSFINALIZE_GROUP_START, - /* + /** * Called when preparing to sweep a group of compartments. Weak references * to unmarked things have been removed and things that are not swept * incrementally have been finalized at this point. The collector may yield @@ -582,7 +583,7 @@ typedef enum JSFinalizeStatus { */ JSFINALIZE_GROUP_END, - /* + /** * Called at the end of collection when everything has been swept. */ JSFINALIZE_COLLECTION_END @@ -600,7 +601,7 @@ typedef bool typedef void (* JSErrorReporter)(JSContext* cx, const char* message, JSErrorReport* report); -/* +/** * Possible exception types. These types are part of a JSErrorFormatString * structure. They define which error to throw in case of a runtime error. * JSEXN_NONE marks an unthrowable error. @@ -619,13 +620,13 @@ typedef enum JSExnType { } JSExnType; typedef struct JSErrorFormatString { - /* The error format string in ASCII. */ + /** The error format string in ASCII. */ const char* format; - /* The number of arguments to expand in the formatted error message. */ + /** The number of arguments to expand in the formatted error message. */ uint16_t argCount; - /* One of the JSExnType constants above. */ + /** One of the JSExnType constants above. */ int16_t exnType; } JSErrorFormatString; @@ -645,7 +646,7 @@ typedef bool typedef bool (* JSLocaleToUnicode)(JSContext* cx, const char* src, JS::MutableHandleValue rval); -/* +/** * Callback used to ask the embedding for the cross compartment wrapper handler * that implements the desired prolicy for this kind of object in the * destination compartment. |obj| is the object to be wrapped. If |existing| is @@ -657,7 +658,7 @@ typedef bool typedef JSObject* (* JSWrapObjectCallback)(JSContext* cx, JS::HandleObject existing, JS::HandleObject obj); -/* +/** * Callback used by the wrap hook to ask the embedding to prepare an object * for wrapping in a context. This might include unwrapping other wrappers * or even finding a more suitable object for the new compartment. @@ -682,18 +683,6 @@ typedef void (* JSCompartmentNameCallback)(JSRuntime* rt, JSCompartment* compartment, char* buf, size_t bufsize); -/** - * Callback used to ask the embedding to determine in which - * Performance Group the current execution belongs. Typically, this is - * used to regroup JSCompartments from several iframes from the same - * page or from several compartments of the same addon into a single - * Performance Group. - * - * Returns an opaque key. - */ -typedef void* -(* JSCurrentPerfGroupCallback)(JSContext*); - /************************************************************************/ static MOZ_ALWAYS_INLINE JS::Value @@ -713,26 +702,27 @@ JS_StringHasBeenPinned(JSContext* cx, JSString* str); namespace JS { -// Container class for passing in script source buffers to the JS engine. This -// not only groups the buffer and length values, it also provides a way to -// optionally pass ownership of the buffer to the JS engine without copying. -// Rules for use: -// -// 1) The data array must be allocated with js_malloc() or js_realloc() if -// ownership is being granted to the SourceBufferHolder. -// 2) If ownership is not given to the SourceBufferHolder, then the memory -// must be kept alive until the JS compilation is complete. -// 3) Any code calling SourceBufferHolder::take() must guarantee to keep the -// memory alive until JS compilation completes. Normally only the JS -// engine should be calling take(). -// -// Example use: -// -// size_t length = 512; -// char16_t* chars = static_cast(js_malloc(sizeof(char16_t) * length)); -// JS::SourceBufferHolder srcBuf(chars, length, JS::SourceBufferHolder::GiveOwnership); -// JS::Compile(cx, options, srcBuf); -// +/** + * Container class for passing in script source buffers to the JS engine. This + * not only groups the buffer and length values, it also provides a way to + * optionally pass ownership of the buffer to the JS engine without copying. + * Rules for use: + * + * 1) The data array must be allocated with js_malloc() or js_realloc() if + * ownership is being granted to the SourceBufferHolder. + * 2) If ownership is not given to the SourceBufferHolder, then the memory + * must be kept alive until the JS compilation is complete. + * 3) Any code calling SourceBufferHolder::take() must guarantee to keep the + * memory alive until JS compilation completes. Normally only the JS + * engine should be calling take(). + * + * Example use: + * + * size_t length = 512; + * char16_t* chars = static_cast(js_malloc(sizeof(char16_t) * length)); + * JS::SourceBufferHolder srcBuf(chars, length, JS::SourceBufferHolder::GiveOwnership); + * JS::Compile(cx, options, srcBuf); + */ class MOZ_STACK_CLASS SourceBufferHolder final { public: @@ -883,7 +873,7 @@ class MOZ_STACK_CLASS SourceBufferHolder final specified when passed to Object.defineProperty from script. */ -/* +/** * The first call to JS_CallOnce by any thread in a process will call 'func'. * Later calls to JS_CallOnce with the same JSCallOnceType object will be * suppressed. @@ -894,11 +884,11 @@ class MOZ_STACK_CLASS SourceBufferHolder final extern JS_PUBLIC_API(bool) JS_CallOnce(JSCallOnceType* once, JSInitCallback func); -/* Microseconds since the epoch, midnight, January 1, 1970 UTC. */ +/** Microseconds since the epoch, midnight, January 1, 1970 UTC. */ extern JS_PUBLIC_API(int64_t) JS_Now(void); -/* Don't want to export data, so provide accessors for non-inline Values. */ +/** Don't want to export data, so provide accessors for non-inline Values. */ extern JS_PUBLIC_API(JS::Value) JS_GetNaNValue(JSContext* cx); @@ -941,11 +931,11 @@ JS_LooselyEqual(JSContext* cx, JS::Handle v1, JS::Handle v extern JS_PUBLIC_API(bool) JS_SameValue(JSContext* cx, JS::Handle v1, JS::Handle v2, bool* same); -/* True iff fun is the global eval function. */ +/** True iff fun is the global eval function. */ extern JS_PUBLIC_API(bool) JS_IsBuiltinEvalFunction(JSFunction* fun); -/* True iff fun is the Function constructor. */ +/** True iff fun is the Function constructor. */ extern JS_PUBLIC_API(bool) JS_IsBuiltinFunctionConstructor(JSFunction* fun); @@ -1010,14 +1000,16 @@ typedef void* (*JS_ICUAllocFn)(const void*, size_t size); typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size); typedef void (*JS_ICUFreeFn)(const void*, void* p); -// This function can be used to track memory used by ICU. -// Do not use it unless you know what you are doing! +/** + * This function can be used to track memory used by ICU. + * Do not use it unless you know what you are doing! + */ extern JS_PUBLIC_API(bool) JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn); typedef double (*JS_CurrentEmbedderTimeFunction)(); -/* +/** * The embedding can specify a time function that will be used in some * situations. The function can return the time however it likes; but * the norm is to return times in units of milliseconds since an @@ -1027,7 +1019,7 @@ typedef double (*JS_CurrentEmbedderTimeFunction)(); JS_PUBLIC_API(void) JS_SetCurrentEmbedderTimeFunction(JS_CurrentEmbedderTimeFunction timeFn); -/* +/** * Return the time as computed using the current time function, or a * suitable default if one has not been set. */ @@ -1120,12 +1112,14 @@ JS_ContextIterator(JSRuntime* rt, JSContext** iterp); extern JS_PUBLIC_API(JSVersion) JS_GetVersion(JSContext* cx); -// Mutate the version on the compartment. This is generally discouraged, but -// necessary to support the version mutation in the js and xpc shell command -// set. -// -// It would be nice to put this in jsfriendapi, but the linkage requirements -// of the shells make that impossible. +/** + * Mutate the version on the compartment. This is generally discouraged, but + * necessary to support the version mutation in the js and xpc shell command + * set. + * + * It would be nice to put this in jsfriendapi, but the linkage requirements + * of the shells make that impossible. + */ JS_PUBLIC_API(void) JS_SetVersionForCompartment(JSCompartment* compartment, JSVersion version); @@ -1447,7 +1441,7 @@ class MOZ_RAII JS_PUBLIC_API(JSAutoNullableCompartment) MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -/* NB: This API is infallible; a nullptr return value does not indicate error. */ +/** NB: This API is infallible; a nullptr return value does not indicate error. */ extern JS_PUBLIC_API(JSCompartment*) JS_EnterCompartment(JSContext* cx, JSObject* target); @@ -1456,7 +1450,7 @@ JS_LeaveCompartment(JSContext* cx, JSCompartment* oldCompartment); typedef void (*JSIterateCompartmentCallback)(JSRuntime* rt, void* data, JSCompartment* compartment); -/* +/** * This function calls |compartmentCallback| on every compartment. Beware that * there is no guarantee that the compartment will survive after the callback * returns. Also, barriers are disabled via the TraceSession. @@ -1465,7 +1459,7 @@ extern JS_PUBLIC_API(void) JS_IterateCompartments(JSRuntime* rt, void* data, JSIterateCompartmentCallback compartmentCallback); -/* +/** * Initialize standard JS class constructors, prototypes, and any top-level * functions and constants associated with the standard classes (e.g. isNaN * for Number). @@ -1475,7 +1469,7 @@ JS_IterateCompartments(JSRuntime* rt, void* data, extern JS_PUBLIC_API(bool) JS_InitStandardClasses(JSContext* cx, JS::Handle obj); -/* +/** * Resolve id, which must contain either a string or an int, to a standard * class name in obj if possible, defining the class's constructor and/or * prototype and storing true in *resolved. If id does not name a standard @@ -1530,28 +1524,28 @@ ProtoKeyToId(JSContext* cx, JSProtoKey key, JS::MutableHandleId idp); extern JS_PUBLIC_API(JSProtoKey) JS_IdToProtoKey(JSContext* cx, JS::HandleId id); -/* +/** * Returns the original value of |Function.prototype| from the global object in * which |forObj| was created. */ extern JS_PUBLIC_API(JSObject*) JS_GetFunctionPrototype(JSContext* cx, JS::HandleObject forObj); -/* +/** * Returns the original value of |Object.prototype| from the global object in * which |forObj| was created. */ extern JS_PUBLIC_API(JSObject*) JS_GetObjectPrototype(JSContext* cx, JS::HandleObject forObj); -/* +/** * Returns the original value of |Array.prototype| from the global object in * which |forObj| was created. */ extern JS_PUBLIC_API(JSObject*) JS_GetArrayPrototype(JSContext* cx, JS::HandleObject forObj); -/* +/** * Returns the original value of |Error.prototype| from the global * object of the current compartment of cx. */ @@ -1573,7 +1567,7 @@ JS_HasExtensibleLexicalScope(JSObject* obj); extern JS_PUBLIC_API(JSObject*) JS_ExtensibleLexicalScope(JSObject* obj); -/* +/** * May return nullptr, if |c| never had a global (e.g. the atoms compartment), * or if |c|'s global has been collected. */ @@ -1587,14 +1581,14 @@ CurrentGlobalOrNull(JSContext* cx); } // namespace JS -/* +/** * Add 'Reflect.parse', a SpiderMonkey extension, to the Reflect object on the * given global. */ extern JS_PUBLIC_API(bool) JS_InitReflectParse(JSContext* cx, JS::HandleObject global); -/* +/** * Add various profiling-related functions as properties of the given object. * Defined in builtin/Profilers.cpp. */ @@ -1606,14 +1600,14 @@ extern JS_PUBLIC_API(bool) JS_DefineDebuggerObject(JSContext* cx, JS::HandleObject obj); #ifdef JS_HAS_CTYPES -/* +/** * Initialize the 'ctypes' object on a global variable 'obj'. The 'ctypes' * object will be sealed. */ extern JS_PUBLIC_API(bool) JS_InitCTypesClass(JSContext* cx, JS::HandleObject global); -/* +/** * Convert a unicode string 'source' of length 'slen' to the platform native * charset, returning a null-terminated string allocated with JS_malloc. On * failure, this function should report an error. @@ -1621,7 +1615,7 @@ JS_InitCTypesClass(JSContext* cx, JS::HandleObject global); typedef char* (* JSCTypesUnicodeToNativeFun)(JSContext* cx, const char16_t* source, size_t slen); -/* +/** * Set of function pointers that ctypes can use for various internal functions. * See JS_SetCTypesCallbacks below. Providing nullptr for a function is safe, * and will result in the applicable ctypes functionality not being available. @@ -1632,7 +1626,7 @@ struct JSCTypesCallbacks { typedef struct JSCTypesCallbacks JSCTypesCallbacks; -/* +/** * Set the callbacks on the provided 'ctypesObj' object. 'callbacks' should be a * pointer to static data that exists for the lifetime of 'ctypesObj', but it * may safely be altered after calling this function and without having @@ -1648,7 +1642,7 @@ JS_malloc(JSContext* cx, size_t nbytes); extern JS_PUBLIC_API(void*) JS_realloc(JSContext* cx, void* p, size_t oldBytes, size_t newBytes); -/* +/** * A wrapper for js_free(p) that may delay js_free(p) invocation as a * performance optimization. * cx may be nullptr. @@ -1656,7 +1650,7 @@ JS_realloc(JSContext* cx, void* p, size_t oldBytes, size_t newBytes); extern JS_PUBLIC_API(void) JS_free(JSContext* cx, void* p); -/* +/** * A wrapper for js_free(p) that may delay js_free(p) invocation as a * performance optimization as specified by the given JSFreeOp instance. */ @@ -1672,11 +1666,11 @@ JS_updateMallocCounter(JSContext* cx, size_t nbytes); extern JS_PUBLIC_API(char*) JS_strdup(JSContext* cx, const char* s); -/* Duplicate a string. Does not report an error on failure. */ +/** Duplicate a string. Does not report an error on failure. */ extern JS_PUBLIC_API(char*) JS_strdup(JSRuntime* rt, const char* s); -/* +/** * Register externally maintained GC roots. * * traceOp: the trace operation. For each root the implementation should call @@ -1686,7 +1680,7 @@ JS_strdup(JSRuntime* rt, const char* s); extern JS_PUBLIC_API(bool) JS_AddExtraGCRootsTracer(JSRuntime* rt, JSTraceDataOp traceOp, void* data); -/* Undo a call to JS_AddExtraGCRootsTracer. */ +/** Undo a call to JS_AddExtraGCRootsTracer. */ extern JS_PUBLIC_API(void) JS_RemoveExtraGCRootsTracer(JSRuntime* rt, JSTraceDataOp traceOp, void* data); @@ -1746,86 +1740,86 @@ extern JS_PUBLIC_API(void) JS_UpdateWeakPointerAfterGCUnbarriered(JSObject** objp); typedef enum JSGCParamKey { - /* Maximum nominal heap before last ditch GC. */ + /** Maximum nominal heap before last ditch GC. */ JSGC_MAX_BYTES = 0, - /* Number of JS_malloc bytes before last ditch GC. */ + /** Number of JS_malloc bytes before last ditch GC. */ JSGC_MAX_MALLOC_BYTES = 1, - /* Amount of bytes allocated by the GC. */ + /** Amount of bytes allocated by the GC. */ JSGC_BYTES = 3, - /* Number of times GC has been invoked. Includes both major and minor GC. */ + /** Number of times GC has been invoked. Includes both major and minor GC. */ JSGC_NUMBER = 4, - /* Max size of the code cache in bytes. */ + /** Max size of the code cache in bytes. */ JSGC_MAX_CODE_CACHE_BYTES = 5, - /* Select GC mode. */ + /** Select GC mode. */ JSGC_MODE = 6, - /* Number of cached empty GC chunks. */ + /** Number of cached empty GC chunks. */ JSGC_UNUSED_CHUNKS = 7, - /* Total number of allocated GC chunks. */ + /** Total number of allocated GC chunks. */ JSGC_TOTAL_CHUNKS = 8, - /* Max milliseconds to spend in an incremental GC slice. */ + /** Max milliseconds to spend in an incremental GC slice. */ JSGC_SLICE_TIME_BUDGET = 9, - /* Maximum size the GC mark stack can grow to. */ + /** Maximum size the GC mark stack can grow to. */ JSGC_MARK_STACK_LIMIT = 10, - /* + /** * GCs less than this far apart in time will be considered 'high-frequency GCs'. * See setGCLastBytes in jsgc.cpp. */ JSGC_HIGH_FREQUENCY_TIME_LIMIT = 11, - /* Start of dynamic heap growth. */ + /** Start of dynamic heap growth. */ JSGC_HIGH_FREQUENCY_LOW_LIMIT = 12, - /* End of dynamic heap growth. */ + /** End of dynamic heap growth. */ JSGC_HIGH_FREQUENCY_HIGH_LIMIT = 13, - /* Upper bound of heap growth. */ + /** Upper bound of heap growth. */ JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MAX = 14, - /* Lower bound of heap growth. */ + /** Lower bound of heap growth. */ JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN = 15, - /* Heap growth for low frequency GCs. */ + /** Heap growth for low frequency GCs. */ JSGC_LOW_FREQUENCY_HEAP_GROWTH = 16, - /* + /** * If false, the heap growth factor is fixed at 3. If true, it is determined * based on whether GCs are high- or low- frequency. */ JSGC_DYNAMIC_HEAP_GROWTH = 17, - /* If true, high-frequency GCs will use a longer mark slice. */ + /** If true, high-frequency GCs will use a longer mark slice. */ JSGC_DYNAMIC_MARK_SLICE = 18, - /* Lower limit after which we limit the heap growth. */ + /** Lower limit after which we limit the heap growth. */ JSGC_ALLOCATION_THRESHOLD = 19, - /* + /** * We decommit memory lazily. If more than this number of megabytes is * available to be decommitted, then JS_MaybeGC will trigger a shrinking GC * to decommit it. */ JSGC_DECOMMIT_THRESHOLD = 20, - /* + /** * We try to keep at least this many unused chunks in the free chunk pool at * all times, even after a shrinking GC. */ JSGC_MIN_EMPTY_CHUNK_COUNT = 21, - /* We never keep more than this many unused chunks in the free chunk pool. */ + /** We never keep more than this many unused chunks in the free chunk pool. */ JSGC_MAX_EMPTY_CHUNK_COUNT = 22, - /* Whether compacting GC is enabled. */ + /** Whether compacting GC is enabled. */ JSGC_COMPACTING_ENABLED = 23 } JSGCParamKey; @@ -1847,7 +1841,7 @@ JS_GetGCParameterForThread(JSContext* cx, JSGCParamKey key); extern JS_PUBLIC_API(void) JS_SetGCParametersBasedOnAvailableMemory(JSRuntime* rt, uint32_t availMem); -/* +/** * Create a new JSString whose chars member refers to external memory, i.e., * memory requiring application-specific finalization. */ @@ -1855,20 +1849,20 @@ extern JS_PUBLIC_API(JSString*) JS_NewExternalString(JSContext* cx, const char16_t* chars, size_t length, const JSStringFinalizer* fin); -/* +/** * Return whether 'str' was created with JS_NewExternalString or * JS_NewExternalStringWithClosure. */ extern JS_PUBLIC_API(bool) JS_IsExternalString(JSString* str); -/* +/** * Return the 'fin' arg passed to JS_NewExternalString. */ extern JS_PUBLIC_API(const JSStringFinalizer*) JS_GetExternalStringFinalizer(JSString* str); -/* +/** * Set the size of the native stack that should not be exceed. To disable * stack size checking pass 0. * @@ -1947,7 +1941,7 @@ typedef JSConstScalarSpec JSConstIntegerSpec; struct JSJitInfo; -/* +/** * Wrapper to relace JSNative for JSPropertySpecs and JSFunctionSpecs. This will * allow us to pass one JSJitInfo per function with the property/function spec, * without additional field overhead. @@ -1963,7 +1957,7 @@ typedef struct JSNativeWrapper { */ #define JSNATIVE_WRAPPER(native) { {native, nullptr} } -/* +/** * Description of a property. JS_DefineProperties and JS_InitClass take arrays * of these and define many properties at once. JS_PSG, JS_PSGS and JS_PS_END * are helper macros for defining such arrays. @@ -2095,7 +2089,7 @@ inline int CheckIsSetterOp(JSSetterOp op); { { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) } }, \ JSNATIVE_WRAPPER(nullptr) } -/* +/** * To define a native function, set call to a JSNativeWrapper. To define a * self-hosted function, set selfHostedName to the name of a function * compiled during JSRuntime::initSelfHosting. @@ -2154,7 +2148,7 @@ JS_InitClass(JSContext* cx, JS::HandleObject obj, JS::HandleObject parent_proto, const JSPropertySpec* ps, const JSFunctionSpec* fs, const JSPropertySpec* static_ps, const JSFunctionSpec* static_fs); -/* +/** * Set up ctor.prototype = proto and proto.constructor = ctor with the * right property flags. */ @@ -2363,26 +2357,28 @@ CompartmentOptionsRef(JSObject* obj); JS_PUBLIC_API(CompartmentOptions&) CompartmentOptionsRef(JSContext* cx); -// During global creation, we fire notifications to callbacks registered -// via the Debugger API. These callbacks are arbitrary script, and can touch -// the global in arbitrary ways. When that happens, the global should not be -// in a half-baked state. But this creates a problem for consumers that need -// to set slots on the global to put it in a consistent state. -// -// This API provides a way for consumers to set slots atomically (immediately -// after the global is created), before any debugger hooks are fired. It's -// unfortunately on the clunky side, but that's the way the cookie crumbles. -// -// If callers have no additional state on the global to set up, they may pass -// |FireOnNewGlobalHook| to JS_NewGlobalObject, which causes that function to -// fire the hook as its final act before returning. Otherwise, callers should -// pass |DontFireOnNewGlobalHook|, which means that they are responsible for -// invoking JS_FireOnNewGlobalObject upon successfully creating the global. If -// an error occurs and the operation aborts, callers should skip firing the -// hook. But otherwise, callers must take care to fire the hook exactly once -// before compiling any script in the global's scope (we have assertions in -// place to enforce this). This lets us be sure that debugger clients never miss -// breakpoints. +/** + * During global creation, we fire notifications to callbacks registered + * via the Debugger API. These callbacks are arbitrary script, and can touch + * the global in arbitrary ways. When that happens, the global should not be + * in a half-baked state. But this creates a problem for consumers that need + * to set slots on the global to put it in a consistent state. + * + * This API provides a way for consumers to set slots atomically (immediately + * after the global is created), before any debugger hooks are fired. It's + * unfortunately on the clunky side, but that's the way the cookie crumbles. + * + * If callers have no additional state on the global to set up, they may pass + * |FireOnNewGlobalHook| to JS_NewGlobalObject, which causes that function to + * fire the hook as its final act before returning. Otherwise, callers should + * pass |DontFireOnNewGlobalHook|, which means that they are responsible for + * invoking JS_FireOnNewGlobalObject upon successfully creating the global. If + * an error occurs and the operation aborts, callers should skip firing the + * hook. But otherwise, callers must take care to fire the hook exactly once + * before compiling any script in the global's scope (we have assertions in + * place to enforce this). This lets us be sure that debugger clients never miss + * breakpoints. + */ enum OnNewGlobalHookOption { FireOnNewGlobalHook, DontFireOnNewGlobalHook @@ -2394,7 +2390,7 @@ extern JS_PUBLIC_API(JSObject*) JS_NewGlobalObject(JSContext* cx, const JSClass* clasp, JSPrincipals* principals, JS::OnNewGlobalHookOption hookOption, const JS::CompartmentOptions& options = JS::CompartmentOptions()); -/* +/** * Spidermonkey does not have a good way of keeping track of what compartments should be marked on * their own. We can mark the roots unconditionally, but marking GC things only relevant in live * compartments is hard. To mitigate this, we create a static trace hook, installed on each global @@ -2422,18 +2418,18 @@ JS_IsNative(JSObject* obj); extern JS_PUBLIC_API(JSRuntime*) JS_GetObjectRuntime(JSObject* obj); -/* +/** * Unlike JS_NewObject, JS_NewObjectWithGivenProto does not compute a default * proto. If proto is nullptr, the JS object will have `null` as [[Prototype]]. */ extern JS_PUBLIC_API(JSObject*) JS_NewObjectWithGivenProto(JSContext* cx, const JSClass* clasp, JS::Handle proto); -// Creates a new plain object, like `new Object()`, with Object.prototype as [[Prototype]]. +/** Creates a new plain object, like `new Object()`, with Object.prototype as [[Prototype]]. */ extern JS_PUBLIC_API(JSObject*) JS_NewPlainObject(JSContext* cx); -/* +/** * Freeze obj, and all objects it refers to, recursively. This will not recurse * through non-extensible objects, on the assumption that those are already * deep-frozen. @@ -2441,7 +2437,7 @@ JS_NewPlainObject(JSContext* cx); extern JS_PUBLIC_API(bool) JS_DeepFreezeObject(JSContext* cx, JS::Handle obj); -/* +/** * Freezes an object; see ES5's Object.freeze(obj) method. */ extern JS_PUBLIC_API(bool) @@ -2987,19 +2983,23 @@ JS_NewArrayObject(JSContext* cx, const JS::HandleValueArray& contents); extern JS_PUBLIC_API(JSObject*) JS_NewArrayObject(JSContext* cx, size_t length); -// Returns true and sets |*isArray| indicating whether |value| is an Array -// object or a wrapper around one, otherwise returns false on failure. -// -// This method returns true with |*isArray == false| when passed a proxy whose -// target is an Array, or when passed a revoked proxy. +/** + * Returns true and sets |*isArray| indicating whether |value| is an Array + * object or a wrapper around one, otherwise returns false on failure. + * + * This method returns true with |*isArray == false| when passed a proxy whose + * target is an Array, or when passed a revoked proxy. + */ extern JS_PUBLIC_API(bool) JS_IsArrayObject(JSContext* cx, JS::HandleValue value, bool* isArray); -// Returns true and sets |*isArray| indicating whether |obj| is an Array object -// or a wrapper around one, otherwise returns false on failure. -// -// This method returns true with |*isArray == false| when passed a proxy whose -// target is an Array, or when passed a revoked proxy. +/** + * Returns true and sets |*isArray| indicating whether |obj| is an Array object + * or a wrapper around one, otherwise returns false on failure. + * + * This method returns true with |*isArray == false| when passed a proxy whose + * target is an Array, or when passed a revoked proxy. + */ extern JS_PUBLIC_API(bool) JS_IsArrayObject(JSContext* cx, JS::HandleObject obj, bool* isArray); @@ -3076,14 +3076,14 @@ JS_DeleteElement(JSContext* cx, JS::HandleObject obj, uint32_t index); extern JS_PUBLIC_API(bool) JS_DeleteElement(JSContext* cx, JS::HandleObject obj, uint32_t index, JS::ObjectOpResult& result); -/* +/** * Assign 'undefined' to all of the object's non-reserved slots. Note: this is * done for all slots, regardless of the associated property descriptor. */ JS_PUBLIC_API(void) JS_SetAllNonReservedSlotsToUndefined(JSContext* cx, JSObject* objArg); -/* +/** * Create a new array buffer with the given contents. It must be legal to pass * these contents to free(). On success, the ownership is transferred to the * new array buffer. @@ -3091,7 +3091,7 @@ JS_SetAllNonReservedSlotsToUndefined(JSContext* cx, JSObject* objArg); extern JS_PUBLIC_API(JSObject*) JS_NewArrayBufferWithContents(JSContext* cx, size_t nbytes, void* contents); -/* +/** * Steal the contents of the given array buffer. The array buffer has its * length set to 0 and its contents array cleared. The caller takes ownership * of the return value and must free it or transfer ownership via @@ -3100,7 +3100,7 @@ JS_NewArrayBufferWithContents(JSContext* cx, size_t nbytes, void* contents); extern JS_PUBLIC_API(void*) JS_StealArrayBufferContents(JSContext* cx, JS::HandleObject obj); -/* +/** * Create a new mapped array buffer with the given memory mapped contents. It * must be legal to free the contents pointer by unmapping it. On success, * ownership is transferred to the new mapped array buffer. @@ -3108,14 +3108,14 @@ JS_StealArrayBufferContents(JSContext* cx, JS::HandleObject obj); extern JS_PUBLIC_API(JSObject*) JS_NewMappedArrayBufferWithContents(JSContext* cx, size_t nbytes, void* contents); -/* +/** * Create memory mapped array buffer contents. * Caller must take care of closing fd after calling this function. */ extern JS_PUBLIC_API(void*) JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length); -/* +/** * Release the allocated resource of mapped array buffer contents before the * object is created. * If a new object has been created by JS_NewMappedArrayBufferWithContents() @@ -3165,7 +3165,7 @@ NewFunctionFromSpec(JSContext* cx, const JSFunctionSpec* fs, HandleId id); extern JS_PUBLIC_API(JSObject*) JS_GetFunctionObject(JSFunction* fun); -/* +/** * Return the function's identifier as a JSString, or null if fun is unnamed. * The returned string lives as long as fun, so you don't need to root a saved * reference to it if fun is well-connected or rooted, and provided you bound @@ -3174,7 +3174,7 @@ JS_GetFunctionObject(JSFunction* fun); extern JS_PUBLIC_API(JSString*) JS_GetFunctionId(JSFunction* fun); -/* +/** * Return a function's display name. This is the defined name if one was given * where the function was defined, or it could be an inferred name by the JS * engine in the case that the function was defined to be anonymous. This can @@ -3204,7 +3204,7 @@ IsConstructor(JSObject* obj); } /* namespace JS */ -/* +/** * Infallible predicate to test whether obj is a function object (faster than * comparing obj's class name to "Function", but equivalent unless someone has * overwritten the "Function" identifier with a different constructor and then @@ -3216,13 +3216,15 @@ JS_ObjectIsFunction(JSContext* cx, JSObject* obj); extern JS_PUBLIC_API(bool) JS_IsNativeFunction(JSObject* funobj, JSNative call); -/* Return whether the given function is a valid constructor. */ +/** Return whether the given function is a valid constructor. */ extern JS_PUBLIC_API(bool) JS_IsConstructor(JSFunction* fun); -// This enum is used to select if properties with JSPROP_DEFINE_LATE flag -// should be defined on the object. -// Normal JSAPI consumers probably always want DefineAllProperties here. +/** + * This enum is used to select if properties with JSPROP_DEFINE_LATE flag + * should be defined on the object. + * Normal JSAPI consumers probably always want DefineAllProperties here. + */ enum PropertyDefinitionBehavior { DefineAllProperties, OnlyDefineLateProperties, @@ -3248,14 +3250,14 @@ JS_DefineFunctionById(JSContext* cx, JS::Handle obj, JS::Handle namespace JS { -/* +/** * Clone a top-level function into cx's global. This function will dynamically * fail if funobj was lexically nested inside some other function. */ extern JS_PUBLIC_API(JSObject*) CloneFunctionObject(JSContext* cx, HandleObject funobj); -/* +/** * As above, but providing an explicit scope chain. scopeChain must not include * the global object on it; that's implicit. It needs to contain the other * objects that should end up on the clone's scope chain. @@ -3265,7 +3267,7 @@ CloneFunctionObject(JSContext* cx, HandleObject funobj, AutoObjectVector& scopeC } // namespace JS -/* +/** * Given a buffer, return false if the buffer might become a valid * javascript statement with the addition of more lines. Otherwise return * true. The intent is to support interactive compilation - accumulate @@ -3276,7 +3278,7 @@ extern JS_PUBLIC_API(bool) JS_BufferIsCompilableUnit(JSContext* cx, JS::Handle obj, const char* utf8, size_t length); -/* +/** * |script| will always be set. On failure, it will be set to nullptr. */ extern JS_PUBLIC_API(bool) @@ -3284,7 +3286,7 @@ JS_CompileScript(JSContext* cx, const char* ascii, size_t length, const JS::CompileOptions& options, JS::MutableHandleScript script); -/* +/** * |script| will always be set. On failure, it will be set to nullptr. */ extern JS_PUBLIC_API(bool) @@ -3352,7 +3354,7 @@ namespace JS { * derived from ReadOnlyCompileOptions, so the compiler accepts it. */ -/* +/** * The common base class for the CompileOptions hierarchy. * * Use this in code that needs to propagate compile options from one compilation @@ -3446,7 +3448,7 @@ class JS_FRIEND_API(TransitiveCompileOptions) void operator=(const TransitiveCompileOptions&) = delete; }; -/* +/** * The class representing a full set of compile options. * * Use this in code that only needs to access compilation options created @@ -3495,7 +3497,7 @@ class JS_FRIEND_API(ReadOnlyCompileOptions) : public TransitiveCompileOptions void operator=(const ReadOnlyCompileOptions&) = delete; }; -/* +/** * Compilation options, with dynamic lifetime. An instance of this type * makes a copy of / holds / roots all dynamically allocated resources * (principals; elements; strings) that it refers to. Its destructor frees @@ -3585,7 +3587,7 @@ class JS_FRIEND_API(OwningCompileOptions) : public ReadOnlyCompileOptions void operator=(const CompileOptions& rhs) = delete; }; -/* +/** * Compilation options stored on the stack. An instance of this type * simply holds references to dynamically allocated resources (element; * filename; source map URL) that are owned by something else. If you @@ -3685,7 +3687,7 @@ class MOZ_STACK_CLASS JS_FRIEND_API(CompileOptions) : public ReadOnlyCompileOpti void operator=(const CompileOptions& rhs) = delete; }; -/* +/** * |script| will always be set. On failure, it will be set to nullptr. */ extern JS_PUBLIC_API(bool) @@ -3793,15 +3795,13 @@ JS_DecompileScript(JSContext* cx, JS::Handle script, const char* name /* * API extension: OR this into indent to avoid pretty-printing the decompiled - * source resulting from JS_DecompileFunction{,Body}. + * source resulting from JS_DecompileFunction. */ #define JS_DONT_PRETTY_PRINT ((unsigned)0x8000) extern JS_PUBLIC_API(JSString*) JS_DecompileFunction(JSContext* cx, JS::Handle fun, unsigned indent); -extern JS_PUBLIC_API(JSString*) -JS_DecompileFunctionBody(JSContext* cx, JS::Handle fun, unsigned indent); /* * NB: JS_ExecuteScript and the JS::Evaluate APIs come in two flavors: either @@ -3820,7 +3820,7 @@ JS_DecompileFunctionBody(JSContext* cx, JS::Handle fun, unsigned in * code can continue to use the familiar JS::Evaluate, etc., entry points. */ -/* +/** * Evaluate a script in the scope of the current global of cx. */ extern JS_PUBLIC_API(bool) @@ -3829,7 +3829,7 @@ JS_ExecuteScript(JSContext* cx, JS::HandleScript script, JS::MutableHandleValue extern JS_PUBLIC_API(bool) JS_ExecuteScript(JSContext* cx, JS::HandleScript script); -/* +/** * As above, but providing an explicit scope chain. scopeChain must not include * the global object on it; that's implicit. It needs to contain the other * objects that should end up on the script's scope chain. @@ -3843,7 +3843,7 @@ JS_ExecuteScript(JSContext* cx, JS::AutoObjectVector& scopeChain, JS::HandleScri namespace JS { -/* +/** * Like the above, but handles a cross-compartment script. If the script is * cross-compartment, it is cloned into the current compartment before executing. */ @@ -3854,14 +3854,14 @@ CloneAndExecuteScript(JSContext* cx, JS::Handle script); namespace JS { -/* +/** * Evaluate the given source buffer in the scope of the current global of cx. */ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, JS::MutableHandleValue rval); -/* +/** * As above, but providing an explicit scope chain. scopeChain must not include * the global object on it; that's implicit. It needs to contain the other * objects that should end up on the script's scope chain. @@ -3870,14 +3870,14 @@ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, AutoObjectVector& scopeChain, const ReadOnlyCompileOptions& options, SourceBufferHolder& srcBuf, JS::MutableHandleValue rval); -/* +/** * Evaluate the given character buffer in the scope of the current global of cx. */ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options, const char16_t* chars, size_t length, JS::MutableHandleValue rval); -/* +/** * As above, but providing an explicit scope chain. scopeChain must not include * the global object on it; that's implicit. It needs to contain the other * objects that should end up on the script's scope chain. @@ -3886,14 +3886,14 @@ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, AutoObjectVector& scopeChain, const ReadOnlyCompileOptions& options, const char16_t* chars, size_t length, JS::MutableHandleValue rval); -/* +/** * Evaluate the given byte buffer in the scope of the current global of cx. */ extern JS_PUBLIC_API(bool) Evaluate(JSContext* cx, const ReadOnlyCompileOptions& options, const char* bytes, size_t length, JS::MutableHandleValue rval); -/* +/** * Evaluate the given file in the scope of the current global of cx. */ extern JS_PUBLIC_API(bool) @@ -4009,7 +4009,7 @@ JS_RestoreFrameChain(JSContext* cx); namespace JS { -/* +/** * This class can be used to store a pointer to the youngest frame of a saved * stack in the specified JSContext. This reference will be picked up by any new * calls performed until the class is destroyed, with the specified asyncCause, @@ -4153,7 +4153,7 @@ JS_GetStringLength(JSString* str); extern JS_PUBLIC_API(bool) JS_StringIsFlat(JSString* str); -/* Returns true iff the string's characters are stored as Latin1. */ +/** Returns true iff the string's characters are stored as Latin1. */ extern JS_PUBLIC_API(bool) JS_StringHasLatin1Chars(JSString* str); @@ -4216,7 +4216,7 @@ JS_FlatStringEqualsAscii(JSFlatString* str, const char* asciiBytes); extern JS_PUBLIC_API(size_t) JS_PutEscapedFlatString(char* buffer, size_t size, JSFlatString* str, char quote); -/* +/** * Create a dependent string, i.e., a string that owns no character storage, * but that refers to a slice of another string's chars. Dependent strings * are mutable by definition, so the thread safety comments above apply. @@ -4225,14 +4225,14 @@ extern JS_PUBLIC_API(JSString*) JS_NewDependentString(JSContext* cx, JS::HandleString str, size_t start, size_t length); -/* +/** * Concatenate two strings, possibly resulting in a rope. * See above for thread safety comments. */ extern JS_PUBLIC_API(JSString*) JS_ConcatStrings(JSContext* cx, JS::HandleString left, JS::HandleString right); -/* +/** * For JS_DecodeBytes, set *dstlenp to the size of the destination buffer before * the call; on return, *dstlenp contains the number of characters actually * stored. To determine the necessary destination buffer size, make a sizing @@ -4249,20 +4249,20 @@ JS_PUBLIC_API(bool) JS_DecodeBytes(JSContext* cx, const char* src, size_t srclen, char16_t* dst, size_t* dstlenp); -/* +/** * A variation on JS_EncodeCharacters where a null terminated string is * returned that you are expected to call JS_free on when done. */ JS_PUBLIC_API(char*) JS_EncodeString(JSContext* cx, JSString* str); -/* +/** * Same behavior as JS_EncodeString(), but encode into UTF-8 string */ JS_PUBLIC_API(char*) JS_EncodeStringToUTF8(JSContext* cx, JS::HandleString str); -/* +/** * Get number of bytes in the string encoding (without accounting for a * terminating zero bytes. The function returns (size_t) -1 if the string * can not be encoded into bytes and reports an error using cx accordingly. @@ -4270,7 +4270,7 @@ JS_EncodeStringToUTF8(JSContext* cx, JS::HandleString str); JS_PUBLIC_API(size_t) JS_GetStringEncodingLength(JSContext* cx, JSString* str); -/* +/** * Encode string into a buffer. The function does not stores an additional * zero byte. The function returns (size_t) -1 if the string can not be * encoded into bytes with no error reported. Otherwise it returns the number @@ -4372,7 +4372,7 @@ AddonIdOfObject(JSObject* obj); namespace JS { -/* +/** * Create a new Symbol with the given description. This function never returns * a Symbol that is in the Runtime-wide symbol registry. * @@ -4382,7 +4382,7 @@ namespace JS { JS_PUBLIC_API(Symbol*) NewSymbol(JSContext* cx, HandleString description); -/* +/** * Symbol.for as specified in ES6. * * Get a Symbol with the description 'key' from the Runtime-wide symbol registry. @@ -4392,7 +4392,7 @@ NewSymbol(JSContext* cx, HandleString description); JS_PUBLIC_API(Symbol*) GetSymbolFor(JSContext* cx, HandleString key); -/* +/** * Get the [[Description]] attribute of the given symbol. * * This function is infallible. If it returns null, that means the symbol's @@ -4423,7 +4423,7 @@ enum class SymbolCode : uint32_t { /* For use in loops that iterate over the well-known symbols. */ const size_t WellKnownSymbolLimit = size_t(SymbolCode::Limit); -/* +/** * Return the SymbolCode telling what sort of symbol `symbol` is. * * A symbol's SymbolCode never changes once it is created. @@ -4431,7 +4431,7 @@ const size_t WellKnownSymbolLimit = size_t(SymbolCode::Limit); JS_PUBLIC_API(SymbolCode) GetSymbolCode(Handle symbol); -/* +/** * Get one of the well-known symbols defined by ES6. A single set of well-known * symbols is shared by all compartments in a JSRuntime. * @@ -4440,7 +4440,7 @@ GetSymbolCode(Handle symbol); JS_PUBLIC_API(Symbol*) GetWellKnownSymbol(JSContext* cx, SymbolCode which); -/* +/** * Return true if the given JSPropertySpec::name or JSFunctionSpec::name value * is actually a symbol code and not a string. See JS_SYM_FN. */ @@ -4454,7 +4454,7 @@ PropertySpecNameIsSymbol(const char* name) JS_PUBLIC_API(bool) PropertySpecNameEqualsId(const char* name, HandleId id); -/* +/** * Create a jsid that does not need to be marked for GC. * * 'name' is a JSPropertySpec::name or JSFunctionSpec::name value. The @@ -4473,14 +4473,14 @@ PropertySpecNameToPermanentId(JSContext* cx, const char* name, jsid* idp); */ typedef bool (* JSONWriteCallback)(const char16_t* buf, uint32_t len, void* data); -/* +/** * JSON.stringify as specified by ES5. */ JS_PUBLIC_API(bool) JS_Stringify(JSContext* cx, JS::MutableHandleValue value, JS::HandleObject replacer, JS::HandleValue space, JSONWriteCallback callback, void* data); -/* +/** * JSON.parse as specified by ES5. */ JS_PUBLIC_API(bool) @@ -4499,7 +4499,7 @@ JS_ParseJSONWithReviver(JSContext* cx, JS::HandleString str, JS::HandleValue rev /************************************************************************/ -/* +/** * The default locale for the ECMAScript Internationalization API * (Intl.Collator, Intl.NumberFormat, Intl.DateTimeFormat). * Note that the Internationalization API encourages clients to @@ -4509,13 +4509,13 @@ JS_ParseJSONWithReviver(JSContext* cx, JS::HandleString str, JS::HandleValue rev extern JS_PUBLIC_API(bool) JS_SetDefaultLocale(JSRuntime* rt, const char* locale); -/* +/** * Reset the default locale to OS defaults. */ extern JS_PUBLIC_API(void) JS_ResetDefaultLocale(JSRuntime* rt); -/* +/** * Locale specific string conversion and error message callbacks. */ struct JSLocaleCallbacks { @@ -4525,14 +4525,14 @@ struct JSLocaleCallbacks { JSLocaleToUnicode localeToUnicode; }; -/* +/** * Establish locale callbacks. The pointer must persist as long as the * JSRuntime. Passing nullptr restores the default behaviour. */ extern JS_PUBLIC_API(void) JS_SetLocaleCallbacks(JSRuntime* rt, const JSLocaleCallbacks* callbacks); -/* +/** * Return the address of the current locale callbacks struct, which may * be nullptr. */ @@ -4549,7 +4549,7 @@ namespace JS { const uint16_t MaxNumErrorArguments = 10; }; -/* +/** * Report an exception represented by the sprintf-like conversion of format * and its arguments. This exception message string is passed to a pre-set * JSErrorReporter function (set by JS_SetErrorReporter). @@ -4582,7 +4582,7 @@ JS_ReportErrorNumberUCArray(JSContext* cx, JSErrorCallback errorCallback, void* userRef, const unsigned errorNumber, const char16_t** args); -/* +/** * As above, but report a warning instead (JSREPORT_IS_WARNING(report.flags)). * Return true if there was no error trying to issue the warning, and if the * warning was not converted into an error due to the JSOPTION_WERROR option @@ -4601,13 +4601,13 @@ JS_ReportErrorFlagsAndNumberUC(JSContext* cx, unsigned flags, JSErrorCallback errorCallback, void* userRef, const unsigned errorNumber, ...); -/* +/** * Complain when out of memory. */ extern JS_PUBLIC_API(void) JS_ReportOutOfMemory(JSContext* cx); -/* +/** * Complain when an allocation size overflows the maximum supported limit. */ extern JS_PUBLIC_API(void) @@ -4778,15 +4778,17 @@ SetForEach(JSContext *cx, HandleObject obj, HandleValue callbackFn, HandleValue extern JS_PUBLIC_API(JSObject*) JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, int sec); -// Returns true and sets |*isDate| indicating whether |obj| is a Date object or -// a wrapper around one, otherwise returns false on failure. -// -// This method returns true with |*isDate == false| when passed a proxy whose -// target is a Date, or when passed a revoked proxy. +/** + * Returns true and sets |*isDate| indicating whether |obj| is a Date object or + * a wrapper around one, otherwise returns false on failure. + * + * This method returns true with |*isDate == false| when passed a proxy whose + * target is a Date, or when passed a revoked proxy. + */ extern JS_PUBLIC_API(bool) JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj, bool* isDate); -/* +/** * Clears the cache of calculated local time from each Date object. * Call to propagate a system timezone change. */ @@ -4835,11 +4837,13 @@ extern JS_PUBLIC_API(bool) JS_ExecuteRegExpNoStatics(JSContext* cx, JS::HandleObject reobj, char16_t* chars, size_t length, size_t* indexp, bool test, JS::MutableHandleValue rval); -// Returns true and sets |*isRegExp| indicating whether |obj| is a RegExp -// object or a wrapper around one, otherwise returns false on failure. -// -// This method returns true with |*isRegExp == false| when passed a proxy whose -// target is a RegExp, or when passed a revoked proxy. +/** + * Returns true and sets |*isRegExp| indicating whether |obj| is a RegExp + * object or a wrapper around one, otherwise returns false on failure. + * + * This method returns true with |*isRegExp == false| when passed a proxy whose + * target is a RegExp, or when passed a revoked proxy. + */ extern JS_PUBLIC_API(bool) JS_ObjectIsRegExp(JSContext* cx, JS::HandleObject obj, bool* isRegExp); @@ -4868,7 +4872,7 @@ JS_ReportPendingException(JSContext* cx); namespace JS { -/* +/** * Save and later restore the current exception state of a given JSContext. * This is useful for implementing behavior in C++ that's like try/catch * or try/finally in JS. @@ -4933,7 +4937,7 @@ JS_RestoreExceptionState(JSContext* cx, JSExceptionState* state); extern JS_PUBLIC_API(void) JS_DropExceptionState(JSContext* cx, JSExceptionState* state); -/* +/** * If the given object is an exception object, the exception will have (or be * able to lazily create) an error report struct, and this function will return * the address of that struct. Otherwise, it returns nullptr. The lifetime @@ -4958,7 +4962,7 @@ JS_IsStopIteration(JS::Value v); extern JS_PUBLIC_API(intptr_t) JS_GetCurrentThread(); -/* +/** * A JS runtime always has an "owner thread". The owner thread is set when the * runtime is created (to the current thread) and practically all entry points * into the JS engine check that a runtime (or anything contained in the @@ -4973,7 +4977,7 @@ JS_AbortIfWrongThread(JSRuntime* rt); /************************************************************************/ -/* +/** * A constructor can request that the JS engine create a default new 'this' * object of the given class, using the callee to determine parentage and * [[Prototype]]. @@ -5027,13 +5031,13 @@ JS_SetGlobalJitCompilerOption(JSRuntime* rt, JSJitCompilerOption opt, uint32_t v extern JS_PUBLIC_API(int) JS_GetGlobalJitCompilerOption(JSRuntime* rt, JSJitCompilerOption opt); -/* +/** * Convert a uint32_t index into a jsid. */ extern JS_PUBLIC_API(bool) JS_IndexToId(JSContext* cx, uint32_t index, JS::MutableHandleId); -/* +/** * Convert chars into a jsid. * * |chars| may not be an index. @@ -5041,13 +5045,13 @@ JS_IndexToId(JSContext* cx, uint32_t index, JS::MutableHandleId); extern JS_PUBLIC_API(bool) JS_CharsToId(JSContext* cx, JS::TwoByteChars chars, JS::MutableHandleId); -/* +/** * Test if the given string is a valid ECMAScript identifier */ extern JS_PUBLIC_API(bool) JS_IsIdentifier(JSContext* cx, JS::HandleString str, bool* isIdentifier); -/* +/** * Test whether the given chars + length are a valid ECMAScript identifier. * This version is infallible, so just returns whether the chars are an * identifier. @@ -5057,7 +5061,7 @@ JS_IsIdentifier(const char16_t* chars, size_t length); namespace JS { -/* +/** * AutoFilename encapsulates a pointer to a C-string and keeps the C-string * alive for as long as the associated AutoFilename object is alive. */ @@ -5077,7 +5081,7 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(AutoFilename) void reset(void* newScriptSource); }; -/* +/** * Return the current filename, line number and column number of the most * currently running frame. Returns true if a scripted frame was found, false * otherwise. @@ -5092,7 +5096,7 @@ DescribeScriptedCaller(JSContext* cx, AutoFilename* filename = nullptr, extern JS_PUBLIC_API(JSObject*) GetScriptedCallerGlobal(JSContext* cx); -/* +/** * Informs the JS engine that the scripted caller should be hidden. This can be * used by the embedding to maintain an override of the scripted caller in its * calculations, by hiding the scripted caller in the JS engine and pushing data @@ -5164,7 +5168,7 @@ typedef bool typedef void (* CloseAsmJSCacheEntryForReadOp)(size_t size, const uint8_t* memory, intptr_t handle); -/* The list of reasons why an asm.js module may not be stored in the cache. */ +/** The list of reasons why an asm.js module may not be stored in the cache. */ enum AsmJSCacheResult { AsmJSCache_MIN, @@ -5204,12 +5208,13 @@ typedef void typedef js::Vector BuildIdCharVector; -// Return the buildId (represented as a sequence of characters) associated with -// the currently-executing build. If the JS engine is embedded such that a -// single cache entry can be observed by different compiled versions of the JS -// engine, it is critical that the buildId shall change for each new build of -// the JS engine. - +/** + * Return the buildId (represented as a sequence of characters) associated with + * the currently-executing build. If the JS engine is embedded such that a + * single cache entry can be observed by different compiled versions of the JS + * engine, it is critical that the buildId shall change for each new build of + * the JS engine. + */ typedef bool (* BuildIdOp)(BuildIdCharVector* buildId); @@ -5225,7 +5230,7 @@ struct AsmJSCacheOps extern JS_PUBLIC_API(void) SetAsmJSCacheOps(JSRuntime* rt, const AsmJSCacheOps* callbacks); -/* +/** * Convenience class for imitating a JS level for-of loop. Typical usage: * * ForOfIterator it(cx); @@ -5275,7 +5280,7 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) { AllowNonIterable }; - /* + /** * Initialize the iterator. If AllowNonIterable is passed then if getting * the @@iterator property from iterable returns undefined init() will just * return true instead of throwing. Callers must then check @@ -5284,13 +5289,13 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) { bool init(JS::HandleValue iterable, NonIterableBehavior nonIterableBehavior = ThrowOnNonIterable); - /* + /** * Get the next value from the iterator. If false *done is true * after this call, do not examine val. */ bool next(JS::MutableHandleValue val, bool* done); - /* + /** * If initialized with throwOnNonCallable = false, check whether * the value is iterable. */ @@ -5304,7 +5309,7 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(ForOfIterator) { }; -/* +/** * If a large allocation fails when calling pod_{calloc,realloc}CanGC, the JS * engine may call the large-allocation- failure callback, if set, to allow the * embedding to flush caches, possibly perform shrinking GCs, etc. to make some @@ -5317,7 +5322,7 @@ typedef void extern JS_PUBLIC_API(void) SetLargeAllocationFailureCallback(JSRuntime* rt, LargeAllocationFailureCallback afc, void* data); -/* +/** * Unlike the error reporter, which is only called if the exception for an OOM * bubbles up and is not caught, the OutOfMemoryCallback is called immediately * at the OOM site to allow the embedding to capture the current state of heap @@ -5335,7 +5340,7 @@ extern JS_PUBLIC_API(void) SetOutOfMemoryCallback(JSRuntime* rt, OutOfMemoryCallback cb, void* data); -/* +/** * Capture the current call stack as a chain of SavedFrame JSObjects, and set * |stackp| to the SavedFrame for the youngest stack frame, or nullptr if there * are no JS frames on the stack. If |maxFrameCount| is non-zero, capture at @@ -5375,26 +5380,26 @@ enum class SavedFrameResult { AccessDenied }; -/* +/** * Given a SavedFrame JSObject, get its source property. Defaults to the empty * string. */ extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameSource(JSContext* cx, HandleObject savedFrame, MutableHandleString sourcep); -/* +/** * Given a SavedFrame JSObject, get its line property. Defaults to 0. */ extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameLine(JSContext* cx, HandleObject savedFrame, uint32_t* linep); -/* +/** * Given a SavedFrame JSObject, get its column property. Defaults to 0. */ extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp); -/* +/** * Given a SavedFrame JSObject, get its functionDisplayName string, or nullptr * if SpiderMonkey was unable to infer a name for the captured frame's * function. Defaults to nullptr. @@ -5402,13 +5407,13 @@ GetSavedFrameColumn(JSContext* cx, HandleObject savedFrame, uint32_t* columnp); extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameFunctionDisplayName(JSContext* cx, HandleObject savedFrame, MutableHandleString namep); -/* +/** * Given a SavedFrame JSObject, get its asyncCause string. Defaults to nullptr. */ extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleString asyncCausep); -/* +/** * Given a SavedFrame JSObject, get its asyncParent SavedFrame object or nullptr * if there is no asyncParent. The `asyncParentp` out parameter is _NOT_ * guaranteed to be in the cx's compartment. Defaults to nullptr. @@ -5416,7 +5421,7 @@ GetSavedFrameAsyncCause(JSContext* cx, HandleObject savedFrame, MutableHandleStr extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject asyncParentp); -/* +/** * Given a SavedFrame JSObject, get its parent SavedFrame object or nullptr if * it is the oldest frame in the stack. The `parentp` out parameter is _NOT_ * guaranteed to be in the cx's compartment. Defaults to nullptr. @@ -5424,7 +5429,7 @@ GetSavedFrameAsyncParent(JSContext* cx, HandleObject savedFrame, MutableHandleOb extern JS_PUBLIC_API(SavedFrameResult) GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject parentp); -/* +/** * Given a SavedFrame JSObject stack, stringify it in the same format as * Error.prototype.stack. The stringified stack out parameter is placed in the * cx's compartment. Defaults to the empty string. @@ -5442,229 +5447,112 @@ BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp, } /* namespace JS */ -/* Stopwatch-based CPU monitoring. */ +/* Stopwatch-based performance monitoring. */ namespace js { class AutoStopwatch; -// Container for performance data -// All values are monotonic. -// All values are updated after running to completion. -struct PerformanceData { - // Number of times we have spent at least 2^n consecutive - // milliseconds executing code in this group. - // durations[0] is increased whenever we spend at least 1 ms - // executing code in this group - // durations[1] whenever we spend 2ms+ - // - // durations[i] whenever we spend 2^ims+ - uint64_t durations[10]; - - // Total amount of time spent executing code in this group, in - // microseconds. - uint64_t totalUserTime; - uint64_t totalSystemTime; - uint64_t totalCPOWTime; - - // Total number of times code execution entered this group, - // since process launch. This may be greater than the number - // of times we have entered the event loop. - uint64_t ticks; - - PerformanceData() - : totalUserTime(0) - , totalSystemTime(0) - , totalCPOWTime(0) - , ticks(0) - { - mozilla::PodArrayZero(durations); - } - PerformanceData(const PerformanceData& from) - : totalUserTime(from.totalUserTime) - , totalSystemTime(from.totalSystemTime) - , totalCPOWTime(from.totalCPOWTime) - , ticks(from.ticks) - { - mozilla::PodArrayCopy(durations, from.durations); - } - PerformanceData& operator=(const PerformanceData& from) - { - mozilla::PodArrayCopy(durations, from.durations); - totalUserTime = from.totalUserTime; - totalSystemTime = from.totalSystemTime; - totalCPOWTime = from.totalCPOWTime; - ticks = from.ticks; - return *this; - } -}; - -// A group of compartments forming a single unit in terms of -// performance monitoring. -// -// Two compartments belong to the same group if either: -// - they are part of the same add-on; -// - they are part of the same webpage; -// - they are both system built-ins. -// -// This class is refcounted by instances of `JSCompartment`. -// Do not attempt to hold to a pointer to a `PerformanceGroup`. -struct PerformanceGroup { - - // Performance data for this group. - PerformanceData data; - - // An id unique to this runtime. - const uint64_t uid; - - // The number of cycles spent in this group during this iteration - // of the event loop. Note that cycles are not a reliable measure, - // especially over short intervals. See Runtime.cpp for a more - // complete discussion on the imprecision of cycle measurement. - uint64_t recentCycles; - - // The number of times this group has been activated during this - // iteration of the event loop. - uint64_t recentTicks; - - // The number of milliseconds spent doing CPOW during this - // iteration of the event loop. - uint64_t recentCPOW; +/** + * Abstract base class for a representation of the performance of a + * component. Embeddings interested in performance monitoring should + * provide a concrete implementation of this class, as well as the + * relevant callbacks (see below). + */ +struct JS_PUBLIC_API(PerformanceGroup) { + PerformanceGroup(); // The current iteration of the event loop. - uint64_t iteration() const { - return iteration_; - } + uint64_t iteration() const; // `true` if an instance of `AutoStopwatch` is already monitoring // the performance of this performance group for this iteration // of the event loop, `false` otherwise. - bool hasStopwatch(uint64_t it) const { - return stopwatch_ != nullptr && iteration_ == it; - } + bool isAcquired(uint64_t it) const; // `true` if a specific instance of `AutoStopwatch` is already monitoring // the performance of this performance group for this iteration // of the event loop, `false` otherwise. - bool hasStopwatch(uint64_t it, const AutoStopwatch* stopwatch) const { - return stopwatch_ == stopwatch && iteration_ == it; - } + bool isAcquired(uint64_t it, const AutoStopwatch* owner) const; // Mark that an instance of `AutoStopwatch` is monitoring // the performance of this group for a given iteration. - void acquireStopwatch(uint64_t it, const AutoStopwatch* stopwatch) { - if (iteration_ != it) { - // Any data that pretends to be recent is actually bound - // to an older iteration and therefore stale. - resetRecentData(); - } - iteration_ = it; - stopwatch_ = stopwatch; - } + void acquire(uint64_t it, const AutoStopwatch* owner); // Mark that no `AutoStopwatch` is monitoring the // performance of this group for the iteration. - void releaseStopwatch(uint64_t it, const AutoStopwatch* stopwatch) { - if (iteration_ != it) - return; + void release(uint64_t it, const AutoStopwatch* owner); - MOZ_ASSERT(stopwatch == stopwatch_ || stopwatch_ == nullptr); - stopwatch_ = nullptr; - } + // The number of cycles spent in this group during this iteration + // of the event loop. Note that cycles are not a reliable measure, + // especially over short intervals. See Stopwatch.* for a more + // complete discussion on the imprecision of cycle measurement. + uint64_t recentCycles(uint64_t iteration) const; + void addRecentCycles(uint64_t iteration, uint64_t cycles); + + // The number of times this group has been activated during this + // iteration of the event loop. + uint64_t recentTicks(uint64_t iteration) const; + void addRecentTicks(uint64_t iteration, uint64_t ticks); + + // The number of microseconds spent doing CPOW during this + // iteration of the event loop. + uint64_t recentCPOW(uint64_t iteration) const; + void addRecentCPOW(uint64_t iteration, uint64_t CPOW); // Get rid of any data that pretends to be recent. - void resetRecentData() { - recentCycles = 0; - recentTicks = 0; - recentCPOW = 0; - } + void resetRecentData(); - // Refcounting. For use with RefPtr. - void AddRef(); - void Release(); + // `true` if new measures should be added to this group, `false` + // otherwise. + bool isActive() const; + void setIsActive(bool); - // Construct a PerformanceGroup for a single compartment. - explicit PerformanceGroup(JSRuntime* rt); + // `true` if this group has been used in the current iteration, + // `false` otherwise. + bool isUsedInThisIteration() const; + void setIsUsedInThisIteration(bool); + protected: + // An implementation of `delete` for this object. Must be provided + // by the embedding. + virtual void Delete() = 0; - // Construct a PerformanceGroup for a group of compartments. - explicit PerformanceGroup(JSContext* rt, void* key); + private: + // The number of cycles spent in this group during this iteration + // of the event loop. Note that cycles are not a reliable measure, + // especially over short intervals. See Runtime.cpp for a more + // complete discussion on the imprecision of cycle measurement. + uint64_t recentCycles_; -private: - PerformanceGroup& operator=(const PerformanceGroup&) = delete; - PerformanceGroup(const PerformanceGroup&) = delete; + // The number of times this group has been activated during this + // iteration of the event loop. + uint64_t recentTicks_; - JSRuntime* runtime_; - - // The stopwatch currently monitoring the group, - // or `nullptr` if none. Used ony for comparison. - const AutoStopwatch* stopwatch_; + // The number of microseconds spent doing CPOW during this + // iteration of the event loop. + uint64_t recentCPOW_; // The current iteration of the event loop. If necessary, // may safely overflow. uint64_t iteration_; - // The hash key for this PerformanceGroup. - void* const key_; + // `true` if new measures should be added to this group, `false` + // otherwise. + bool isActive_; - // Refcounter. + // `true` if this group has been used in the current iteration, + // `false` otherwise. + bool isUsedInThisIteration_; + + // The stopwatch currently monitoring the group, + // or `nullptr` if none. Used ony for comparison. + const AutoStopwatch* owner_; + + public: + // Compatibility with RefPtr<> + void AddRef(); + void Release(); uint64_t refCount_; - - // `true` if this PerformanceGroup may be shared by several - // compartments, `false` if it is dedicated to a single - // compartment. - const bool isSharedGroup_; -}; - -// -// Each PerformanceGroupHolder handles: -// - a reference-counted indirection towards a PerformanceGroup shared -// by several compartments -// - a owned PerformanceGroup representing the performance of a single -// compartment. -// -struct PerformanceGroupHolder { - // Get the shared group. - // On first call, this causes a single Hashtable lookup. - // Successive calls do not require further lookups. - js::PerformanceGroup* getSharedGroup(JSContext*); - - // Get the own group. - js::PerformanceGroup* getOwnGroup(); - - // `true` if the this holder is currently associated to a shared - // PerformanceGroup, `false` otherwise. Use this method to avoid - // instantiating a PerformanceGroup if you only need to get - // available performance data. - inline bool hasSharedGroup() const { - return sharedGroup_ != nullptr; - } - inline bool hasOwnGroup() const { - return ownGroup_ != nullptr; - } - - // Remove the link to the PerformanceGroup. This method is designed - // as an invalidation mechanism if the JSCompartment changes nature - // (new values of `isSystem()`, `principals()` or `addonId`). - void unlink(); - - explicit PerformanceGroupHolder(JSRuntime* runtime) - : runtime_(runtime) - { } - ~PerformanceGroupHolder(); - - private: - // Return the key representing this PerformanceGroup in - // Runtime::Stopwatch. - // Do not deallocate the key. - void* getHashKey(JSContext* cx); - - JSRuntime *runtime_; - - // The PerformanceGroups held by this object. - // Initially set to `nullptr` until the first call to `getGroup`. - // May be reset to `nullptr` by a call to `unlink`. - RefPtr sharedGroup_; - RefPtr ownGroup_; }; /** @@ -5673,7 +5561,7 @@ struct PerformanceGroupHolder { * Until `FlushMonitoring` has been called, all PerformanceMonitoring data is invisible * to the outside world and can cancelled with a call to `ResetMonitoring`. */ -extern JS_PUBLIC_API(void) +extern JS_PUBLIC_API(bool) FlushPerformanceMonitoring(JSRuntime*); /** @@ -5682,6 +5570,12 @@ FlushPerformanceMonitoring(JSRuntime*); extern JS_PUBLIC_API(void) ResetPerformanceMonitoring(JSRuntime*); +/** + * Cleanup any memory used by performance monitoring. + */ +extern JS_PUBLIC_API(void) +DisposePerformanceMonitoring(JSRuntime*); + /** * Turn on/off stopwatch-based CPU monitoring. * @@ -5697,10 +5591,6 @@ extern JS_PUBLIC_API(bool) SetStopwatchIsMonitoringJank(JSRuntime*, bool); extern JS_PUBLIC_API(bool) GetStopwatchIsMonitoringJank(JSRuntime*); -extern JS_PUBLIC_API(bool) -SetStopwatchIsMonitoringPerCompartment(JSRuntime*, bool); -extern JS_PUBLIC_API(bool) -GetStopwatchIsMonitoringPerCompartment(JSRuntime*); extern JS_PUBLIC_API(bool) IsStopwatchActive(JSRuntime*); @@ -5718,31 +5608,21 @@ extern JS_PUBLIC_API(void) AddCPOWPerformanceDelta(JSRuntime*, uint64_t delta); typedef bool -(PerformanceStatsWalker)(JSContext* cx, - const PerformanceData& stats, uint64_t uid, - const uint64_t* parentId, void* closure); - -/** - * Extract the performance statistics. - * - * Note that before calling `walker`, we enter the corresponding context. - */ +(*StopwatchStartCallback)(uint64_t, void*); extern JS_PUBLIC_API(bool) -IterPerformanceStats(JSContext* cx, PerformanceStatsWalker* walker, js::PerformanceData* process, void* closure); +SetStopwatchStartCallback(JSRuntime*, StopwatchStartCallback, void*); + +typedef bool +(*StopwatchCommitCallback)(uint64_t, mozilla::Vector>&, void*); +extern JS_PUBLIC_API(bool) +SetStopwatchCommitCallback(JSRuntime*, StopwatchCommitCallback, void*); + +typedef bool +(*GetGroupsCallback)(JSContext*, mozilla::Vector>&, void*); +extern JS_PUBLIC_API(bool) +SetGetPerformanceGroupsCallback(JSRuntime*, GetGroupsCallback, void*); } /* namespace js */ -/** - * Callback used to ask the embedding to determine in which - * Performance Group a compartment belongs. Typically, this is used to - * regroup JSCompartments from several iframes from the same page or - * from several compartments of the same addon into a single - * Performance Group. - * - * Returns an opaque key. - */ -extern JS_PUBLIC_API(void) -JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb); - #endif /* jsapi_h */ diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 9d4f55c6b3..c5dc360283 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -57,10 +57,12 @@ JS_SplicePrototype(JSContext* cx, JS::HandleObject obj, JS::HandleObject proto); extern JS_FRIEND_API(JSObject*) JS_NewObjectWithUniqueType(JSContext* cx, const JSClass* clasp, JS::HandleObject proto); -// Allocate an object in exactly the same way as JS_NewObjectWithGivenProto, but -// without invoking the metadata callback on it. This allows creation of -// internal bookkeeping objects that are guaranteed to not have metadata -// attached to them. +/** + * Allocate an object in exactly the same way as JS_NewObjectWithGivenProto, but + * without invoking the metadata callback on it. This allows creation of + * internal bookkeeping objects that are guaranteed to not have metadata + * attached to them. + */ extern JS_FRIEND_API(JSObject*) JS_NewObjectWithoutMetadata(JSContext* cx, const JSClass* clasp, JS::Handle proto); @@ -83,7 +85,7 @@ JS_NondeterministicGetWeakMapKeys(JSContext* cx, JS::HandleObject obj, JS::Mutab extern JS_FRIEND_API(unsigned) JS_PCToLineNumber(JSScript* script, jsbytecode* pc, unsigned* columnp = nullptr); -/* +/** * Determine whether the given object is backed by a DeadObjectProxy. * * Such objects hold no other objects (they have no outgoing reference edges) @@ -124,7 +126,7 @@ JS_ObjectToOuterObject(JSContext* cx, JS::HandleObject obj); extern JS_FRIEND_API(JSObject*) JS_CloneObject(JSContext* cx, JS::HandleObject obj, JS::HandleObject proto); -/* +/** * Copy the own properties of src to dst in a fast way. src and dst must both * be native and must be in the compartment of cx. They must have the same * class, the same parent, and the same prototype. Class reserved slots will @@ -209,13 +211,13 @@ DumpBacktrace(JSContext* cx); namespace JS { -// Exposed for DumpJSStack +/** Exposed for DumpJSStack */ extern JS_FRIEND_API(char*) FormatStackDump(JSContext* cx, char* buf, bool showArgs, bool showLocals, bool showThisProps); } // namespace JS -/* +/** * Copies all own properties from |obj| to |target|. |obj| must be a "native" * object (that is to say, normal-ish - not an Array or a Proxy). * @@ -388,7 +390,7 @@ proxy_GetElements(JSContext* cx, JS::HandleObject proxy, uint32_t begin, uint32_ extern JS_FRIEND_API(JSString*) proxy_FunToString(JSContext* cx, JS::HandleObject proxy, unsigned indent); -/* +/** * A class of objects that return source code on demand. * * When code is compiled with setSourceIsLazy(true), SpiderMonkey doesn't @@ -403,7 +405,7 @@ class SourceHook { public: virtual ~SourceHook() { } - /* + /** * Set |*src| and |*length| to refer to the source code for |filename|. * On success, the caller owns the buffer to which |*src| points, and * should use JS_free to free it. @@ -411,7 +413,7 @@ class SourceHook { virtual bool load(JSContext* cx, const char* filename, char16_t** src, size_t* length) = 0; }; -/* +/** * Have |rt| use |hook| to retrieve lazily-retrieved source code. See the * comments for SourceHook. The runtime takes ownership of the hook, and * will delete it when the runtime itself is deleted, or when a new hook is @@ -420,7 +422,7 @@ class SourceHook { extern JS_FRIEND_API(void) SetSourceHook(JSRuntime* rt, mozilla::UniquePtr hook); -/* Remove |rt|'s source hook, and return it. The caller now owns the hook. */ +/** Remove |rt|'s source hook, and return it. The caller now owns the hook. */ extern JS_FRIEND_API(mozilla::UniquePtr) ForgetSourceHook(JSRuntime* rt); @@ -435,7 +437,7 @@ typedef enum { IgnoreNurseryObjects } DumpHeapNurseryBehaviour; - /* + /** * Dump the complete object graph of heap-allocated things. * fp is the file for the dump output. */ @@ -495,7 +497,7 @@ GetWeakmapKeyDelegate(JSObject* key); JS_FRIEND_API(JS::TraceKind) GCThingTraceKind(void* thing); -/* +/** * Invoke cellCallback on every gray JS_OBJECT in the given zone. */ extern JS_FRIEND_API(void) @@ -537,9 +539,11 @@ public: static const uint32_t FIXED_SLOTS_SHIFT = 27; }; -// This layout is shared by all native objects. For non-native objects, the -// group may always be accessed safely, and other members may be as well, -// depending on the object's specific layout. +/** + * This layout is shared by all native objects. For non-native objects, the + * group may always be accessed safely, and other members may be as well, + * depending on the object's specific layout. + */ struct Object { shadow::ObjectGroup* group; shadow::Shape* shape; @@ -677,7 +681,7 @@ inline void AssertSameCompartment(JSObject* objA, JSObject* objB) {} JS_FRIEND_API(void) NotifyAnimationActivity(JSObject* obj); -/* +/** * Return the outermost enclosing function (script) of the scripted caller. * This function returns nullptr in several cases: * - no script is running on the context @@ -1032,12 +1036,14 @@ GetPCCountScriptSummary(JSContext* cx, size_t script); JS_FRIEND_API(JSString*) GetPCCountScriptContents(JSContext* cx, size_t script); -// Generate lcov trace file content for the current compartment, and allocate a -// new buffer and return the content in it, the size of the newly allocated -// content within the buffer would be set to the length out-param. -// -// In case of out-of-memory, this function returns nullptr and does not set any -// value to the length out-param. +/** + * Generate lcov trace file content for the current compartment, and allocate a + * new buffer and return the content in it, the size of the newly allocated + * content within the buffer would be set to the length out-param. + * + * In case of out-of-memory, this function returns nullptr and does not set any + * value to the length out-param. + */ JS_FRIEND_API(char*) GetCodeCoverageSummary(JSContext* cx, size_t* length); @@ -1047,7 +1053,7 @@ ContextHasOutstandingRequests(const JSContext* cx); typedef void (* ActivityCallback)(void* arg, bool active); -/* +/** * Sets a callback that is run whenever the runtime goes idle - the * last active request ceases - and begins activity - when it was * idle and a request begins. @@ -1072,7 +1078,7 @@ GetDOMCallbacks(JSRuntime* rt); extern JS_FRIEND_API(JSObject*) GetTestingFunctions(JSContext* cx); -/* +/** * Helper to convert FreeOp to JSFreeOp when the definition of FreeOp is not * available and the compiler does not know that FreeOp inherits from * JSFreeOp. @@ -1085,7 +1091,7 @@ CastToJSFreeOp(FreeOp* fop) /* Implemented in jsexn.cpp. */ -/* +/** * Get an error type name from a JSExnType constant. * Returns nullptr for invalid arguments and JSEXN_INTERNALERR */ @@ -1228,7 +1234,7 @@ inline bool DOMProxyIsShadowing(DOMProxyShadowsResult result) { /* Implemented in jsdate.cpp. */ -/* Detect whether the internal date value is NaN. */ +/** Detect whether the internal date value is NaN. */ extern JS_FRIEND_API(bool) DateIsValid(JSContext* cx, JS::HandleObject obj, bool* isValid); @@ -1239,7 +1245,7 @@ DateGetMsecSinceEpoch(JSContext* cx, JS::HandleObject obj, double* msecSinceEpoc /* Implemented in jscntxt.cpp. */ -/* +/** * Report an exception, which is currently realized as a printf-style format * string and its arguments. */ @@ -1259,7 +1265,7 @@ GetErrorMessage(void* userRef, const unsigned errorNumber); // AutoStableStringChars is here so we can use it in ErrorReport. It // should get moved out of here if we can manage it. See bug 1040316. -/* +/** * This class provides safe access to a string's chars across a GC. Once * we allocate strings and chars in the nursery (bug 903519), this class * will have to make a copy of the string's chars if they are allocated @@ -1328,8 +1334,10 @@ class MOZ_STACK_CLASS JS_FRIEND_API(AutoStableStringChars) void operator=(const AutoStableStringChars& other) = delete; }; -// Creates a string of the form |ErrorType: ErrorMessage| for a JSErrorReport, -// which generally matches the toString() behavior of an ErrorObject. +/** + * Creates a string of the form |ErrorType: ErrorMessage| for a JSErrorReport, + * which generally matches the toString() behavior of an ErrorObject. + */ extern JS_FRIEND_API(JSString*) ErrorReportToString(JSContext* cx, JSErrorReport* reportp); @@ -1402,7 +1410,7 @@ GetSCOffset(JSStructuredCloneWriter* writer); namespace Scalar { -/* +/** * Scalar types that can appear in typed arrays and typed objects. The enum * values must to be kept in sync with the JS_SCALARTYPEREPR_ constants, as * well as the TypedArrayObject::classes and TypedArrayObject::protoClasses @@ -1418,13 +1426,13 @@ enum Type { Float32, Float64, - /* + /** * Special type that is a uint8_t, but assignments are clamped to [0, 256). * Treat the raw data type as a uint8_t. */ Uint8Clamped, - /* + /** * SIMD types don't have their own TypedArray equivalent, for now. */ MaxTypedArrayViewType, @@ -1661,19 +1669,19 @@ extern JS_FRIEND_API(JSObject*) JS_NewSharedFloat64ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, uint32_t length); -/* +/** * Create a new SharedArrayBuffer with the given byte length. */ extern JS_FRIEND_API(JSObject*) JS_NewSharedArrayBuffer(JSContext* cx, uint32_t nbytes); -/* +/** * Create a new ArrayBuffer with the given byte length. */ extern JS_FRIEND_API(JSObject*) JS_NewArrayBuffer(JSContext* cx, uint32_t nbytes); -/* +/** * Check whether obj supports JS_GetTypedArray* APIs. Note that this may return * false if a security wrapper is encountered that denies the unwrapping. If * this test or one of the JS_Is*Array tests succeeds, then it is safe to call @@ -1682,13 +1690,13 @@ JS_NewArrayBuffer(JSContext* cx, uint32_t nbytes); extern JS_FRIEND_API(bool) JS_IsTypedArrayObject(JSObject* obj); -/* +/** * Ditto for JS_GetSharedTypedArray* APIs. */ extern JS_FRIEND_API(bool) JS_IsSharedTypedArrayObject(JSObject* obj); -/* +/** * Check whether obj supports JS_GetArrayBufferView* APIs. Note that this may * return false if a security wrapper is encountered that denies the * unwrapping. If this test or one of the more specific tests succeeds, then it @@ -1932,7 +1940,7 @@ JS_IsArrayBufferObject(JSObject* obj); extern JS_FRIEND_API(bool) JS_IsSharedArrayBufferObject(JSObject* obj); -/* +/** * Return the available byte length of an array buffer. * * |obj| must have passed a JS_IsArrayBufferObject test, or somehow be known @@ -1942,7 +1950,7 @@ JS_IsSharedArrayBufferObject(JSObject* obj); extern JS_FRIEND_API(uint32_t) JS_GetArrayBufferByteLength(JSObject* obj); -/* +/** * Return true if the arrayBuffer contains any data. This will return false for * ArrayBuffer.prototype and neutered ArrayBuffers. * @@ -1953,7 +1961,7 @@ JS_GetArrayBufferByteLength(JSObject* obj); extern JS_FRIEND_API(bool) JS_ArrayBufferHasData(JSObject* obj); -/* +/** * Check whether the obj is ArrayBufferObject and memory mapped. Note that this * may return false if a security wrapper is encountered that denies the * unwrapping. @@ -1961,7 +1969,7 @@ JS_ArrayBufferHasData(JSObject* obj); extern JS_FRIEND_API(bool) JS_IsMappedArrayBufferObject(JSObject* obj); -/* +/** * Return the number of elements in a typed array. * * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow @@ -1971,7 +1979,7 @@ JS_IsMappedArrayBufferObject(JSObject* obj); extern JS_FRIEND_API(uint32_t) JS_GetTypedArrayLength(JSObject* obj); -/* +/** * Return the byte offset from the start of an array buffer to the start of a * typed array view. * @@ -1982,7 +1990,7 @@ JS_GetTypedArrayLength(JSObject* obj); extern JS_FRIEND_API(uint32_t) JS_GetTypedArrayByteOffset(JSObject* obj); -/* +/** * Return the byte length of a typed array. * * |obj| must have passed a JS_IsTypedArrayObject/JS_Is*Array test, or somehow @@ -1992,7 +2000,7 @@ JS_GetTypedArrayByteOffset(JSObject* obj); extern JS_FRIEND_API(uint32_t) JS_GetTypedArrayByteLength(JSObject* obj); -/* +/** * Check whether obj supports JS_ArrayBufferView* APIs. Note that this may * return false if a security wrapper is encountered that denies the * unwrapping. @@ -2000,7 +2008,7 @@ JS_GetTypedArrayByteLength(JSObject* obj); extern JS_FRIEND_API(bool) JS_IsArrayBufferViewObject(JSObject* obj); -/* +/** * More generic name for JS_GetTypedArrayByteLength to cover DataViews as well */ extern JS_FRIEND_API(uint32_t) @@ -2060,14 +2068,14 @@ JS_GetSharedFloat32ArrayData(JSObject* obj, const JS::AutoCheckCannotGC&); extern JS_FRIEND_API(double*) JS_GetSharedFloat64ArrayData(JSObject* obj, const JS::AutoCheckCannotGC&); -/* +/** * Same as above, but for any kind of ArrayBufferView. Prefer the type-specific * versions when possible. */ extern JS_FRIEND_API(void*) JS_GetArrayBufferViewData(JSObject* obj, const JS::AutoCheckCannotGC&); -/* +/** * Return the ArrayBuffer underlying an ArrayBufferView. If the buffer has been * neutered, this will still return the neutered buffer. |obj| must be an * object that would return true for JS_IsArrayBufferViewObject(). @@ -2080,7 +2088,7 @@ typedef enum { KeepData } NeuterDataDisposition; -/* +/** * Set an ArrayBuffer's length to 0 and neuter all of its views. * * The |changeData| argument is a hint to inform internal behavior with respect @@ -2093,7 +2101,7 @@ extern JS_FRIEND_API(bool) JS_NeuterArrayBuffer(JSContext* cx, JS::HandleObject obj, NeuterDataDisposition changeData); -/* +/** * Check whether the obj is ArrayBufferObject and neutered. Note that this * may return false if a security wrapper is encountered that denies the * unwrapping. @@ -2101,13 +2109,13 @@ JS_NeuterArrayBuffer(JSContext* cx, JS::HandleObject obj, extern JS_FRIEND_API(bool) JS_IsNeuteredArrayBufferObject(JSObject* obj); -/* +/** * Check whether obj supports JS_GetDataView* APIs. */ JS_FRIEND_API(bool) JS_IsDataViewObject(JSObject* obj); -/* +/** * Create a new DataView using the given ArrayBuffer for storage. The given * buffer must be an ArrayBuffer (or a cross-compartment wrapper of an * ArrayBuffer), and the offset and length must fit within the bounds of the @@ -2117,7 +2125,7 @@ JS_IsDataViewObject(JSObject* obj); JS_FRIEND_API(JSObject*) JS_NewDataView(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, int32_t byteLength); -/* +/** * Return the byte offset of a data view into its array buffer. |obj| must be a * DataView. * @@ -2128,7 +2136,7 @@ JS_NewDataView(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, JS_FRIEND_API(uint32_t) JS_GetDataViewByteOffset(JSObject* obj); -/* +/** * Return the byte length of a data view. * * |obj| must have passed a JS_IsDataViewObject test, or somehow be known that @@ -2139,7 +2147,7 @@ JS_GetDataViewByteOffset(JSObject* obj); JS_FRIEND_API(uint32_t) JS_GetDataViewByteLength(JSObject* obj); -/* +/** * Return a pointer to the beginning of the data referenced by a DataView. * * |obj| must have passed a JS_IsDataViewObject test, or somehow be known that @@ -2152,7 +2160,7 @@ JS_GetDataViewData(JSObject* obj, const JS::AutoCheckCannotGC&); namespace js { -/* +/** * Add a watchpoint -- in the Object.prototype.watch sense -- to |obj| for the * property |id|, using the callable object |callable| as the function to be * called for notifications. @@ -2164,7 +2172,7 @@ namespace js { extern JS_FRIEND_API(bool) WatchGuts(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::HandleObject callable); -/* +/** * Remove a watchpoint -- in the Object.prototype.watch sense -- from |obj| for * the property |id|. * @@ -2183,7 +2191,7 @@ enum class InlinableNative : uint16_t; } // namespace js -/* +/** * A class, expected to be passed by value, which represents the CallArgs for a * JSJitGetterOp. */ @@ -2203,7 +2211,7 @@ class JSJitGetterCallArgs : protected JS::MutableHandleValue } }; -/* +/** * A class, expected to be passed by value, which represents the CallArgs for a * JSJitSetterOp. */ @@ -2226,7 +2234,7 @@ class JSJitSetterCallArgs : protected JS::MutableHandleValue struct JSJitMethodCallArgsTraits; -/* +/** * A class, expected to be passed by reference, which represents the CallArgs * for a JSJitMethodOp. */ @@ -2273,11 +2281,6 @@ struct JSJitMethodCallArgsTraits static const size_t offsetOfArgc = offsetof(JSJitMethodCallArgs, argc_); }; -/* - * This struct contains metadata passed from the DOM to the JS Engine for JIT - * optimizations on DOM property accessors. Eventually, this should be made - * available to general JSAPI users, but we are not currently ready to do so. - */ typedef bool (* JSJitGetterOp)(JSContext* cx, JS::HandleObject thisObj, void* specializedThis, JSJitGetterCallArgs args); @@ -2288,6 +2291,11 @@ typedef bool (* JSJitMethodOp)(JSContext* cx, JS::HandleObject thisObj, void* specializedThis, const JSJitMethodCallArgs& args); +/** + * This struct contains metadata passed from the DOM to the JS Engine for JIT + * optimizations on DOM property accessors. Eventually, this should be made + * available to general JSAPI users, but we are not currently ready to do so. + */ struct JSJitInfo { enum OpType { Getter, @@ -2327,24 +2335,31 @@ struct JSJitInfo { static_assert(Any & Object, "Any must include Object."); static_assert(Any & Null, "Any must include Null."); + /** + * An enum that describes what this getter/setter/method aliases. This + * determines what things can be hoisted past this call, and if this + * call is movable what it can be hoisted past. + */ enum AliasSet { - // An enum that describes what this getter/setter/method aliases. This - // determines what things can be hoisted past this call, and if this - // call is movable what it can be hoisted past. - - // Alias nothing: a constant value, getting it can't affect any other - // values, nothing can affect it. + /** + * Alias nothing: a constant value, getting it can't affect any other + * values, nothing can affect it. + */ AliasNone, - // Alias things that can modify the DOM but nothing else. Doing the - // call can't affect the behavior of any other function. + /** + * Alias things that can modify the DOM but nothing else. Doing the + * call can't affect the behavior of any other function. + */ AliasDOMSets, - // Alias the world. Calling this can change arbitrary values anywhere - // in the system. Most things fall in this bucket. + /** + * Alias the world. Calling this can change arbitrary values anywhere + * in the system. Most things fall in this bucket. + */ AliasEverything, - // Must be last. + /** Must be last. */ AliasSetCount }; @@ -2377,7 +2392,7 @@ struct JSJitInfo { JSJitGetterOp getter; JSJitSetterOp setter; JSJitMethodOp method; - /* A DOM static method, used for Promise wrappers */ + /** A DOM static method, used for Promise wrappers */ JSNative staticMethod; }; @@ -2397,16 +2412,18 @@ struct JSJitInfo { #define JITINFO_RETURN_TYPE_BITS 8 #define JITINFO_SLOT_INDEX_BITS 10 - // The OpType that says what sort of function we are. + /** The OpType that says what sort of function we are. */ uint32_t type_ : JITINFO_OP_TYPE_BITS; - // The alias set for this op. This is a _minimal_ alias set; in - // particular for a method it does not include whatever argument - // conversions might do. That's covered by argTypes and runtime - // analysis of the actual argument types being passed in. + /** + * The alias set for this op. This is a _minimal_ alias set; in + * particular for a method it does not include whatever argument + * conversions might do. That's covered by argTypes and runtime + * analysis of the actual argument types being passed in. + */ uint32_t aliasSet_ : JITINFO_ALIAS_SET_BITS; - // The return type tag. Might be JSVAL_TYPE_UNKNOWN. + /** The return type tag. Might be JSVAL_TYPE_UNKNOWN. */ uint32_t returnType_ : JITINFO_RETURN_TYPE_BITS; static_assert(OpTypeCount <= (1 << JITINFO_OP_TYPE_BITS), @@ -2420,29 +2437,46 @@ struct JSJitInfo { #undef JITINFO_ALIAS_SET_BITS #undef JITINFO_OP_TYPE_BITS - uint32_t isInfallible : 1; /* Is op fallible? False in setters. */ - uint32_t isMovable : 1; /* Is op movable? To be movable the op must - not AliasEverything, but even that might - not be enough (e.g. in cases when it can - throw or is explicitly not movable). */ - uint32_t isEliminatable : 1; /* Can op be dead-code eliminated? Again, this - depends on whether the op can throw, in - addition to the alias set. */ + /** Is op fallible? False in setters. */ + uint32_t isInfallible : 1; + + /** + * Is op movable? To be movable the op must + * not AliasEverything, but even that might + * not be enough (e.g. in cases when it can + * throw or is explicitly not movable). + */ + uint32_t isMovable : 1; + + /** + * Can op be dead-code eliminated? Again, this + * depends on whether the op can throw, in + * addition to the alias set. + */ + uint32_t isEliminatable : 1; + // XXXbz should we have a JSValueType for the type of the member? - uint32_t isAlwaysInSlot : 1; /* True if this is a getter that can always - get the value from a slot of the "this" - object. */ - uint32_t isLazilyCachedInSlot : 1; /* True if this is a getter that can - sometimes (if the slot doesn't contain - UndefinedValue()) get the value from a - slot of the "this" object. */ - uint32_t isTypedMethod : 1; /* True if this is an instance of - JSTypedMethodJitInfo. */ - uint32_t slotIndex : JITINFO_SLOT_INDEX_BITS; /* If isAlwaysInSlot or - isSometimesInSlot is true, - the index of the slot to - get the value from. - Otherwise 0. */ + /** + * True if this is a getter that can always + * get the value from a slot of the "this" object. + */ + uint32_t isAlwaysInSlot : 1; + + /** + * True if this is a getter that can sometimes (if the slot doesn't contain + * UndefinedValue()) get the value from a slot of the "this" object. + */ + uint32_t isLazilyCachedInSlot : 1; + + /** True if this is an instance of JSTypedMethodJitInfo. */ + uint32_t isTypedMethod : 1; + + /** + * If isAlwaysInSlot or isSometimesInSlot is true, + * the index of the slot to get the value from. + * Otherwise 0. + */ + uint32_t slotIndex : JITINFO_SLOT_INDEX_BITS; static const size_t maxSlotIndex = (1 << JITINFO_SLOT_INDEX_BITS) - 1; @@ -2537,7 +2571,7 @@ bool IdMatchesAtom(jsid id, JSAtom* atom); } // namespace detail } // namespace js -/* +/** * Must not be used on atoms that are representable as integer jsids. * Prefer NameToId or AtomToId over this function: * @@ -2603,7 +2637,7 @@ IdToValue(jsid id) return JS::UndefinedValue(); } -/* +/** * If the embedder has registered a ScriptEnvironmentPreparer, * PrepareScriptEnvironmentAndInvoke will call the preparer's 'invoke' method * with the given |closure|, with the assumption that the preparer will set up @@ -2631,7 +2665,7 @@ JS_FRIEND_API(void) SetScriptEnvironmentPreparer(JSRuntime* rt, ScriptEnvironmentPreparer* preparer); -/* +/** * To help embedders enforce their invariants, we allow them to specify in * advance which JSContext should be passed to JSAPI calls. If this is set * to a non-null value, the assertSameCompartment machinery does double- @@ -2655,7 +2689,7 @@ enum CTypesActivityType { typedef void (* CTypesActivityCallback)(JSContext* cx, CTypesActivityType type); -/* +/** * Sets a callback that is run whenever js-ctypes is about to be used when * calling into C. */ @@ -2687,7 +2721,7 @@ class MOZ_RAII JS_FRIEND_API(AutoCTypesActivityCallback) { typedef JSObject* (* ObjectMetadataCallback)(JSContext* cx, JSObject* obj); -/* +/** * Specify a callback to invoke when creating each JS object in the current * compartment, which may return a metadata object to associate with the * object. @@ -2695,7 +2729,7 @@ typedef JSObject* JS_FRIEND_API(void) SetObjectMetadataCallback(JSContext* cx, ObjectMetadataCallback callback); -/* Get the metadata associated with an object. */ +/** Get the metadata associated with an object. */ JS_FRIEND_API(JSObject*) GetObjectMetadata(JSObject* obj); @@ -2706,7 +2740,7 @@ GetElementsWithAdder(JSContext* cx, JS::HandleObject obj, JS::HandleObject recei JS_FRIEND_API(bool) ForwardToNative(JSContext* cx, JSNative native, const JS::CallArgs& args); -/* +/** * Helper function for HTMLDocument and HTMLFormElement. * * These are the only two interfaces that have [OverrideBuiltins], a named @@ -2747,28 +2781,30 @@ typedef long (*JitExceptionHandler)(void* exceptionRecord, // PEXECTION_RECORD void* context); // PCONTEXT -// Windows uses "structured exception handling" to handle faults. When a fault -// occurs, the stack is searched for a handler (similar to C++ exception -// handling). If the search does not find a handler, the "unhandled exception -// filter" is called. Breakpad uses the unhandled exception filter to do crash -// reporting. Unfortunately, on Win64, JIT code on the stack completely throws -// off this unwinding process and prevents the unhandled exception filter from -// being called. The reason is that Win64 requires unwind information be -// registered for all code regions and JIT code has none. While it is possible -// to register full unwind information for JIT code, this is a lot of work (one -// has to be able to recover the frame pointer at any PC) so instead we register -// a handler for all JIT code that simply calls breakpad's unhandled exception -// filter (which will perform crash reporting and then terminate the process). -// This would be wrong if there was an outer __try block that expected to handle -// the fault, but this is not generally allowed. -// -// Gecko must call SetJitExceptionFilter before any JIT code is compiled and -// only once per process. +/** + * Windows uses "structured exception handling" to handle faults. When a fault + * occurs, the stack is searched for a handler (similar to C++ exception + * handling). If the search does not find a handler, the "unhandled exception + * filter" is called. Breakpad uses the unhandled exception filter to do crash + * reporting. Unfortunately, on Win64, JIT code on the stack completely throws + * off this unwinding process and prevents the unhandled exception filter from + * being called. The reason is that Win64 requires unwind information be + * registered for all code regions and JIT code has none. While it is possible + * to register full unwind information for JIT code, this is a lot of work (one + * has to be able to recover the frame pointer at any PC) so instead we register + * a handler for all JIT code that simply calls breakpad's unhandled exception + * filter (which will perform crash reporting and then terminate the process). + * This would be wrong if there was an outer __try block that expected to handle + * the fault, but this is not generally allowed. + * + * Gecko must call SetJitExceptionFilter before any JIT code is compiled and + * only once per process. + */ extern JS_FRIEND_API(void) SetJitExceptionHandler(JitExceptionHandler handler); #endif -/* +/** * Get the nearest enclosing with scope object for a given function. If the * function is not scripted or is not enclosed by a with scope, returns the * global. @@ -2776,7 +2812,7 @@ SetJitExceptionHandler(JitExceptionHandler handler); extern JS_FRIEND_API(JSObject*) GetNearestEnclosingWithScopeObjectForFunction(JSFunction* fun); -/* +/** * Get the first SavedFrame object in this SavedFrame stack whose principals are * subsumed by the cx's principals. If there is no such frame, return nullptr. * @@ -2805,7 +2841,7 @@ JS_StoreStringPostBarrierCallback(JSContext* cx, void (*callback)(JSTracer* trc, JSString* key, void* data), JSString* key, void* data); -/* +/** * Forcibly clear postbarrier callbacks queued by the previous two methods. * This should be used when the object owning the postbarriered pointers is * being destroyed outside of a garbage collection. diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index d49fe4c8f2..b912a5ac26 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -487,6 +487,18 @@ fun_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) if (fun->hasResolvedName()) return true; + if (fun->isClassConstructor()) { + // It's impossible to have an empty named class expression. We + // use empty as a sentinel when creating default class + // constructors. + MOZ_ASSERT(fun->atom() != cx->names().empty); + + // Unnamed class expressions should not get a .name property + // at all. + if (fun->atom() == nullptr) + return true; + } + v.setString(fun->atom() == nullptr ? cx->runtime()->emptyString : fun->atom()); } @@ -996,7 +1008,7 @@ js::FindBody(JSContext* cx, HandleFunction fun, HandleLinearString src, size_t* } JSString* -js::FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lambdaParen) +js::FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen) { if (fun->isInterpretedLazy() && !fun->getOrCreateScript(cx)) return nullptr; @@ -1012,9 +1024,9 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lamb if (fun->hasScript()) { script = fun->nonLazyScript(); if (script->isGeneratorExp()) { - if ((!bodyOnly && !out.append("function genexp() {")) || + if (!out.append("function genexp() {") || !out.append("\n [generator expression]\n") || - (!bodyOnly && !out.append("}"))) + !out.append("}")) { return nullptr; } @@ -1024,21 +1036,21 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lamb bool funIsMethodOrNonArrowLambda = (fun->isLambda() && !fun->isArrow()) || fun->isMethod() || fun->isGetter() || fun->isSetter(); - if (!bodyOnly) { - // If we're not in pretty mode, put parentheses around lambda functions and methods. - if (fun->isInterpreted() && !lambdaParen && funIsMethodOrNonArrowLambda) { - if (!out.append("(")) - return nullptr; - } - if (!fun->isArrow()) { - if (!(fun->isStarGenerator() ? out.append("function* ") : out.append("function "))) - return nullptr; - } - if (fun->atom()) { - if (!out.append(fun->atom())) - return nullptr; - } + + // If we're not in pretty mode, put parentheses around lambda functions and methods. + if (fun->isInterpreted() && !lambdaParen && funIsMethodOrNonArrowLambda) { + if (!out.append("(")) + return nullptr; } + if (!fun->isArrow()) { + if (!(fun->isStarGenerator() ? out.append("function* ") : out.append("function "))) + return nullptr; + } + if (fun->atom()) { + if (!out.append(fun->atom())) + return nullptr; + } + bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin(); if (haveSource && !script->scriptSource()->hasSourceData() && !JSScript::loadSource(cx, script->scriptSource(), &haveSource)) @@ -1074,7 +1086,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lamb // resulting function will have the same semantics. bool addUseStrict = script->strict() && !script->explicitUseStrict() && !fun->isArrow(); - bool buildBody = funCon && !bodyOnly; + bool buildBody = funCon; if (buildBody) { // This function was created with the Function constructor. We don't // have source for the arguments, so we have to generate that. Part @@ -1099,7 +1111,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lamb if (!out.append(") {\n")) return nullptr; } - if ((bodyOnly && !funCon) || addUseStrict) { + if (addUseStrict) { // We need to get at the body either because we're only supposed to // return the body or we need to insert "use strict" into the body. size_t bodyStart = 0, bodyEnd; @@ -1130,10 +1142,8 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lamb } } - // Output just the body (for bodyOnly) or the body and possibly - // closing braces (for addUseStrict). - size_t dependentEnd = bodyOnly ? bodyEnd : src->length(); - if (!out.appendSubstring(src, bodyStart, dependentEnd - bodyStart)) + // Output the body and possibly closing braces (for addUseStrict). + if (!out.appendSubstring(src, bodyStart, src->length() - bodyStart)) return nullptr; } else { if (!out.append(src)) @@ -1143,29 +1153,39 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lamb if (!out.append("\n}")) return nullptr; } - if (bodyOnly) { - // Slap a semicolon on the end of functions with an expression body. - if (exprBody && !out.append(";")) - return nullptr; - } else if (!lambdaParen && funIsMethodOrNonArrowLambda) { + if (!lambdaParen && funIsMethodOrNonArrowLambda) { if (!out.append(")")) return nullptr; } } else if (fun->isInterpreted() && !fun->isSelfHostedBuiltin()) { - if ((!bodyOnly && !out.append("() {\n ")) || + if (!out.append("() {\n ") || !out.append("[sourceless code]") || - (!bodyOnly && !out.append("\n}"))) + !out.append("\n}")) + { return nullptr; + } if (!lambdaParen && fun->isLambda() && !fun->isArrow() && !out.append(")")) return nullptr; } else { MOZ_ASSERT(!fun->isExprBody()); - if ((!bodyOnly && !out.append("() {\n ")) - || !out.append("[native code]") - || (!bodyOnly && !out.append("\n}"))) - { - return nullptr; + if (fun->isNative() && fun->native() == js::DefaultDerivedClassConstructor) { + if (!out.append("(...args) {\n ") || + !out.append("super(...args);\n}")) + { + return nullptr; + } + } else { + if (!out.append("() {\n ")) + return nullptr; + + if (!fun->isNative() || fun->native() != js::DefaultClassConstructor) { + if (!out.append("[native code]")) + return nullptr; + } + + if (!out.append("\n}")) + return nullptr; } } return out.finishString(); @@ -1200,7 +1220,7 @@ fun_toStringHelper(JSContext* cx, HandleObject obj, unsigned indent) } RootedFunction fun(cx, &obj->as()); - return FunctionToString(cx, fun, false, indent != JS_DONT_PRETTY_PRINT); + return FunctionToString(cx, fun, indent != JS_DONT_PRETTY_PRINT); } bool diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 86bb26c667..c69893a816 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -78,6 +78,7 @@ class JSFunction : public js::NativeObject /* Derived Flags values for convenience: */ NATIVE_FUN = 0, NATIVE_CTOR = NATIVE_FUN | CONSTRUCTOR, + NATIVE_CLASS_CTOR = NATIVE_FUN | CONSTRUCTOR | CLASSCONSTRUCTOR_KIND, ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR, ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA, INTERPRETED_METHOD = INTERPRETED | METHOD_KIND, @@ -156,6 +157,7 @@ class JSFunction : public js::NativeObject nonLazyScript()->funHasExtensibleScope() || nonLazyScript()->funNeedsDeclEnvObject() || nonLazyScript()->needsHomeObject() || + nonLazyScript()->isDerivedClassConstructor() || isGenerator(); } @@ -531,6 +533,16 @@ class JSFunction : public js::NativeObject u.n.jitinfo = data; } + bool isDerivedClassConstructor() { + bool derived; + if (isInterpretedLazy()) + derived = lazyScript()->isDerivedClassConstructor(); + else + derived = nonLazyScript()->isDerivedClassConstructor(); + MOZ_ASSERT_IF(derived, isClassConstructor()); + return derived; + } + static unsigned offsetOfNativeOrScript() { static_assert(offsetof(U, n.native) == offsetof(U, i.s.script_), "native and script pointers must be in the same spot " @@ -776,7 +788,7 @@ JSFunction::getExtendedSlot(size_t which) const namespace js { -JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool bodyOnly, bool lambdaParen); +JSString* FunctionToString(JSContext* cx, HandleFunction fun, bool lambdaParen); template bool diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 8601a024d5..23e09015ad 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -967,14 +967,14 @@ CreateThisForFunctionWithGroup(JSContext* cx, HandleObjectGroup group, } JSObject* -js::CreateThisForFunctionWithProto(JSContext* cx, HandleObject callee, HandleObject proto, - NewObjectKind newKind /* = GenericObject */) +js::CreateThisForFunctionWithProto(JSContext* cx, HandleObject callee, HandleObject newTarget, + HandleObject proto, NewObjectKind newKind /* = GenericObject */) { RootedObject res(cx); if (proto) { RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, nullptr, TaggedProto(proto), - &callee->as())); + newTarget)); if (!group) return nullptr; @@ -986,7 +986,7 @@ js::CreateThisForFunctionWithProto(JSContext* cx, HandleObject callee, HandleObj // The script was analyzed successfully and may have changed // the new type table, so refetch the group. group = ObjectGroup::defaultNewGroup(cx, nullptr, TaggedProto(proto), - &callee->as()); + newTarget); MOZ_ASSERT(group && group->newScript()); } } @@ -1007,15 +1007,16 @@ js::CreateThisForFunctionWithProto(JSContext* cx, HandleObject callee, HandleObj } JSObject* -js::CreateThisForFunction(JSContext* cx, HandleObject callee, NewObjectKind newKind) +js::CreateThisForFunction(JSContext* cx, HandleObject callee, HandleObject newTarget, + NewObjectKind newKind) { RootedValue protov(cx); - if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov)) + if (!GetProperty(cx, newTarget, newTarget, cx->names().prototype, &protov)) return nullptr; RootedObject proto(cx); if (protov.isObject()) proto = &protov.toObject(); - JSObject* obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind); + JSObject* obj = CreateThisForFunctionWithProto(cx, callee, newTarget, proto, newKind); if (obj && newKind == SingletonObject) { RootedPlainObject nobj(cx, &obj->as()); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 490c1fb2b1..dd45f857aa 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1162,12 +1162,13 @@ GetInitialHeap(NewObjectKind newKind, const Class* clasp) // Specialized call for constructing |this| with a known function callee, // and a known prototype. extern JSObject* -CreateThisForFunctionWithProto(JSContext* cx, js::HandleObject callee, HandleObject proto, - NewObjectKind newKind = GenericObject); +CreateThisForFunctionWithProto(JSContext* cx, js::HandleObject callee, HandleObject newTarget, + HandleObject proto, NewObjectKind newKind = GenericObject); // Specialized call for constructing |this| with a known function callee. extern JSObject* -CreateThisForFunction(JSContext* cx, js::HandleObject callee, NewObjectKind newKind); +CreateThisForFunction(JSContext* cx, js::HandleObject callee, js::HandleObject newTarget, + NewObjectKind newKind); // Generic call for constructing |this|. extern JSObject* diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index e1b07beb8c..50acb568de 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -125,6 +125,7 @@ js::StackUses(JSScript* script, jsbytecode* pc) case JSOP_POPN: return GET_UINT16(pc); case JSOP_NEW: + case JSOP_SUPERCALL: return 2 + GET_ARGC(pc) + 1; default: /* stack: fun, this, [argc arguments] */ diff --git a/js/src/moz.build b/js/src/moz.build index e0472015ec..6c453e8a1c 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -328,6 +328,7 @@ UNIFIED_SOURCES += [ 'vm/SharedTypedArrayObject.cpp', 'vm/SPSProfiler.cpp', 'vm/Stack.cpp', + 'vm/Stopwatch.cpp', 'vm/String.cpp', 'vm/StringBuffer.cpp', 'vm/StructuredClone.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index ca99613ff8..8fc0bd53bb 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3626,8 +3626,7 @@ NestedShell(JSContext* cx, unsigned argc, Value* vp) } static bool -DecompileFunctionSomehow(JSContext* cx, unsigned argc, Value* vp, - JSString* (*decompiler)(JSContext*, HandleFunction, unsigned)) +DecompileFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 1 || !args[0].isObject() || !args[0].toObject().is()) { @@ -3635,25 +3634,13 @@ DecompileFunctionSomehow(JSContext* cx, unsigned argc, Value* vp, return true; } RootedFunction fun(cx, &args[0].toObject().as()); - JSString* result = decompiler(cx, fun, 0); + JSString* result = JS_DecompileFunction(cx, fun, 0); if (!result) return false; args.rval().setString(result); return true; } -static bool -DecompileBody(JSContext* cx, unsigned argc, Value* vp) -{ - return DecompileFunctionSomehow(cx, argc, vp, JS_DecompileFunctionBody); -} - -static bool -DecompileFunction(JSContext* cx, unsigned argc, Value* vp) -{ - return DecompileFunctionSomehow(cx, argc, vp, JS_DecompileFunction); -} - static bool DecompileThisScript(JSContext* cx, unsigned argc, Value* vp) { @@ -4789,10 +4776,6 @@ static const JSFunctionSpecWithHelp shell_functions[] = { "decompileFunction(func)", " Decompile a function."), - JS_FN_HELP("decompileBody", DecompileBody, 1, 0, -"decompileBody(func)", -" Decompile a function's body."), - JS_FN_HELP("decompileThis", DecompileThisScript, 0, 0, "decompileThis()", " Decompile the currently executing script."), diff --git a/js/src/tests/ecma_5/extensions/strict-function-toSource.js b/js/src/tests/ecma_5/extensions/strict-function-toSource.js index 9f366cddf6..c144500dda 100644 --- a/js/src/tests/ecma_5/extensions/strict-function-toSource.js +++ b/js/src/tests/ecma_5/extensions/strict-function-toSource.js @@ -10,8 +10,6 @@ function testRunOptionStrictMode(str, arg, result) { } assertEq(eval(uneval(testRunOptionStrictMode()))(), true); -if (typeof decompileBody !== "undefined") { - assertEq(decompileBody(new Function('x', 'return x*2;')).includes('\n"use strict"'), true); -} +assertEq((new Function('x', 'return x*2;')).toSource().includes('\n"use strict"'), true); reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Class/classHeritage.js b/js/src/tests/ecma_6/Class/classHeritage.js index d8804bdfbd..0b01612cf5 100644 --- a/js/src/tests/ecma_6/Class/classHeritage.js +++ b/js/src/tests/ecma_6/Class/classHeritage.js @@ -36,11 +36,11 @@ class base { override() { overrideCalled = "base" } } class derived extends base { - constructor() { }; + constructor() { super(); }; override() { overrideCalled = "derived"; } } var derivedExpr = class extends base { - constructor() { }; + constructor() { super(); }; override() { overrideCalled = "derived"; } }; diff --git a/js/src/tests/ecma_6/Class/className.js b/js/src/tests/ecma_6/Class/className.js index 6d47f790c2..645e505dd7 100644 --- a/js/src/tests/ecma_6/Class/className.js +++ b/js/src/tests/ecma_6/Class/className.js @@ -173,10 +173,15 @@ testName(ExtendedExpr3, "base", false, false, false); // ---- anonymous ---- +// Anonymous class expressions don't get name properties unless specified in a +// static manner. let Anon = class { constructor() {} }; -testName(Anon, "", true, false, false); +testName(Anon, "", false, false, false); + +let AnonDefault = class { }; +testName(AnonDefault, "", false, false, false); let AnonWithGetter = class { constructor() {} @@ -201,10 +206,11 @@ testName(AnonWithGetterSetter, "base", false, true, true); let ExtendedAnon1 = class extends Anon { constructor() {} }; -testName(ExtendedAnon1, "", true, false, false); -delete ExtendedAnon1.name; testName(ExtendedAnon1, "", false, false, false); +let ExtendedAnonDefault = class extends Anon { }; +testName(ExtendedAnonDefault, "", false, false, false); + let ExtendedAnon2 = class extends AnonWithGetterSetter { constructor() {} static get name() { return "extend"; } diff --git a/js/src/tests/ecma_6/Class/defaultConstructorBase.js b/js/src/tests/ecma_6/Class/defaultConstructorBase.js new file mode 100644 index 0000000000..851cf10672 --- /dev/null +++ b/js/src/tests/ecma_6/Class/defaultConstructorBase.js @@ -0,0 +1,25 @@ +var test = ` + +class base { + method() { return 1; } + *gen() { return 2; } + static sMethod() { return 3; } + get answer() { return 42; } +} + +// Having a default constructor should work, and also not make you lose +// everything for no good reason + +assertEq(Object.getPrototypeOf(new base()), base.prototype); +assertEq(new base().method(), 1); +assertEq(new base().gen().next().value, 2); +assertEq(base.sMethod(), 3); +assertEq(new base().answer, 42); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/defaultConstructorNotCallable.js b/js/src/tests/ecma_6/Class/defaultConstructorNotCallable.js new file mode 100644 index 0000000000..0df212bb22 --- /dev/null +++ b/js/src/tests/ecma_6/Class/defaultConstructorNotCallable.js @@ -0,0 +1,15 @@ +var test = ` + +class badBase {} +assertThrowsInstanceOf(badBase, TypeError); + +class badSub extends (class {}) {} +assertThrowsInstanceOf(badSub, TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js b/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js index 6f4898b31f..4b86408fc1 100644 --- a/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js +++ b/js/src/tests/ecma_6/Class/derivedConstructorDisabled.js @@ -18,6 +18,7 @@ class derived extends base { // Make sure eval and arrows are still valid in non-derived constructors. new base(); + // Eval throws in derived class constructors, in both class expressions and // statements. assertThrowsInstanceOf((() => new derived()), InternalError); @@ -26,8 +27,8 @@ assertThrowsInstanceOf((() => new class extends base { constructor() { eval('') var g = newGlobal(); var dbg = Debugger(g); dbg.onDebuggerStatement = function(frame) { assertThrowsInstanceOf(()=>frame.eval(''), InternalError); }; -g.eval("new class foo extends null { constructor() { debugger; } }()"); +g.eval("new class foo extends null { constructor() { debugger; return {}; } }()"); `; if (classesEnabled()) diff --git a/js/src/tests/ecma_6/Class/derivedConstructorInlining.js b/js/src/tests/ecma_6/Class/derivedConstructorInlining.js new file mode 100644 index 0000000000..e0052305bc --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorInlining.js @@ -0,0 +1,19 @@ +// Since we (for now!) can't emit jitcode for derived class statements. Make +// sure we can corectly invoke derived class constructors. + +class foo extends null { + constructor() { + // Anything that tests |this| should throw, so just let it run off the + // end. + } +} + +function intermediate() { + new foo(); +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(intermediate, "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/derivedConstructorReturnPrimitive.js b/js/src/tests/ecma_6/Class/derivedConstructorReturnPrimitive.js new file mode 100644 index 0000000000..8b1594fa81 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorReturnPrimitive.js @@ -0,0 +1,15 @@ +class foo extends null { + constructor() { + // Returning a primitive is a TypeError in derived constructors. This + // ensures that super() can take the return value directly, without + // checking it. Use |null| here, as a tricky check to make sure we + // didn't lump it in with the object check, somehow. + return null; + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "return"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/derivedConstructorTDZExplicitThis.js b/js/src/tests/ecma_6/Class/derivedConstructorTDZExplicitThis.js new file mode 100644 index 0000000000..8ef9e48eea --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZExplicitThis.js @@ -0,0 +1,18 @@ +function pleaseRunMyCode() { } + +class foo extends null { + constructor() { + // Just bareword |this| is DCEd by the BytecodeEmitter. Your guess as + // to why we think this is a good idea is as good as mine. In order to + // combat this inanity, make it a function arg, so we have to compute + // it. + pleaseRunMyCode(this); + assertEq(false, true); + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/derivedConstructorTDZOffEdge.js b/js/src/tests/ecma_6/Class/derivedConstructorTDZOffEdge.js new file mode 100644 index 0000000000..145b12d2a1 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZOffEdge.js @@ -0,0 +1,11 @@ +class foo extends null { + constructor() { + // Let it fall off the edge and throw. + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnObject.js b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnObject.js new file mode 100644 index 0000000000..48206a881e --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnObject.js @@ -0,0 +1,13 @@ +class foo extends null { + constructor() { + // If you return an object, we don't care that |this| went + // uninitialized + return {}; + } +} + +for (let i = 0; i < 1100; i++) + new foo(); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnUndefined.js b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnUndefined.js new file mode 100644 index 0000000000..ab31b7aedd --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnUndefined.js @@ -0,0 +1,13 @@ +class foo extends null { + constructor() { + // Explicit returns of undefined should act the same as falling off the + // end of the function. That is to say, they should throw. + return undefined; + } +} + +for (let i = 0; i < 1100; i++) + assertThrownErrorContains(() => new foo(), "|this|"); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallBadDynamicSuperClass.js b/js/src/tests/ecma_6/Class/superCallBadDynamicSuperClass.js new file mode 100644 index 0000000000..1792434538 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallBadDynamicSuperClass.js @@ -0,0 +1,19 @@ +var test = ` + +class base { constructor() { } } + +class inst extends base { constructor() { super(); } } +Object.setPrototypeOf(inst, Math.sin); +assertThrowsInstanceOf(() => new inst(), TypeError); + +class defaultInst extends base { } +Object.setPrototypeOf(inst, Math.sin); +assertThrowsInstanceOf(() => new inst(), TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallBadNewTargetPrototype.js b/js/src/tests/ecma_6/Class/superCallBadNewTargetPrototype.js new file mode 100644 index 0000000000..cc79da2a1a --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallBadNewTargetPrototype.js @@ -0,0 +1,32 @@ +var test = ` + +class base { constructor() { } } + +// lies and the lying liars who tell them +function lies() { } +lies.prototype = 4; + +assertThrowsInstanceOf(()=>Reflect.consruct(base, [], lies), TypeError); + +// lie a slightly different way +function get(target, property, receiver) { + if (property === "prototype") + return 42; + return Reflect.get(target, property, receiver); +} + +class inst extends base { + constructor() { super(); } +} +assertThrowsInstanceOf(()=>new new Proxy(inst, {get})(), TypeError); + +class defaultInst extends base {} +assertThrowsInstanceOf(()=>new new Proxy(defaultInst, {get})(), TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallBaseInvoked.js b/js/src/tests/ecma_6/Class/superCallBaseInvoked.js new file mode 100644 index 0000000000..807b132000 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallBaseInvoked.js @@ -0,0 +1,64 @@ +var test = ` + +function testBase(base) { + class instance extends base { + constructor(inst, one) { + super(inst, one); + } + } + + let inst = new instance(instance, 1); + assertEq(Object.getPrototypeOf(inst), instance.prototype); + assertEq(inst.calledBase, true); + + class defaultInstance extends base { } + let defInst = new defaultInstance(defaultInstance, 1); + assertEq(Object.getPrototypeOf(defInst), defaultInstance.prototype); + assertEq(defInst.calledBase, true); +} + +class base { + // Base class must be [[Construct]]ed, as you cannot [[Call]] a class + // constructor + constructor(nt, one) { + assertEq(new.target, nt); + + // Check argument ordering + assertEq(one, 1); + this.calledBase = true; + } +} + +testBase(base); +testBase(class extends base { + constructor(nt, one) { + // Every step of the way, new.target and args should be right + assertEq(new.target, nt); + assertEq(one, 1); + super(nt, one); + } + }); +function baseFunc(nt, one) { + assertEq(new.target, nt); + assertEq(one, 1); + this.calledBase = true; +} + +testBase(baseFunc); + +let handler = {}; +let p = new Proxy(baseFunc, handler); +testBase(p); + +handler.construct = (target, args, nt) => Reflect.construct(target, args, nt); +testBase(p); + +// Object will have to wait for fixed builtins. + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallIllegal.js b/js/src/tests/ecma_6/Class/superCallIllegal.js new file mode 100644 index 0000000000..8b7b36397d --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallIllegal.js @@ -0,0 +1,7 @@ +// super() invalid outside derived class constructors, including in dynamic +// functions and eval +assertThrowsInstanceOf(() => new Function("super();"), SyntaxError); +assertThrowsInstanceOf(() => eval("super()"), SyntaxError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallInvalidBase.js b/js/src/tests/ecma_6/Class/superCallInvalidBase.js new file mode 100644 index 0000000000..3f15ae6927 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallInvalidBase.js @@ -0,0 +1,17 @@ +var test = ` + +class instance extends null { + constructor() { super(); } +} + +assertThrowsInstanceOf(() => new instance(), TypeError); +assertThrowsInstanceOf(() => new class extends null { }(), TypeError); + + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallOrder.js b/js/src/tests/ecma_6/Class/superCallOrder.js new file mode 100644 index 0000000000..1c71785b38 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallOrder.js @@ -0,0 +1,33 @@ +var test = ` + +function base() { } + +class beforeSwizzle extends base { + constructor() { + super(Object.setPrototypeOf(beforeSwizzle, null)); + } +} + +new beforeSwizzle(); + +// Again, testing both dynamic prototype dispatch, and that we get the function +// before evaluating args +class beforeThrow extends base { + constructor() { + function thrower() { throw new Error(); } + super(thrower()); + } +} + +Object.setPrototypeOf(beforeThrow, Math.sin); + +// Will throw that Math.sin is not a constructor before evaluating the args +assertThrowsInstanceOf(() => new beforeThrow(), TypeError); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallProperBase.js b/js/src/tests/ecma_6/Class/superCallProperBase.js new file mode 100644 index 0000000000..262cdc5e83 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallProperBase.js @@ -0,0 +1,41 @@ +var test = ` + +class base1 { + constructor() { + this.base = 1; + } +} + +class base2 { + constructor() { + this.base = 2; + } +} + +class inst extends base1 { + constructor() { + super(); + } +} + +assertEq(new inst().base, 1); + +Object.setPrototypeOf(inst, base2); + +assertEq(new inst().base, 2); + +// Still works with default constructor + +class defaultInst extends base1 { } + +assertEq(new defaultInst().base, 1); +Object.setPrototypeOf(defaultInst, base2); +assertEq(new defaultInst().base, 2); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallSpreadCall.js b/js/src/tests/ecma_6/Class/superCallSpreadCall.js new file mode 100644 index 0000000000..e20f9627d6 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallSpreadCall.js @@ -0,0 +1,34 @@ +var test = ` + +class base { + constructor(a, b, c) { + assertEq(a, 1); + assertEq(b, 2); + assertEq(c, 3); + this.calledBase = true; + } +} + +class test extends base { + constructor(arr) { + super(...arr); + } +} + +assertEq(new test([1,2,3]).calledBase, true); + +class testRest extends base { + constructor(...args) { + super(...args); + } +} + +assertEq(new testRest(1,2,3).calledBase, true); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superCallThisInit.js b/js/src/tests/ecma_6/Class/superCallThisInit.js new file mode 100644 index 0000000000..b2442d91d0 --- /dev/null +++ b/js/src/tests/ecma_6/Class/superCallThisInit.js @@ -0,0 +1,52 @@ +var test = ` + +function base() { this.prop = 42; } + +class testInitialize extends base { + constructor() { + // A poor man's assertThrowsInstanceOf, as arrow functions are currently + // disabled in this context + try { + this; + throw new Error(); + } catch (e if e instanceof ReferenceError) { } + super(); + assertEq(this.prop, 42); + } +} +assertEq(new testInitialize().prop, 42); + +// super() twice is a no-go. +class willThrow extends base { + constructor() { + super(); + super(); + } +} +assertThrowsInstanceOf(()=>new willThrow(), ReferenceError); + +// This is determined at runtime, not the syntax level. +class willStillThrow extends base { + constructor() { + for (let i = 0; i < 3; i++) { + super(); + } + } +} +assertThrowsInstanceOf(()=>new willStillThrow(), ReferenceError); + +class canCatchThrow extends base { + constructor() { + super(); + try { super(); } catch(e) { } + } +} +assertEq(new canCatchThrow().prop, 42); + +`; + +if (classesEnabled()) + eval(test); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_6/Class/superPropBasicGetter.js b/js/src/tests/ecma_6/Class/superPropBasicGetter.js index e30c180540..88c9c64f35 100644 --- a/js/src/tests/ecma_6/Class/superPropBasicGetter.js +++ b/js/src/tests/ecma_6/Class/superPropBasicGetter.js @@ -13,7 +13,7 @@ class base { } class derived extends base { - constructor() {} + constructor() { super(); } get a() { return super.getValue(); } set a(v) { super.setValue(v); } diff --git a/js/src/tests/ecma_6/Class/superPropBasicNew.js b/js/src/tests/ecma_6/Class/superPropBasicNew.js index 84d2487269..e12cdea56d 100644 --- a/js/src/tests/ecma_6/Class/superPropBasicNew.js +++ b/js/src/tests/ecma_6/Class/superPropBasicNew.js @@ -4,12 +4,13 @@ class Base { constructor() {} } class Mid extends Base { - constructor() {} + constructor() { super(); } f() { return new super.constructor(); } } class Derived extends Mid { - constructor() {} + constructor() { super(); } } + let d = new Derived(); var df = d.f(); assertEq(df.constructor, Base); diff --git a/js/src/tests/ecma_6/Class/superPropChains.js b/js/src/tests/ecma_6/Class/superPropChains.js index 9b83a83567..8770e9eac3 100644 --- a/js/src/tests/ecma_6/Class/superPropChains.js +++ b/js/src/tests/ecma_6/Class/superPropChains.js @@ -9,7 +9,7 @@ class base { } class middle extends base { - constructor() { } + constructor() { super(); } testChain() { this.middleCalled = true; super.testChain(); @@ -17,7 +17,7 @@ class middle extends base { } class derived extends middle { - constructor() { } + constructor() { super(); } testChain() { super.testChain(); assertEq(this.middleCalled, true); @@ -32,7 +32,7 @@ function bootlegMiddle() { } bootlegMiddle.prototype = middle.prototype; new class extends bootlegMiddle { - constructor() { } + constructor() { super(); } testChain() { super.testChain(); assertEq(this.middleCalled, true); @@ -43,11 +43,11 @@ new class extends bootlegMiddle { // Now let's try out some "long" chains base.prototype.x = "yeehaw"; -let chain = class extends base { constructor() { } } +let chain = class extends base { constructor() { super(); } } const CHAIN_LENGTH = 100; for (let i = 0; i < CHAIN_LENGTH; i++) - chain = class extends chain { constructor() { } } + chain = class extends chain { constructor() { super(); } } // Now we poke the chain let inst = new chain(); diff --git a/js/src/tests/ecma_6/Class/superPropDelete.js b/js/src/tests/ecma_6/Class/superPropDelete.js index 24f709d3e2..68e1331365 100644 --- a/js/src/tests/ecma_6/Class/superPropDelete.js +++ b/js/src/tests/ecma_6/Class/superPropDelete.js @@ -8,7 +8,7 @@ class base { } class derived extends base { - constructor() { } + constructor() { super(); } testDeleteProp() { delete super.prop; } testDeleteElem() { let sideEffect = 0; diff --git a/js/src/tests/ecma_6/Class/superPropDerivedCalls.js b/js/src/tests/ecma_6/Class/superPropDerivedCalls.js index b5239b9650..3b0ef4def3 100644 --- a/js/src/tests/ecma_6/Class/superPropDerivedCalls.js +++ b/js/src/tests/ecma_6/Class/superPropDerivedCalls.js @@ -26,7 +26,7 @@ class base { } class derived extends base { - constructor() { } + constructor() { super(); } // |super| actually checks the chain, not |this| method() { throw "FAIL"; } diff --git a/js/src/tests/ecma_6/Class/superPropDestructuring.js b/js/src/tests/ecma_6/Class/superPropDestructuring.js index 418e1c71e7..f6bc0c5925 100644 --- a/js/src/tests/ecma_6/Class/superPropDestructuring.js +++ b/js/src/tests/ecma_6/Class/superPropDestructuring.js @@ -22,7 +22,7 @@ Object.defineProperty(base.prototype, "intendent", const testArr = [525600, "Fred"]; class derived extends base { - constructor() { } + constructor() { super(); } prepForTest() { seenValues = []; } testAsserts() { assertDeepEq(seenValues, testArr); } testProps() { diff --git a/js/src/tests/ecma_6/Class/superPropHomeObject.js b/js/src/tests/ecma_6/Class/superPropHomeObject.js index 514f388db4..6fee180701 100644 --- a/js/src/tests/ecma_6/Class/superPropHomeObject.js +++ b/js/src/tests/ecma_6/Class/superPropHomeObject.js @@ -14,7 +14,7 @@ class base { } class derived extends base { - constructor() { } + constructor() { super(); } test(expected) { super.test(expected); } testArrow() { return (() => super.test(this)); } ["testCPN"](expected) { super.test(expected); } @@ -55,7 +55,7 @@ class base2 { let animals = []; for (let exprBase of [base1, base2]) new class extends exprBase { - constructor() { } + constructor() { super(); } test() { animals.push(super["test"]()); } }().test(); assertDeepEq(animals, ["llama", "alpaca"]); diff --git a/js/src/tests/ecma_6/Class/superPropOrdering.js b/js/src/tests/ecma_6/Class/superPropOrdering.js index 75f43daced..6898190202 100644 --- a/js/src/tests/ecma_6/Class/superPropOrdering.js +++ b/js/src/tests/ecma_6/Class/superPropOrdering.js @@ -6,16 +6,16 @@ class base { } class derived extends base { - constructor() { this.methodCalled = 0; } + constructor() { super(); this.methodCalled = 0; } // Test orderings of various evaluations relative to the superbase - + // Unlike in regular element evaluation, the propVal is evaluated before // checking the starting object ([[HomeObject]].[[Prototype]]) testElem() { super[ruin()]; } - + // The starting object for looking up super.method is determined before - // ruin() is called. + // ruin() is called. testProp() { super.method(ruin()); } // The entire super.method property lookup has concluded before the args @@ -71,7 +71,6 @@ function reset() { } let instance = new derived(); - assertThrowsInstanceOf(() => instance.testElem(), TypeError); reset(); @@ -91,6 +90,7 @@ instance.testAssignElemPropValChange(); instance.testAssignProp(); instance.testCompoundAssignProp(); + `; if (classesEnabled()) diff --git a/js/src/tests/ecma_6/Class/superPropProtoChanges.js b/js/src/tests/ecma_6/Class/superPropProtoChanges.js index 7cb457f6cd..bca849ee11 100644 --- a/js/src/tests/ecma_6/Class/superPropProtoChanges.js +++ b/js/src/tests/ecma_6/Class/superPropProtoChanges.js @@ -10,7 +10,7 @@ class base { let standin = { test() { return true; } }; class derived extends base { - constructor() { } + constructor() { super(); } test() { assertEq(super.test(), false); Object.setPrototypeOf(derived.prototype, standin); diff --git a/js/src/tests/ecma_6/Class/superPropProxies.js b/js/src/tests/ecma_6/Class/superPropProxies.js index 3d5669d781..926205600d 100644 --- a/js/src/tests/ecma_6/Class/superPropProxies.js +++ b/js/src/tests/ecma_6/Class/superPropProxies.js @@ -11,7 +11,7 @@ let midStaticHandler = { }; let getterCalled, setterCalled; class mid extends new Proxy(base, midStaticHandler) { - constructor() { } + constructor() { super(); } testSuperInProxy() { super.prop = "looking"; assertEq(setterCalled, true); @@ -21,7 +21,7 @@ class mid extends new Proxy(base, midStaticHandler) { } class child extends mid { - constructor() { } + constructor() { super(); } static testStaticLookups() { // This funtion is called more than once. this.called = false; @@ -30,7 +30,6 @@ class child extends mid { } } - let midInstance = new mid(); // Make sure proxies are searched properly on the prototype chain diff --git a/js/src/tests/ecma_6/Class/superPropSkips.js b/js/src/tests/ecma_6/Class/superPropSkips.js index 05d659a569..408e53b0b4 100644 --- a/js/src/tests/ecma_6/Class/superPropSkips.js +++ b/js/src/tests/ecma_6/Class/superPropSkips.js @@ -9,7 +9,7 @@ class base { } class derived extends base { - constructor() { this.prop = "flamingo"; } + constructor() { super(); this.prop = "flamingo"; } toString() { throw "No!"; } @@ -37,6 +37,7 @@ class derived extends base { } Object.defineProperty(derived.prototype, "nonWritableProp", { writable: false, value: "pony" }); + let instance = new derived(); instance.testSkipGet(); instance.testSkipDerivedOverrides(); diff --git a/js/src/tests/ecma_6/Proxy/revoke-as-side-effect.js b/js/src/tests/ecma_6/Proxy/revoke-as-side-effect.js index 6ff5aaf5b4..21f8f8e20d 100644 --- a/js/src/tests/ecma_6/Proxy/revoke-as-side-effect.js +++ b/js/src/tests/ecma_6/Proxy/revoke-as-side-effect.js @@ -71,8 +71,13 @@ assertEq(Object.getOwnPropertyNames(createProxy({a: 5})).length, 1); assertEq(createProxy(function() { return "ok" })(), "ok"); // [[Construct]] -// This should throw per bug 1141865. -assertEq(new (createProxy(function(){ return obj; })), obj); +// This throws because after the "construct" trap on the proxy is consulted, +// OrdinaryCreateFromConstructor (called because the |q| function's +// [[ConstructorKind]] is "base" per FunctionAllocate) accesses +// |new.target.prototype| to create the |this| for the construct operation, that +// would be returned if |return obj;| didn't override it. +assertThrowsInstanceOf(() => new (createProxy(function q(){ return obj; })), + TypeError); if (typeof reportCompare === "function") reportCompare(true, true); diff --git a/js/src/tests/ecma_6/Reflect/construct.js b/js/src/tests/ecma_6/Reflect/construct.js index 4be216d36f..870dcb3302 100644 --- a/js/src/tests/ecma_6/Reflect/construct.js +++ b/js/src/tests/ecma_6/Reflect/construct.js @@ -48,20 +48,15 @@ if (classesEnabled()) { this.newTarget = new.target; } } - //class Derived extends Base { - // constructor(...args) { super(...args); } - //} + class Derived extends Base { + constructor(...args) { super(...args); } + } assertDeepEq(Reflect.construct(Base, []), new Base); - //assertDeepEq(Reflect.construct(Derived, [7]), new Derived(7)); - //g = Derived.bind(null, "q"); - //assertDeepEq(Reflect.construct(g, [8, 9]), new g(8, 9)); + assertDeepEq(Reflect.construct(Derived, [7]), new Derived(7)); + g = Derived.bind(null, "q"); + assertDeepEq(Reflect.construct(g, [8, 9]), new g(8, 9)); }`); - - if (classesEnabled("class X extends Y { constructor() { super(); } }")) { - throw new Error("Congratulations on implementing super()! " + - "Please uncomment the Derived tests in this file!"); - } } // Cross-compartment wrappers: diff --git a/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js b/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js index 2bbae71e1e..f497f81801 100644 --- a/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js +++ b/js/src/tests/js1_8_5/reflect-parse/PatternBuilders.js @@ -207,6 +207,9 @@ function newExpr(callee, args) { function callExpr(callee, args) { return Pattern({ type: "CallExpression", callee: callee, arguments: args }); } +function superCallExpr(args) { + return callExpr({ type: "Super" }, args); +} function arrExpr(elts) { return Pattern({ type: "ArrayExpression", elements: elts }); } diff --git a/js/src/tests/js1_8_5/reflect-parse/classes.js b/js/src/tests/js1_8_5/reflect-parse/classes.js index 615a36673f..44fcd4081c 100644 --- a/js/src/tests/js1_8_5/reflect-parse/classes.js +++ b/js/src/tests/js1_8_5/reflect-parse/classes.js @@ -25,9 +25,9 @@ function testClasses() { methodFun(id, kind, generator, args), kind, isStatic); } - function ctorWithName(id) { + function ctorWithName(id, body = []) { return classMethod(ident("constructor"), - methodFun(id, "method", false, []), + methodFun(id, "method", false, [], body), "method", false); } function emptyCPNMethod(id, isStatic) { @@ -40,14 +40,15 @@ function testClasses() { let template = classExpr(name, heritage, methods); assertExpr("(" + str + ")", template); } + // FunctionExpression of constructor has class name as its id. // FIXME: Implement ES6 function "name" property semantics (bug 883377). let ctorPlaceholder = {}; - function assertClass(str, methods, heritage=null) { + function assertClass(str, methods, heritage=null, constructorBody=[]) { let namelessStr = str.replace("NAME", ""); let namedStr = str.replace("NAME", "Foo"); - let namedCtor = ctorWithName("Foo"); - let namelessCtor = ctorWithName(null); + let namedCtor = ctorWithName("Foo", constructorBody); + let namelessCtor = ctorWithName(null, constructorBody); let namelessMethods = methods.map(x => x == ctorPlaceholder ? namelessCtor : x); let namedMethods = methods.map(x => x == ctorPlaceholder ? namedCtor : x); assertClassExpr(namelessStr, namelessMethods, heritage); @@ -126,8 +127,9 @@ function testClasses() { [ctorPlaceholder, emptyCPNMethod("prototype", true)]); /* Constructor */ - // Currently, we do not allow default constructors - assertClassError("class NAME { }", TypeError); + // Allow default constructors + assertClass("class NAME { }", []); + assertClass("class NAME extends null { }", [], lit(null)); // For now, disallow arrow functions in derived class constructors assertClassError("class NAME extends null { constructor() { (() => 0); }", InternalError); @@ -437,6 +439,42 @@ function testClasses() { assertError("{ foo() { super } }", SyntaxError); assertClassError("class NAME { constructor() { super; } }", SyntaxError); + /* SuperCall */ + + // SuperCall is invalid outside derived class constructors. + assertError("super()", SyntaxError); + assertError("(function() { super(); })", SyntaxError); + + // SuperCall is invalid in generator comprehensions, even inside derived + // class constructors + assertError("(super() for (x in y))", SyntaxError); + assertClassError("class NAME { constructor() { (super() for (x in y))", SyntaxError); + + + // Even in class constructors + assertClassError("class NAME { constructor() { super(); } }", SyntaxError); + + function superConstructor(args) { + return classMethod(ident("constructor"), + methodFun("NAME", "method", false, + [], [exprStmt(superCallExpr(args))]), + "method", false); + } + + function superCallBody(args) { + return [exprStmt(superCallExpr(args))]; + } + + // SuperCall works with various argument configurations. + assertClass("class NAME extends null { constructor() { super() } }", + [ctorPlaceholder], lit(null), superCallBody([])); + assertClass("class NAME extends null { constructor() { super(1) } }", + [ctorPlaceholder], lit(null), superCallBody([lit(1)])); + assertClass("class NAME extends null { constructor() { super(1, a) } }", + [ctorPlaceholder], lit(null), superCallBody([lit(1), ident("a")])); + assertClass("class NAME extends null { constructor() { super(...[]) } }", + [ctorPlaceholder], lit(null), superCallBody([spread(arrExpr([]))])); + /* EOF */ // Clipped classes should throw a syntax error assertClassError("class NAME {", SyntaxError); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index bf4102a2c6..63fa88d953 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -41,6 +41,7 @@ #include "vm/GeneratorObject.h" #include "vm/Opcodes.h" #include "vm/Shape.h" +#include "vm/Stopwatch.h" #include "vm/TraceLogging.h" #include "jsatominlines.h" @@ -55,11 +56,6 @@ #include "vm/ScopeObject-inl.h" #include "vm/Stack-inl.h" -#if defined(XP_WIN) -#include -#include -#endif // defined(XP_WIN) - using namespace js; using namespace js::gc; @@ -312,6 +308,17 @@ SetPropertyOperation(JSContext* cx, JSOp op, HandleValue lval, HandleId id, Hand result.checkStrictErrorOrWarning(cx, obj, id, op == JSOP_STRICTSETPROP); } +static JSFunction* +MakeDefaultConstructor(JSContext* cx, JSOp op, JSAtom* atom, HandleObject proto) +{ + bool derived = op == JSOP_DERIVEDCONSTRUCTOR; + MOZ_ASSERT(derived == !!proto); + + RootedAtom name(cx, atom == cx->names().empty ? nullptr : atom); + JSNative native = derived ? DefaultDerivedClassConstructor : DefaultClassConstructor; + return NewFunctionWithProto(cx, native, 0, JSFunction::NATIVE_CLASS_CTOR, nullptr, name, proto); +} + bool js::ReportIsNotFunction(JSContext* cx, HandleValue v, int numToSkip, MaybeConstruct construct) { @@ -340,11 +347,17 @@ RunState::maybeCreateThisForConstructor(JSContext* cx) InvokeState& invoke = *asInvoke(); if (invoke.constructing() && invoke.args().thisv().isPrimitive()) { RootedObject callee(cx, &invoke.args().callee()); - NewObjectKind newKind = invoke.createSingleton() ? SingletonObject : GenericObject; - JSObject* obj = CreateThisForFunction(cx, callee, newKind); - if (!obj) - return false; - invoke.args().setThis(ObjectValue(*obj)); + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(callee->as().isClassConstructor()); + invoke.args().setThis(MagicValue(JS_UNINITIALIZED_LEXICAL)); + } else { + RootedObject newTarget(cx, &invoke.args().newTarget().toObject()); + NewObjectKind newKind = invoke.createSingleton() ? SingletonObject : GenericObject; + JSObject* obj = CreateThisForFunction(cx, callee, newTarget, newKind); + if (!obj) + return false; + invoke.args().setThis(ObjectValue(*obj)); + } } } return true; @@ -365,295 +378,6 @@ ExecuteState::pushInterpreterFrame(JSContext* cx) return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, newTargetValue_, scopeChain_, type_, evalInFrame_); } -namespace js { -// Implementation of per-performance group performance measurement. -// -// -// All mutable state is stored in `Runtime::stopwatch` (per-process -// performance stats and logistics) and in `PerformanceGroup` (per -// group performance stats). -class MOZ_RAII AutoStopwatch final -{ - // The context with which this object was initialized. - // Non-null. - JSContext* const cx_; - - // An indication of the number of times we have entered the event - // loop. Used only for comparison. - uint64_t iteration_; - - // `true` if we are monitoring jank, `false` otherwise. - bool isMonitoringJank_; - // `true` if we are monitoring CPOW, `false` otherwise. - bool isMonitoringCPOW_; - - // Timestamps captured while starting the stopwatch. - uint64_t cyclesStart_; - uint64_t CPOWTimeStart_; - - // The CPU on which we started the measure. Defined only - // if `isMonitoringJank_` is `true`. -#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA - struct cpuid_t { - WORD group_; - BYTE number_; - cpuid_t(WORD group, BYTE number) - : group_(group), - number_(number) - { } - cpuid_t() - : group_(0), - number_(0) - { } - }; -#elif defined(XP_LINUX) - typedef int cpuid_t; -#else - typedef struct {} cpuid_t; -#endif // defined(XP_WIN) || defined(XP_LINUX) - - cpuid_t cpuStart_; - - // The performance group shared by this compartment and possibly - // others, or `nullptr` if another AutoStopwatch is already in - // charge of monitoring that group. - RefPtr sharedGroup_; - - // The toplevel group, representing the entire process, or `nullptr` - // if another AutoStopwatch is already in charge of monitoring that group. - RefPtr topGroup_; - - // The performance group specific to this compartment, or - // `nullptr` if another AutoStopwatch is already in charge of - // monitoring that group. - RefPtr ownGroup_; - - public: - // If the stopwatch is active, constructing an instance of - // AutoStopwatch causes it to become the current owner of the - // stopwatch. - // - // Previous owner is restored upon destruction. - explicit inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : cx_(cx) - , iteration_(0) - , isMonitoringJank_(false) - , isMonitoringCPOW_(false) - , cyclesStart_(0) - , CPOWTimeStart_(0) - { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - - JSCompartment* compartment = cx_->compartment(); - if (compartment->scheduledForDestruction) - return; - - JSRuntime* runtime = cx_->runtime(); - iteration_ = runtime->stopwatch.iteration(); - - sharedGroup_ = acquireGroup(compartment->performanceMonitoring.getSharedGroup(cx)); - if (sharedGroup_) - topGroup_ = acquireGroup(runtime->stopwatch.performance.getOwnGroup()); - - if (runtime->stopwatch.isMonitoringPerCompartment()) - ownGroup_ = acquireGroup(compartment->performanceMonitoring.getOwnGroup()); - - if (!sharedGroup_ && !ownGroup_) { - // We are not in charge of monitoring anything. - return; - } - - // Now that we are sure that JS code is being executed, - // initialize the stopwatch for this iteration, lazily. - runtime->stopwatch.start(); - enter(); - } - ~AutoStopwatch() - { - if (!sharedGroup_ && !ownGroup_) { - // We are not in charge of monitoring anything. - return; - } - - JSCompartment* compartment = cx_->compartment(); - if (compartment->scheduledForDestruction) - return; - - JSRuntime* runtime = cx_->runtime(); - if (iteration_ != runtime->stopwatch.iteration()) { - // We have entered a nested event loop at some point. - // Any information we may have is obsolete. - return; - } - - // Finish and commit measures - exit(); - - releaseGroup(sharedGroup_); - releaseGroup(topGroup_); - releaseGroup(ownGroup_); - } - private: - void enter() { - JSRuntime* runtime = cx_->runtime(); - - if (runtime->stopwatch.isMonitoringCPOW()) { - CPOWTimeStart_ = runtime->stopwatch.totalCPOWTime; - isMonitoringCPOW_ = true; - } - - if (runtime->stopwatch.isMonitoringJank()) { - cyclesStart_ = this->getCycles(); - cpuStart_ = this->getCPU(); - isMonitoringJank_ = true; - } - - } - - void exit() { - JSRuntime* runtime = cx_->runtime(); - - uint64_t cyclesDelta = 0; - if (isMonitoringJank_ && runtime->stopwatch.isMonitoringJank()) { - // We were monitoring jank when we entered and we still are. - - // If possible, discard results when we don't end on the - // same CPU as we started. Note that we can be - // rescheduled to another CPU beween `getCycles()` and - // `getCPU()`. We hope that this will happen rarely - // enough that the impact on our statistics will remain - // limited. - const cpuid_t cpuEnd = this->getCPU(); - if (isSameCPU(cpuStart_, cpuEnd)) { - const uint64_t cyclesEnd = getCycles(); - cyclesDelta = getDelta(cyclesEnd, cyclesStart_); - } -#if (defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA) || defined(XP_LINUX) - if (isSameCPU(cpuStart_, cpuEnd)) - runtime->stopwatch.testCpuRescheduling.stayed += 1; - else - runtime->stopwatch.testCpuRescheduling.moved += 1; -#endif // defined(XP_WIN) || defined(XP_LINUX) - } - - uint64_t CPOWTimeDelta = 0; - if (isMonitoringCPOW_ && runtime->stopwatch.isMonitoringCPOW()) { - // We were monitoring CPOW when we entered and we still are. - const uint64_t CPOWTimeEnd = runtime->stopwatch.totalCPOWTime; - CPOWTimeDelta = getDelta(CPOWTimeEnd, CPOWTimeStart_); - - } - addToGroups(cyclesDelta, CPOWTimeDelta); - } - - // Attempt to acquire a group - // If the group is `null` or if the group already has a stopwatch, - // do nothing and return `null`. - // Otherwise, bind the group to `this` for the current iteration - // and return `group`. - PerformanceGroup* acquireGroup(PerformanceGroup* group) { - if (!group) - return nullptr; - - if (group->hasStopwatch(iteration_)) - return nullptr; - - group->acquireStopwatch(iteration_, this); - return group; - } - - // Release a group. - // Noop if `group` is null or if `this` is not the stopwatch - // of `group` for the current iteration. - void releaseGroup(PerformanceGroup* group) { - if (group) - group->releaseStopwatch(iteration_, this); - } - - // Add recent changes to all the groups owned by this stopwatch. - // Mark the groups as changed recently. - void addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta) { - addToGroup(cyclesDelta, CPOWTimeDelta, sharedGroup_); - addToGroup(cyclesDelta, CPOWTimeDelta, topGroup_); - addToGroup(cyclesDelta, CPOWTimeDelta, ownGroup_); - } - - // Add recent changes to a single group. Mark the group as changed recently. - void addToGroup(uint64_t cyclesDelta, uint64_t CPOWTimeDelta, PerformanceGroup* group) { - if (!group) - return; - - MOZ_ASSERT(group->hasStopwatch(iteration_, this)); - - if (group->recentTicks == 0) { - // First time we meet this group during the tick, - // mark it as needing updates. - JSRuntime* runtime = cx_->runtime(); - runtime->stopwatch.addChangedGroup(group); - } - group->recentTicks++; - group->recentCycles += cyclesDelta; - group->recentCPOW += CPOWTimeDelta; - } - - // Perform a subtraction for a quantity that should be monotonic - // but is not guaranteed to be so. - // - // If `start <= end`, return `end - start`. - // Otherwise, return `0`. - uint64_t getDelta(const uint64_t end, const uint64_t start) const - { - if (start >= end) - return 0; - return end - start; - } - - // Return the value of the Timestamp Counter, as provided by the CPU. - // 0 on platforms for which we do not have access to a Timestamp Counter. - uint64_t getCycles() const - { -#if defined(MOZ_HAVE_RDTSC) - return ReadTimestampCounter(); -#else - return 0; -#endif // defined(MOZ_HAVE_RDTSC) - } - - - // Return the identifier of the current CPU, on platforms for which we have - // access to the current CPU. - cpuid_t inline getCPU() const - { -#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA - PROCESSOR_NUMBER proc; - GetCurrentProcessorNumberEx(&proc); - - cpuid_t result(proc.Group, proc.Number); - return result; -#elif defined(XP_LINUX) - return sched_getcpu(); -#else - return {}; -#endif // defined(XP_WIN) || defined(XP_LINUX) - } - - // Compare two CPU identifiers. - bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const - { -#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA - return a.group_ == b.group_ && a.number_ == b.number_; -#elif defined(XP_LINUX) - return a == b; -#else - return true; -#endif - } - private: - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; -}; - -} // namespace js - // MSVC with PGO inlines a lot of functions in RunScript, resulting in large // stack frames and stack overflow issues, see bug 1167883. Turn off PGO to // avoid this. @@ -862,9 +586,9 @@ StackCheckIsConstructorCalleeNewTarget(JSContext* cx, HandleValue callee, Handle return false; } - // The new.target for a stack construction attempt is just the callee: no - // need to check that it's a constructor. - MOZ_ASSERT(&callee.toObject() == &newTarget.toObject()); + // The new.target has already been vetted by previous calls, or is the callee. + // We can just assert that it's a constructor. + MOZ_ASSERT(IsConstructor(newTarget)); return true; } @@ -1775,6 +1499,34 @@ SetObjectElementOperation(JSContext* cx, HandleObject obj, HandleId id, HandleVa result.checkStrictErrorOrWarning(cx, obj, id, strict); } +/* + * Get the innermost enclosing function that has a 'this' binding. + * + * Implements ES6 12.3.5.2 GetSuperConstructor() steps 1-3, including + * the loop in ES6 8.3.2 GetThisEnvironment(). Our implementation of + * ES6 12.3.5.3 MakeSuperPropertyReference() also uses this code. + */ +static JSFunction& +GetSuperEnvFunction(JSContext *cx, InterpreterRegs& regs) +{ + ScopeIter si(cx, regs.fp()->scopeChain(), regs.fp()->script()->innermostStaticScope(regs.pc)); + for (; !si.done(); ++si) { + if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) { + JSFunction& callee = si.scope().as().callee(); + + // Arrow functions don't have the information we're looking for, + // their enclosing scopes do. Nevertheless, they might have call + // objects. Skip them to find what we came for. + if (callee.isArrow()) + continue; + + return callee; + } + } + MOZ_CRASH("unexpected scope chain for GetSuperEnvFunction"); +} + + /* * As an optimization, the interpreter creates a handful of reserved Rooted * variables at the beginning, thus inserting them into the Rooted list once @@ -2084,12 +1836,6 @@ CASE(JSOP_NOP) CASE(JSOP_UNUSED2) CASE(JSOP_UNUSED14) CASE(JSOP_BACKPATCH) -CASE(JSOP_UNUSED163) -CASE(JSOP_UNUSED164) -CASE(JSOP_UNUSED165) -CASE(JSOP_UNUSED166) -CASE(JSOP_UNUSED167) -CASE(JSOP_UNUSED168) CASE(JSOP_UNUSED169) CASE(JSOP_UNUSED170) CASE(JSOP_UNUSED171) @@ -2228,8 +1974,12 @@ CASE(JSOP_RETRVAL) */ CHECK_BRANCH(); + if (!REGS.fp()->checkReturn(cx)) + goto error; + successful_return_continuation: interpReturnOK = true; + return_continuation: if (activation.entryFrame() != REGS.fp()) { // Stop the engine. (No details about which engine exactly, could be @@ -2806,6 +2556,8 @@ END_CASE(JSOP_VOID) CASE(JSOP_THIS) if (!ComputeThis(cx, REGS.fp())) goto error; + if (!REGS.fp()->checkThis(cx)) + goto error; PUSH_COPY(REGS.fp()->thisValue()); END_CASE(JSOP_THIS) @@ -3027,6 +2779,7 @@ END_CASE(JSOP_EVAL) CASE(JSOP_SPREADNEW) CASE(JSOP_SPREADCALL) +CASE(JSOP_SPREADSUPERCALL) if (REGS.fp()->hasPushedSPSFrame()) cx->runtime()->spsProfiler.updatePC(script, REGS.pc); /* FALL THROUGH */ @@ -3036,7 +2789,7 @@ CASE(JSOP_STRICTSPREADEVAL) { static_assert(JSOP_SPREADEVAL_LENGTH == JSOP_STRICTSPREADEVAL_LENGTH, "spreadeval and strictspreadeval must be the same size"); - bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW; + bool construct = JSOp(*REGS.pc) == JSOP_SPREADNEW || JSOp(*REGS.pc) == JSOP_SPREADSUPERCALL;; MOZ_ASSERT(REGS.stackDepth() >= 3u + construct); @@ -3068,12 +2821,13 @@ CASE(JSOP_FUNAPPLY) CASE(JSOP_NEW) CASE(JSOP_CALL) +CASE(JSOP_SUPERCALL) CASE(JSOP_FUNCALL) { if (REGS.fp()->hasPushedSPSFrame()) cx->runtime()->spsProfiler.updatePC(script, REGS.pc); - bool construct = (*REGS.pc == JSOP_NEW); + bool construct = (*REGS.pc == JSOP_NEW || *REGS.pc == JSOP_SUPERCALL); unsigned argStackSlots = GET_ARGC(REGS.pc) + construct; MOZ_ASSERT(REGS.stackDepth() >= 2u + GET_ARGC(REGS.pc)); @@ -4096,37 +3850,22 @@ END_CASE(JSOP_INITHOMEOBJECT) CASE(JSOP_SUPERBASE) { - ScopeIter si(cx, REGS.fp()->scopeChain(), REGS.fp()->script()->innermostStaticScope(REGS.pc)); - for (; !si.done(); ++si) { - if (si.hasSyntacticScopeObject() && si.type() == ScopeIter::Call) { - JSFunction& callee = si.scope().as().callee(); + JSFunction& superEnvFunc = GetSuperEnvFunction(cx, REGS); + MOZ_ASSERT(superEnvFunc.allowSuperProperty()); + MOZ_ASSERT(superEnvFunc.nonLazyScript()->needsHomeObject()); + const Value& homeObjVal = superEnvFunc.getExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT); - // Arrow functions don't have the information we're looking for, - // their enclosing scopes do. Nevertheless, they might have call - // objects. Skip them to find what we came for. - if (callee.isArrow()) - continue; + ReservedRooted homeObj(&rootObject0, &homeObjVal.toObject()); + ReservedRooted superBase(&rootObject1); + if (!GetPrototype(cx, homeObj, &superBase)) + goto error; - MOZ_ASSERT(callee.allowSuperProperty()); - MOZ_ASSERT(callee.nonLazyScript()->needsHomeObject()); - const Value& homeObjVal = callee.getExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT); - - ReservedRooted homeObj(&rootObject0, &homeObjVal.toObject()); - ReservedRooted superBase(&rootObject1); - if (!GetPrototype(cx, homeObj, &superBase)) - goto error; - - if (!superBase) { - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, - "null", "object"); - goto error; - } - PUSH_OBJECT(*superBase); - break; - } + if (!superBase) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, + "null", "object"); + goto error; } - if (si.done()) - MOZ_CRASH("Unexpected scope chain in superbase"); + PUSH_OBJECT(*superBase); } END_CASE(JSOP_SUPERBASE) @@ -4135,6 +3874,72 @@ CASE(JSOP_NEWTARGET) MOZ_ASSERT(REGS.sp[-1].isObject() || REGS.sp[-1].isUndefined()); END_CASE(JSOP_NEWTARGET) +CASE(JSOP_SUPERFUN) +{ + ReservedRooted superEnvFunc(&rootObject0, &GetSuperEnvFunction(cx, REGS)); + MOZ_ASSERT(superEnvFunc->as().isClassConstructor()); + MOZ_ASSERT(superEnvFunc->as().nonLazyScript()->isDerivedClassConstructor()); + + ReservedRooted superFun(&rootObject1); + + if (!GetPrototype(cx, superEnvFunc, &superFun)) + goto error; + + ReservedRooted superFunVal(&rootValue0, UndefinedValue()); + if (!superFun) + superFunVal = NullValue(); + else if (!superFun->isConstructor()) + superFunVal = ObjectValue(*superFun); + + if (superFunVal.isObjectOrNull()) { + ReportIsNotFunction(cx, superFunVal, JSDVG_IGNORE_STACK, CONSTRUCT); + goto error; + } + + PUSH_OBJECT(*superFun); +} +END_CASE(JSOP_SUPERFUN) + +CASE(JSOP_SETTHIS) +{ + MOZ_ASSERT(REGS.fp()->isNonEvalFunctionFrame()); + MOZ_ASSERT(REGS.fp()->script()->isDerivedClassConstructor()); + MOZ_ASSERT(REGS.fp()->callee().isClassConstructor()); + + if (!REGS.fp()->thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_REINIT_THIS); + goto error; + } + + ReservedRooted thisv(&rootObject0, ®S.sp[-1].toObject()); + REGS.fp()->setDerivedConstructorThis(thisv); +} +END_CASE(JSOP_SETTHIS) + +CASE(JSOP_DERIVEDCONSTRUCTOR) +{ + MOZ_ASSERT(REGS.sp[-1].isObject()); + ReservedRooted proto(&rootObject0, ®S.sp[-1].toObject()); + + JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc), + proto); + if (!constructor) + goto error; + + REGS.sp[-1].setObject(*constructor); +} +END_CASE(JSOP_DERIVEDCONSTRUCTOR) + +CASE(JSOP_CLASSCONSTRUCTOR) +{ + JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc), + nullptr); + if (!constructor) + goto error; + PUSH_OBJECT(*constructor); +} +END_CASE(JSOP_CLASSCONSTRUCTOR) + DEFAULT() { char numBuf[12]; @@ -4745,7 +4550,7 @@ js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, Hand RootedArrayObject aobj(cx, &arr.toObject().as()); uint32_t length = aobj->length(); JSOp op = JSOp(*pc); - bool constructing = op == JSOP_SPREADNEW; + bool constructing = op == JSOP_SPREADNEW || op == JSOP_SPREADSUPERCALL; if (length > ARGS_LENGTH_MAX) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, @@ -4763,7 +4568,7 @@ js::SpreadCallOperation(JSContext* cx, HandleScript script, jsbytecode* pc, Hand MOZ_ASSERT(!aobj->getDenseElement(i).isMagic()); #endif - if (op == JSOP_SPREADNEW) { + if (constructing) { if (!StackCheckIsConstructorCalleeNewTarget(cx, callee, newTarget)) return false; @@ -5006,6 +4811,63 @@ js::ReportUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* p ReportUninitializedLexical(cx, name); } +bool +js::DefaultClassConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.isConstructing()) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CALL_CLASS_CONSTRUCTOR); + return false; + } + + RootedObject newTarget(cx, &args.newTarget().toObject()); + RootedValue protoVal(cx); + + if (!GetProperty(cx, newTarget, newTarget, cx->names().prototype, &protoVal)) + return false; + + RootedObject proto(cx); + if (!protoVal.isObject()) { + if (!GetBuiltinPrototype(cx, JSProto_Object, &proto)) + return false; + } else { + proto = &protoVal.toObject(); + } + + JSObject* obj = NewObjectWithGivenProto(cx, &PlainObject::class_, proto); + if (!obj) + return false; + + args.rval().set(ObjectValue(*obj)); + return true; +} + +bool +js::DefaultDerivedClassConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.isConstructing()) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_CANT_CALL_CLASS_CONSTRUCTOR); + return false; + } + + RootedObject fun(cx, &args.callee()); + RootedObject superFun(cx); + if (!GetPrototype(cx, fun, &superFun)) + return false; + + RootedValue fval(cx, ObjectOrNullValue(superFun)); + if (!IsConstructor(fval)) { + ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, fval, nullptr); + return false; + } + + ConstructArgs constArgs(cx); + if (!FillArgumentsFromArraylike(cx, constArgs, args)) + return false; + return Construct(cx, fval, constArgs, args.newTarget(), args.rval()); +} + void js::ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, frontend::Definition::Kind declKind) @@ -5023,3 +4885,23 @@ js::ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, kindStr, printable.ptr()); } } + +bool +js::ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame) +{ + RootedFunction fun(cx, frame.callee()); + + MOZ_ASSERT(fun->isClassConstructor()); + MOZ_ASSERT(fun->nonLazyScript()->isDerivedClassConstructor()); + + const char* name = "anonymous"; + JSAutoByteString str; + if (fun->atom()) { + if (!AtomToPrintableString(cx, fun->atom(), &str)) + return false; + name = str.ptr(); + } + + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_THIS, name); + return false; +} diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 57c86f9a0b..5e0944b263 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -492,6 +492,14 @@ ReportUninitializedLexical(JSContext* cx, HandleScript script, jsbytecode* pc); void ReportRuntimeRedeclaration(JSContext* cx, HandlePropertyName name, frontend::Definition::Kind declKind); +bool +ThrowUninitializedThis(JSContext* cx, AbstractFramePtr frame); + +bool +DefaultClassConstructor(JSContext* cx, unsigned argc, Value* vp); + +bool +DefaultDerivedClassConstructor(JSContext* cx, unsigned argc, Value* vp); } /* namespace js */ diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 4ebf94f645..256453df09 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -458,13 +458,12 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp, TaggedProto proto, JSObject* associated) { MOZ_ASSERT_IF(associated, proto.isObject()); - MOZ_ASSERT_IF(associated, associated->is() || associated->is()); MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject())); // A null lookup clasp is used for 'new' groups with an associated // function. The group starts out as a plain object but might mutate into an // unboxed plain object. - MOZ_ASSERT(!clasp == (associated && associated->is())); + MOZ_ASSERT_IF(!clasp, !!associated); AutoEnterAnalysis enter(cx); @@ -480,16 +479,21 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp, } } - if (associated && associated->is()) { + if (associated && !associated->is()) { MOZ_ASSERT(!clasp); + if (associated->is()) { - // Canonicalize new functions to use the original one associated with its script. - associated = associated->as().maybeCanonicalFunction(); + // Canonicalize new functions to use the original one associated with its script. + associated = associated->as().maybeCanonicalFunction(); - // If we have previously cleared the 'new' script information for this - // function, don't try to construct another one. - if (associated && associated->wasNewScriptCleared()) + // If we have previously cleared the 'new' script information for this + // function, don't try to construct another one. + if (associated && associated->wasNewScriptCleared()) + associated = nullptr; + + } else { associated = nullptr; + } if (!associated) clasp = &PlainObject::class_; @@ -1323,7 +1327,7 @@ ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_ RootedPlainObject obj(cx, NewObjectWithGroup(cx, group, allocKind, newKind)); - if (!obj->setLastProperty(cx, shape)) + if (!obj || !obj->setLastProperty(cx, shape)) return nullptr; for (size_t i = 0; i < nproperties; i++) @@ -1447,9 +1451,13 @@ ObjectGroupCompartment::replaceAllocationSiteGroup(JSScript* script, jsbytecode* key.kind = kind; AllocationSiteTable::Ptr p = allocationSiteTable->lookup(key); - MOZ_ASSERT(p); + MOZ_RELEASE_ASSERT(p); allocationSiteTable->remove(p); - allocationSiteTable->putNew(key, group); + { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!allocationSiteTable->putNew(key, group)) + oomUnsafe.crash("Inconsistent object table"); + } } /* static */ ObjectGroup* @@ -1584,7 +1592,7 @@ ObjectGroupCompartment::removeDefaultNewGroup(const Class* clasp, TaggedProto pr JSObject* associated) { NewTable::Ptr p = defaultNewTable->lookup(NewEntry::Lookup(clasp, proto, associated)); - MOZ_ASSERT(p); + MOZ_RELEASE_ASSERT(p); defaultNewTable->remove(p); } @@ -1596,9 +1604,13 @@ ObjectGroupCompartment::replaceDefaultNewGroup(const Class* clasp, TaggedProto p NewEntry::Lookup lookup(clasp, proto, associated); NewTable::Ptr p = defaultNewTable->lookup(lookup); - MOZ_ASSERT(p); + MOZ_RELEASE_ASSERT(p); defaultNewTable->remove(p); - defaultNewTable->putNew(lookup, NewEntry(group, associated)); + { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!defaultNewTable->putNew(lookup, NewEntry(group, associated))) + oomUnsafe.crash("Inconsistent object table"); + } } /* static */ diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index 00079b3790..49b92f3f11 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -79,6 +79,7 @@ * Object * Array * RegExp + * Class * [Other] */ @@ -1669,12 +1670,63 @@ */ \ macro(JSOP_DEFLET, 162,"deflet", NULL, 5, 0, 0, JOF_ATOM) \ \ - macro(JSOP_UNUSED163, 163,"unused163", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED164, 164,"unused164", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED165, 165,"unused165", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED166, 166,"unused166", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED167, 167,"unused167", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED168, 168,"unused168", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Bind the |this| value of a function to the supplied value. + * + * Category: Variables and Scopes + * Type: This + * Operands: + * Stack: this => this + */ \ + macro(JSOP_SETTHIS , 163,"setthis", NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Find the function to invoke with |super()| on the scope chain. + * + * Category: Variables and Scopes + * Type: Super + * Operands: + * Stack: => superFun + */ \ + macro(JSOP_SUPERFUN, 164,"superfun", NULL, 1, 0, 1, JOF_BYTE) \ + /* + * Behaves exactly like JSOP_NEW, but allows JITs to distinguish the two cases. + * + * Category: Statements + * Type: Function + * Operands: uint16_t argc + * Stack: callee, this, args[0], ..., args[argc-1], newTarget => rval + * nuses: (argc+3) + */ \ + macro(JSOP_SUPERCALL, 165,"supercall", NULL, 3, -1, 1, JOF_UINT16|JOF_INVOKE|JOF_TYPESET) \ + /* + * spreadcall variant of JSOP_SUPERCALL. + * + * Behaves exactly like JSOP_SPREADNEW. + * + * Category: Statements + * Type: Function + * Operands: + * Stack: callee, this, args, newTarget => rval + */ \ + macro(JSOP_SPREADSUPERCALL, 166, "spreadsupercall", NULL, 1, 4, 1, JOF_BYTE|JOF_INVOKE|JOF_TYPESET) \ + /* + * Push a default constructor for a base class literal. + * + * Category: Literals + * Type: Class + * Operands: atom className + * Stack: => constructor + */ \ + macro(JSOP_CLASSCONSTRUCTOR, 167,"classconstructor", NULL, 5, 0, 1, JOF_ATOM) \ + /* + * Push a default constructor for a derived class literal. + * + * Category: Literals + * Type: Class + * Operands: atom className + * Stack: => constructor + */ \ + macro(JSOP_DERIVEDCONSTRUCTOR, 168,"derivedconstructor", NULL, 5, 1, 1, JOF_ATOM) \ macro(JSOP_UNUSED169, 169,"unused169", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED170, 170,"unused170", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED171, 171,"unused171", NULL, 1, 0, 0, JOF_BYTE) \ diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 4f77eec947..1a54b19fec 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -232,7 +232,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime) oomCallback(nullptr), debuggerMallocSizeOf(ReturnZeroSize), lastAnimationTime(0), - stopwatch(thisFromCtor()) + performanceMonitoring(thisFromCtor()) { setGCStoreBufferPtr(&gc.storeBuffer); @@ -877,456 +877,3 @@ JS::IsProfilingEnabledForRuntime(JSRuntime* runtime) MOZ_ASSERT(runtime); return runtime->spsProfiler.enabled(); } - -JS_PUBLIC_API(void) -js::FlushPerformanceMonitoring(JSRuntime* runtime) -{ - MOZ_ASSERT(runtime); - return runtime->stopwatch.commit(); -} -JS_PUBLIC_API(void) -js::ResetPerformanceMonitoring(JSRuntime* runtime) -{ - MOZ_ASSERT(runtime); - return runtime->stopwatch.reset(); -} - -void -JSRuntime::Stopwatch::reset() -{ - // All ongoing measures are dependent on the current iteration#. - // By incrementing it, we mark all data as stale. Stale data will - // be overwritten progressively during the execution. - ++iteration_; - touchedGroups.clear(); -} - -void -JSRuntime::Stopwatch::start() -{ - if (!isMonitoringJank_) { - return; - } - - if (iteration_ == startedAtIteration_) { - // The stopwatch is already started for this iteration. - return; - } - - startedAtIteration_ = iteration_; - if (!getResources(&userTimeStart_, &systemTimeStart_)) - return; -} - -// Commit the data that has been collected during the iteration -// into the actual `PerformanceData`. -// -// We use the proportion of cycles-spent-in-group over -// cycles-spent-in-toplevel-group as an approximation to allocate -// system (kernel) time and user (CPU) time to each group. Note -// that cycles are not an exact measure: -// -// 1. if the computer has gone to sleep, the clock may be reset to 0; -// 2. if the process is moved between CPUs/cores, it may end up on a CPU -// or core with an unsynchronized clock; -// 3. the mapping between clock cycles and walltime varies with the current -// frequency of the CPU; -// 4. other threads/processes using the same CPU will also increment -// the counter. -// -// ** Effect of 1. (computer going to sleep) -// -// We assume that this will happen very seldom. Since the final numbers -// are bounded by the CPU time and Kernel time reported by `getresources`, -// the effect will be contained to a single iteration of the event loop. -// -// ** Effect of 2. (moving between CPUs/cores) -// -// On platforms that support it, we only measure the number of cycles -// if we start and end execution of a group on the same -// CPU/core. While there is a small window (a few cycles) during which -// the thread can be migrated without us noticing, we expect that this -// will happen rarely enough that this won't affect the statistics -// meaningfully. -// -// On other platforms, assuming that the probability of jumping -// between CPUs/cores during a given (real) cycle is constant, and -// that the distribution of differences between clocks is even, the -// probability that the number of cycles reported by a measure is -// modified by X cycles should be a gaussian distribution, with groups -// with longer execution having a larger amplitude than groups with -// shorter execution. Since we discard measures that result in a -// negative number of cycles, this distribution is actually skewed -// towards over-estimating the number of cycles of groups that already -// have many cycles and under-estimating the number of cycles that -// already have fewer cycles. -// -// Since the final numbers are bounded by the CPU time and Kernel time -// reported by `getresources`, we accept this bias. -// -// ** Effect of 3. (mapping between clock cycles and walltime) -// -// Assuming that this is evenly distributed, we expect that this will -// eventually balance out. -// -// ** Effect of 4. (cycles increase with system activity) -// -// Assuming that, within an iteration of the event loop, this happens -// unformly over time, this will skew towards over-estimating the number -// of cycles of groups that already have many cycles and under-estimating -// the number of cycles that already have fewer cycles. -// -// Since the final numbers are bounded by the CPU time and Kernel time -// reported by `getresources`, we accept this bias. -// -// ** Big picture -// -// Computing the number of cycles is fast and should be accurate -// enough in practice. Alternatives (such as calling `getresources` -// all the time or sampling from another thread) are very expensive -// in system calls and/or battery and not necessarily more accurate. -void -JSRuntime::Stopwatch::commit() -{ -#if !defined(MOZ_HAVE_RDTSC) - // The AutoStopwatch is only executed if `MOZ_HAVE_RDTSC`. - return; -#endif // !defined(MOZ_HAVE_RDTSC) - - if (!isMonitoringJank_) { - // Either we have not started monitoring or monitoring has - // been cancelled during the iteration. - return; - } - - if (startedAtIteration_ != iteration_) { - // No JS code has been monitored during this iteration. - return; - } - - uint64_t userTimeStop, systemTimeStop; - if (!getResources(&userTimeStop, &systemTimeStop)) - return; - - // `getResources` is not guaranteed to be monotonic, so round up - // any negative result to 0 milliseconds. - uint64_t userTimeDelta = 0; - if (userTimeStop > userTimeStart_) - userTimeDelta = userTimeStop - userTimeStart_; - - uint64_t systemTimeDelta = 0; - if (systemTimeStop > systemTimeStart_) - systemTimeDelta = systemTimeStop - systemTimeStart_; - - RefPtr group = performance.getOwnGroup(); - const uint64_t totalRecentCycles = group->recentCycles; - - mozilla::Vector> recentGroups; - touchedGroups.swap(recentGroups); - MOZ_ASSERT(recentGroups.length() > 0); - - // We should only reach this stage if `group` has had some activity. - MOZ_ASSERT(group->recentTicks > 0); - for (RefPtr* iter = recentGroups.begin(); iter != recentGroups.end(); ++iter) { - transferDeltas(userTimeDelta, systemTimeDelta, totalRecentCycles, *iter); - } - - // Make sure that `group` was treated along with the other items of `recentGroups`. - MOZ_ASSERT(group->recentTicks == 0); - - // Finally, reset immediately, to make sure that we're not hit by the - // end of a nested event loop (which would cause `commit` to be called - // twice in succession). - reset(); -} - -void -JSRuntime::Stopwatch::transferDeltas(uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta, - uint64_t totalCyclesDelta, js::PerformanceGroup* group) { - - const uint64_t ticksDelta = group->recentTicks; - const uint64_t cpowTimeDelta = group->recentCPOW; - const uint64_t cyclesDelta = group->recentCycles; - group->resetRecentData(); - - // We have now performed all cleanup and may `return` at any time without fear of leaks. - - if (group->iteration() != iteration_) { - // Stale data, don't commit. - return; - } - - // When we add a group as changed, we immediately set its - // `recentTicks` from 0 to 1. If we have `ticksDelta == 0` at - // this stage, we have already called `resetRecentData` but we - // haven't removed it from the list. - MOZ_ASSERT(ticksDelta != 0); - MOZ_ASSERT(cyclesDelta <= totalCyclesDelta); - if (cyclesDelta == 0 || totalCyclesDelta == 0) { - // Nothing useful, don't commit. - return; - } - - double proportion = (double)cyclesDelta / (double)totalCyclesDelta; - MOZ_ASSERT(proportion <= 1); - - const uint64_t userTimeDelta = proportion * totalUserTimeDelta; - const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta; - - group->data.totalUserTime += userTimeDelta; - group->data.totalSystemTime += systemTimeDelta; - group->data.totalCPOWTime += cpowTimeDelta; - group->data.ticks += ticksDelta; - - const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta; - - size_t i = 0; - uint64_t duration = 1000; // 1ms in µs - for (i = 0, duration = 1000; - i < mozilla::ArrayLength(group->data.durations) && duration < totalTimeDelta; - ++i, duration *= 2) { - group->data.durations[i]++; - } -} - -// Get the OS-reported time spent in userland/systemland, in -// microseconds. On most platforms, this data is per-thread, -// but on some platforms we need to fall back to per-process. -// Data is not guaranteed to be monotonic. -bool -JSRuntime::Stopwatch::getResources(uint64_t* userTime, - uint64_t* systemTime) const { - MOZ_ASSERT(userTime); - MOZ_ASSERT(systemTime); - -#if defined(XP_DARWIN) - // On MacOS X, to get we per-thread data, we need to - // reach into the kernel. - - mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; - thread_basic_info_data_t info; - mach_port_t port = mach_thread_self(); - kern_return_t err = - thread_info(/* [in] targeted thread*/ port, - /* [in] nature of information*/ THREAD_BASIC_INFO, - /* [out] thread information */ (thread_info_t)&info, - /* [inout] number of items */ &count); - - // We do not need ability to communicate with the thread, so - // let's release the port. - mach_port_deallocate(mach_task_self(), port); - - if (err != KERN_SUCCESS) - return false; - - *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000; - *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000; - -#elif defined(XP_UNIX) - struct rusage rusage; -#if defined(RUSAGE_THREAD) - // Under Linux, we can obtain per-thread statistics - int err = getrusage(RUSAGE_THREAD, &rusage); -#else - // Under other Unices, we need to do with more noisy - // per-process statistics. - int err = getrusage(RUSAGE_SELF, &rusage); -#endif // defined(RUSAGE_THREAD) - - if (err) - return false; - - *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000; - *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000; - -#elif defined(XP_WIN) - // Under Windows, we can obtain per-thread statistics, - // although experience seems to suggest that they are - // not very good under Windows XP. - FILETIME creationFileTime; // Ignored - FILETIME exitFileTime; // Ignored - FILETIME kernelFileTime; - FILETIME userFileTime; - BOOL success = GetThreadTimes(GetCurrentThread(), - &creationFileTime, &exitFileTime, - &kernelFileTime, &userFileTime); - - if (!success) - return false; - - ULARGE_INTEGER kernelTimeInt; - kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime; - kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime; - // Convert 100 ns to 1 us. - *systemTime = kernelTimeInt.QuadPart / 10; - - ULARGE_INTEGER userTimeInt; - userTimeInt.LowPart = userFileTime.dwLowDateTime; - userTimeInt.HighPart = userFileTime.dwHighDateTime; - // Convert 100 ns to 1 us. - *userTime = userTimeInt.QuadPart / 10; - -#endif // defined(XP_DARWIN) || defined(XP_UNIX) || defined(XP_WIN) - - return true; -} - - -bool -js::SetStopwatchIsMonitoringJank(JSRuntime* rt, bool value) -{ - return rt->stopwatch.setIsMonitoringJank(value); -} -bool -js::GetStopwatchIsMonitoringJank(JSRuntime* rt) -{ - return rt->stopwatch.isMonitoringJank(); -} - -bool -js::SetStopwatchIsMonitoringCPOW(JSRuntime* rt, bool value) -{ - return rt->stopwatch.setIsMonitoringCPOW(value); -} -bool -js::GetStopwatchIsMonitoringCPOW(JSRuntime* rt) -{ - return rt->stopwatch.isMonitoringCPOW(); -} - -bool -js::SetStopwatchIsMonitoringPerCompartment(JSRuntime* rt, bool value) -{ - return rt->stopwatch.setIsMonitoringPerCompartment(value); -} -bool -js::GetStopwatchIsMonitoringPerCompartment(JSRuntime* rt) -{ - return rt->stopwatch.isMonitoringPerCompartment(); -} - -void -js::GetPerfMonitoringTestCpuRescheduling(JSRuntime* rt, uint64_t* stayed, uint64_t* moved) -{ - *stayed = rt->stopwatch.testCpuRescheduling.stayed; - *moved = rt->stopwatch.testCpuRescheduling.moved; -} - -js::PerformanceGroupHolder::~PerformanceGroupHolder() -{ - unlink(); -} - -void* -js::PerformanceGroupHolder::getHashKey(JSContext* cx) -{ - if (runtime_->stopwatch.currentPerfGroupCallback) { - return (*runtime_->stopwatch.currentPerfGroupCallback)(cx); - } - - // As a fallback, put everything in the same PerformanceGroup. - return nullptr; -} - -void -js::PerformanceGroupHolder::unlink() -{ - ownGroup_ = nullptr; - sharedGroup_ = nullptr; -} - -PerformanceGroup* -js::PerformanceGroupHolder::getOwnGroup() -{ - if (ownGroup_) - return ownGroup_; - - return ownGroup_ = runtime_->new_(runtime_); -} - -PerformanceGroup* -js::PerformanceGroupHolder::getSharedGroup(JSContext* cx) -{ - if (sharedGroup_) - return sharedGroup_; - - if (!runtime_->stopwatch.groups().initialized()) - return nullptr; - - void* key = getHashKey(cx); - JSRuntime::Stopwatch::Groups::AddPtr ptr = runtime_->stopwatch.groups().lookupForAdd(key); - if (ptr) { - sharedGroup_ = ptr->value(); - MOZ_ASSERT(sharedGroup_); - } else { - sharedGroup_ = runtime_->new_(cx, key); - if (!sharedGroup_) - return nullptr; - runtime_->stopwatch.groups().add(ptr, key, sharedGroup_); - } - - return sharedGroup_; -} - -void -js::AddCPOWPerformanceDelta(JSRuntime* rt, uint64_t delta) -{ - rt->stopwatch.totalCPOWTime += delta; -} - -js::PerformanceGroup::PerformanceGroup(JSRuntime* rt) - : uid(rt->stopwatch.uniqueId()), - recentCycles(0), - recentTicks(0), - recentCPOW(0), - runtime_(rt), - stopwatch_(nullptr), - iteration_(0), - key_(nullptr), - refCount_(0), - isSharedGroup_(false) -{ -} - -js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key) - : uid(cx->runtime()->stopwatch.uniqueId()), - recentCycles(0), - recentTicks(0), - recentCPOW(0), - runtime_(cx->runtime()), - stopwatch_(nullptr), - iteration_(0), - key_(key), - refCount_(0), - isSharedGroup_(true) -{ -} - -void -js::PerformanceGroup::AddRef() -{ - ++refCount_; -} - -void -js::PerformanceGroup::Release() -{ - MOZ_ASSERT(refCount_ > 0); - --refCount_; - if (refCount_ > 0) - return; - - if (isSharedGroup_) { - JSRuntime::Stopwatch::Groups::Ptr ptr = runtime_->stopwatch.groups().lookup(key_); - MOZ_ASSERT(ptr); - runtime_->stopwatch.groups().remove(ptr); - } - - js_delete(this); -} - -void -JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb) -{ - rt->stopwatch.currentPerfGroupCallback = cb; -} diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 433344b5a3..59f73a6c50 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -45,6 +45,7 @@ #include "vm/MallocProvider.h" #include "vm/SPSProfiler.h" #include "vm/Stack.h" +#include "vm/Stopwatch.h" #include "vm/Symbol.h" #ifdef _MSC_VER @@ -1494,302 +1495,7 @@ struct JSRuntime : public JS::shadow::Runtime, int64_t lastAnimationTime; public: - - /* ------------------------------------------ - Performance measurements - ------------------------------------------ */ - struct Stopwatch { - /** - * A map used to collapse compartments belonging to the same - * add-on (respectively to the same webpage, to the platform) - * into a single group. - * - * Keys: for system compartments, a `JSAddonId*` (which may be - * `nullptr`), and for webpages, a `JSPrincipals*` (which may - * not). Note that compartments may start as non-system - * compartments and become compartments later during their - * lifetime, which requires an invalidation. - * - * This map is meant to be accessed only by instances of - * PerformanceGroupHolder, which handle both reference-counting - * of the values and invalidation of the key/value pairs. - */ - typedef js::HashMap, - js::SystemAllocPolicy> Groups; - - Groups& groups() { - return groups_; - } - - /** - * Performance data on the entire runtime. - */ - js::PerformanceGroupHolder performance; - - /** - * Callback used to ask the embedding to determine in which - * Performance Group the current execution belongs. Typically, this is - * used to regroup JSCompartments from several iframes from the same - * page or from several compartments of the same addon into a single - * Performance Group. - * - * May be `nullptr`, in which case we put all the JSCompartments - * in the same PerformanceGroup. - */ - JSCurrentPerfGroupCallback currentPerfGroupCallback; - - /** - * The number of the current iteration of the event loop. - */ - uint64_t iteration() { - return iteration_; - } - - explicit Stopwatch(JSRuntime* runtime) - : performance(runtime) - , currentPerfGroupCallback(nullptr) - , totalCPOWTime(0) - , isMonitoringJank_(false) - , isMonitoringCPOW_(false) - , isMonitoringPerCompartment_(false) - , iteration_(0) - , startedAtIteration_(0) - , idCounter_(0) - { } - - /** - * Reset the stopwatch. - * - * This method is meant to be called whenever we start - * processing an event, to ensure that we stop any ongoing - * measurement that would otherwise provide irrelevant - * results. - */ - void reset(); - - /** - * Start the stopwatch. - * - * This method is meant to be called once we know that the - * current event contains JavaScript code to execute. Calling - * this several times during the same iteration is idempotent. - */ - void start(); - - /** - * Commit the performance data collected since the last call - * to `start()`, unless `reset()` has been called since then. - */ - void commit(); - - /** - * Activate/deactivate stopwatch measurement of jank. - * - * Noop if `value` is `true` and the stopwatch is already - * measuring jank, or if `value` is `false` and the stopwatch - * is not measuring jank. - * - * Otherwise, any pending measurements are dropped, but previous - * measurements remain stored. - * - * May return `false` if the underlying hashtable cannot be allocated. - */ - bool setIsMonitoringJank(bool value) { - if (isMonitoringJank_ != value) - reset(); - - if (value && !groups_.initialized()) { - if (!groups_.init(128)) - return false; - } - - isMonitoringJank_ = value; - return true; - } - bool isMonitoringJank() const { - return isMonitoringJank_; - } - - /** - * Activate/deactivate stopwatch measurement per compartment. - * - * Noop if `value` is `true` and the stopwatch is already - * measuring per compartment, or if `value` is `false` and the - * stopwatch is not measuring per compartment. - * - * Otherwise, any pending measurements are dropped, but previous - * measurements remain stored. - * - * May return `false` if the underlying hashtable cannot be allocated. - */ - bool setIsMonitoringPerCompartment(bool value) { - if (isMonitoringPerCompartment_ != value) - reset(); - - if (value && !groups_.initialized()) { - if (!groups_.init(128)) - return false; - } - - isMonitoringPerCompartment_ = value; - return true; - } - bool isMonitoringPerCompartment() const { - return isMonitoringPerCompartment_; - } - - /** - * Activate/deactivate stopwatch measurement of CPOW. - * - * Noop if `value` is `true` and the stopwatch is already - * measuring CPOW, or if `value` is `false` and the stopwatch - * is not measuring CPOW. - * - * Otherwise, any pending measurements are dropped, but previous - * measurements remain stored. - * - * May return `false` if the underlying hashtable cannot be allocated. - */ - bool setIsMonitoringCPOW(bool value) { - if (isMonitoringCPOW_ != value) - reset(); - - if (value && !groups_.initialized()) { - if (!groups_.init(128)) - return false; - } - - isMonitoringCPOW_ = value; - return true; - } - - bool isMonitoringCPOW() const { - return isMonitoringCPOW_; - } - - /** - * Return a identifier for a group, unique to the runtime. - */ - uint64_t uniqueId() { - return idCounter_++; - } - - /** - * Mark a group as changed during the current iteration. - * - * Recent data from this group will be post-processed and - * committed at the end of the iteration. - */ - void addChangedGroup(js::PerformanceGroup* group) { - MOZ_ASSERT(group->recentTicks == 0); - touchedGroups.append(group); - } - - // The total amount of time spent waiting on CPOWs since the - // start of the process, in microseconds. - uint64_t totalCPOWTime; - - // Data extracted by the AutoStopwatch to determine how often - // we reschedule the process to a different CPU during the - // execution of JS. - // - // Warning: These values are incremented *only* on platforms - // that offer a syscall/libcall to check on which CPU a - // process is currently executed. - struct TestCpuRescheduling - { - // Incremented once we have finished executing code - // in a group, if the CPU on which we started - // execution is the same as the CPU on which - // we finished. - uint64_t stayed; - // Incremented once we have finished executing code - // in a group, if the CPU on which we started - // execution is different from the CPU on which - // we finished. - uint64_t moved; - TestCpuRescheduling() - : stayed(0), - moved(0) - { } - }; - TestCpuRescheduling testCpuRescheduling; - - private: - Stopwatch(const Stopwatch&) = delete; - Stopwatch& operator=(const Stopwatch&) = delete; - - // Commit a piece of data to a single group. - // `totalUserTimeDelta`, `totalSystemTimeDelta`, `totalCyclesDelta` - // represent the outer measures, taken for the entire runtime. - void transferDeltas(uint64_t totalUserTimeDelta, - uint64_t totalSystemTimeDelta, - uint64_t totalCyclesDelta, - js::PerformanceGroup* destination); - - // Query the OS for the time spent in CPU/kernel since process - // launch. - bool getResources(uint64_t* userTime, uint64_t* systemTime) const; - - private: - Groups groups_; - friend struct js::PerformanceGroupHolder; - - /** - * `true` if stopwatch monitoring is active for Jank, `false` otherwise. - */ - bool isMonitoringJank_; - /** - * `true` if stopwatch monitoring is active for CPOW, `false` otherwise. - */ - bool isMonitoringCPOW_; - /** - * `true` if the stopwatch should udpdate data per-compartment, in - * addition to data per-group. - */ - bool isMonitoringPerCompartment_; - - /** - * The number of times we have entered the event loop. - * Used to reset counters whenever we enter the loop, - * which may be caused either by having completed the - * previous run of the event loop, or by entering a - * nested loop. - * - * Always incremented by 1, may safely overflow. - */ - uint64_t iteration_; - - /** - * The iteration at which the stopwatch was last started. - * - * Used both to avoid starting the stopwatch several times - * during the same event loop and to avoid committing stale - * stopwatch results. - */ - uint64_t startedAtIteration_; - - /** - * A counter used to generate unique identifiers for groups. - */ - uint64_t idCounter_; - - /** - * The timestamps returned by `getResources()` during the call to - * `start()` in the current iteration of the event loop. - */ - uint64_t userTimeStart_; - uint64_t systemTimeStart_; - - /** - * Performance groups used during the current event. - * - * They are cleared by `commit()` and `reset()`. - */ - mozilla::Vector> touchedGroups; - }; - Stopwatch stopwatch; + js::PerformanceMonitoring performanceMonitoring; }; namespace js { diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 6de93ec6eb..47c5d5f1ba 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -258,13 +258,19 @@ InterpreterFrame::prologue(JSContext* cx) if (fun()->needsCallObject() && !initFunctionScopeObjects(cx)) return false; - if (isConstructing() && functionThis().isPrimitive()) { - RootedObject callee(cx, &this->callee()); - JSObject* obj = CreateThisForFunction(cx, callee, - createSingleton() ? SingletonObject : GenericObject); - if (!obj) - return false; - functionThis() = ObjectValue(*obj); + if (isConstructing()) { + if (script->isDerivedClassConstructor()) { + MOZ_ASSERT(callee().isClassConstructor()); + functionThis() = MagicValue(JS_UNINITIALIZED_LEXICAL); + } else if (functionThis().isPrimitive()) { + RootedObject callee(cx, &this->callee()); + RootedObject newTarget(cx, &this->newTarget().toObject()); + JSObject* obj = CreateThisForFunction(cx, callee, newTarget, + createSingleton() ? SingletonObject : GenericObject); + if (!obj) + return false; + functionThis() = ObjectValue(*obj); + } } return probes::EnterScript(cx, script, script->functionNonDelazifying(), this); @@ -316,6 +322,43 @@ InterpreterFrame::epilogue(JSContext* cx) setReturnValue(ObjectValue(constructorThis())); } +bool +InterpreterFrame::checkThis(JSContext* cx) +{ + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(isNonEvalFunctionFrame()); + MOZ_ASSERT(fun()->isClassConstructor()); + + if (thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)) { + RootedFunction func(cx, fun()); + return ThrowUninitializedThis(cx, this); + } + } + return true; +} + +bool +InterpreterFrame::checkReturn(JSContext* cx) +{ + if (script()->isDerivedClassConstructor()) { + MOZ_ASSERT(isNonEvalFunctionFrame()); + MOZ_ASSERT(callee().isClassConstructor()); + + HandleValue retVal = returnValue(); + if (retVal.isObject()) + return true; + + if (!retVal.isUndefined()) { + ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, retVal, nullptr); + return false; + } + + if (!checkThis(cx)) + return false; + } + return true; +} + bool InterpreterFrame::pushBlock(JSContext* cx, StaticBlockObject& block) { diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 731c0bb0df..b6fd39364a 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -452,6 +452,9 @@ class InterpreterFrame bool prologue(JSContext* cx); void epilogue(JSContext* cx); + bool checkReturn(JSContext* cx); + bool checkThis(JSContext* cx); + bool initFunctionScopeObjects(JSContext* cx); /* @@ -738,6 +741,14 @@ class InterpreterFrame return argv()[-1]; } + void setDerivedConstructorThis(HandleObject thisv) { + MOZ_ASSERT(isNonEvalFunctionFrame()); + MOZ_ASSERT(script()->isDerivedClassConstructor()); + MOZ_ASSERT(callee().isClassConstructor()); + MOZ_ASSERT(thisValue().isMagic(JS_UNINITIALIZED_LEXICAL)); + argv()[-1] = ObjectValue(*thisv); + } + /* * Callee * diff --git a/js/src/vm/Stopwatch.cpp b/js/src/vm/Stopwatch.cpp new file mode 100644 index 0000000000..71f83e3b6f --- /dev/null +++ b/js/src/vm/Stopwatch.cpp @@ -0,0 +1,621 @@ +#include "vm/Stopwatch.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/IntegerTypeTraits.h" +#include "mozilla/unused.h" + +namespace js { + +bool +PerformanceMonitoring::addRecentGroup(PerformanceGroup* group) +{ + if (group->isUsedInThisIteration()) + return true; + + group->setIsUsedInThisIteration(true); + return recentGroups_.append(group); +} + +void +PerformanceMonitoring::reset() +{ + // All ongoing measures are dependent on the current iteration#. + // By incrementing it, we mark all data as stale. Stale data will + // be overwritten progressively during the execution. + ++iteration_; + recentGroups_.clear(); +} + +void +PerformanceMonitoring::start() +{ + if (!isMonitoringJank_) + return; + + if (iteration_ == startedAtIteration_) { + // The stopwatch is already started for this iteration. + return; + } + + startedAtIteration_ = iteration_; + if (stopwatchStartCallback) + stopwatchStartCallback(iteration_, stopwatchStartClosure); +} + +// Commit the data that has been collected during the iteration +// into the actual `PerformanceData`. +// +// We use the proportion of cycles-spent-in-group over +// cycles-spent-in-toplevel-group as an approximation to allocate +// system (kernel) time and user (CPU) time to each group. Note +// that cycles are not an exact measure: +// +// 1. if the computer has gone to sleep, the clock may be reset to 0; +// 2. if the process is moved between CPUs/cores, it may end up on a CPU +// or core with an unsynchronized clock; +// 3. the mapping between clock cycles and walltime varies with the current +// frequency of the CPU; +// 4. other threads/processes using the same CPU will also increment +// the counter. +// +// ** Effect of 1. (computer going to sleep) +// +// We assume that this will happen very seldom. Since the final numbers +// are bounded by the CPU time and Kernel time reported by `getresources`, +// the effect will be contained to a single iteration of the event loop. +// +// ** Effect of 2. (moving between CPUs/cores) +// +// On platforms that support it, we only measure the number of cycles +// if we start and end execution of a group on the same +// CPU/core. While there is a small window (a few cycles) during which +// the thread can be migrated without us noticing, we expect that this +// will happen rarely enough that this won't affect the statistics +// meaningfully. +// +// On other platforms, assuming that the probability of jumping +// between CPUs/cores during a given (real) cycle is constant, and +// that the distribution of differences between clocks is even, the +// probability that the number of cycles reported by a measure is +// modified by X cycles should be a gaussian distribution, with groups +// with longer execution having a larger amplitude than groups with +// shorter execution. Since we discard measures that result in a +// negative number of cycles, this distribution is actually skewed +// towards over-estimating the number of cycles of groups that already +// have many cycles and under-estimating the number of cycles that +// already have fewer cycles. +// +// Since the final numbers are bounded by the CPU time and Kernel time +// reported by `getresources`, we accept this bias. +// +// ** Effect of 3. (mapping between clock cycles and walltime) +// +// Assuming that this is evenly distributed, we expect that this will +// eventually balance out. +// +// ** Effect of 4. (cycles increase with system activity) +// +// Assuming that, within an iteration of the event loop, this happens +// unformly over time, this will skew towards over-estimating the number +// of cycles of groups that already have many cycles and under-estimating +// the number of cycles that already have fewer cycles. +// +// Since the final numbers are bounded by the CPU time and Kernel time +// reported by `getresources`, we accept this bias. +// +// ** Big picture +// +// Computing the number of cycles is fast and should be accurate +// enough in practice. Alternatives (such as calling `getresources` +// all the time or sampling from another thread) are very expensive +// in system calls and/or battery and not necessarily more accurate. +bool +PerformanceMonitoring::commit() +{ +#if !defined(MOZ_HAVE_RDTSC) + // The AutoStopwatch is only executed if `MOZ_HAVE_RDTSC`. + return false; +#endif // !defined(MOZ_HAVE_RDTSC) + + if (!isMonitoringJank_) { + // Either we have not started monitoring or monitoring has + // been cancelled during the iteration. + return true; + } + + if (startedAtIteration_ != iteration_) { + // No JS code has been monitored during this iteration. + return true; + } + + GroupVector recentGroups; + recentGroups_.swap(recentGroups); + + bool success = true; + if (stopwatchCommitCallback) + success = stopwatchCommitCallback(iteration_, recentGroups, stopwatchCommitClosure); + + // Reset immediately, to make sure that we're not hit by the end + // of a nested event loop (which would cause `commit` to be called + // twice in succession). + reset(); + return success; +} + +void +PerformanceMonitoring::dispose(JSRuntime* rt) +{ + reset(); + for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) { + c->performanceMonitoring.unlink(); + } +} + +PerformanceGroupHolder::~PerformanceGroupHolder() +{ + unlink(); +} + +void +PerformanceGroupHolder::unlink() +{ + initialized_ = false; + groups_.clear(); +} + +const GroupVector* +PerformanceGroupHolder::getGroups(JSContext* cx) +{ + if (initialized_) + return &groups_; + + if (!runtime_->performanceMonitoring.getGroupsCallback) + return nullptr; + + if (!runtime_->performanceMonitoring.getGroupsCallback(cx, groups_, runtime_->performanceMonitoring.getGroupsClosure)) + return nullptr; + + initialized_ = true; + return &groups_; +} + +AutoStopwatch::AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : cx_(cx) + , iteration_(0) + , isMonitoringJank_(false) + , isMonitoringCPOW_(false) + , cyclesStart_(0) + , CPOWTimeStart_(0) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + JSCompartment* compartment = cx_->compartment(); + if (compartment->scheduledForDestruction) + return; + + JSRuntime* runtime = cx_->runtime(); + iteration_ = runtime->performanceMonitoring.iteration(); + + const GroupVector* groups = compartment->performanceMonitoring.getGroups(cx); + if (!groups) { + // Either the embedding has not provided any performance + // monitoring logistics or there was an error that prevents + // performance monitoring. + return; + } + for (auto group = groups->begin(); group < groups->end(); group++) { + auto acquired = acquireGroup(*group); + if (acquired) + groups_.append(acquired); + } + if (groups_.length() == 0) { + // We are not in charge of monitoring anything. + return; + } + + // Now that we are sure that JS code is being executed, + // initialize the stopwatch for this iteration, lazily. + runtime->performanceMonitoring.start(); + enter(); +} + +AutoStopwatch::~AutoStopwatch() +{ + if (groups_.length() == 0) { + // We are not in charge of monitoring anything. + return; + } + + JSCompartment* compartment = cx_->compartment(); + if (compartment->scheduledForDestruction) + return; + + JSRuntime* runtime = cx_->runtime(); + if (iteration_ != runtime->performanceMonitoring.iteration()) { + // We have entered a nested event loop at some point. + // Any information we may have is obsolete. + return; + } + + mozilla::unused << exit(); // Sadly, there is nothing we can do about an error at this point. + + for (auto group = groups_.begin(); group < groups_.end(); group++) + releaseGroup(*group); +} + +void +AutoStopwatch::enter() +{ + JSRuntime* runtime = cx_->runtime(); + + if (runtime->performanceMonitoring.isMonitoringCPOW()) { + CPOWTimeStart_ = runtime->performanceMonitoring.totalCPOWTime; + isMonitoringCPOW_ = true; + } + + if (runtime->performanceMonitoring.isMonitoringJank()) { + cyclesStart_ = this->getCycles(); + cpuStart_ = this->getCPU(); + isMonitoringJank_ = true; + } +} + +bool +AutoStopwatch::exit() +{ + JSRuntime* runtime = cx_->runtime(); + + uint64_t cyclesDelta = 0; + if (isMonitoringJank_ && runtime->performanceMonitoring.isMonitoringJank()) { + // We were monitoring jank when we entered and we still are. + + // If possible, discard results when we don't end on the + // same CPU as we started. Note that we can be + // rescheduled to another CPU beween `getCycles()` and + // `getCPU()`. We hope that this will happen rarely + // enough that the impact on our statistics will remain + // limited. + const cpuid_t cpuEnd = this->getCPU(); + if (isSameCPU(cpuStart_, cpuEnd)) { + const uint64_t cyclesEnd = getCycles(); + cyclesDelta = getDelta(cyclesEnd, cyclesStart_); + } +#if WINVER >= 0x600 + updateTelemetry(cpuStart_, cpuEnd); +#elif defined(__linux__) + updateTelemetry(cpuStart_, cpuEnd); +#endif // WINVER >= 0x600 || _linux__ + } + + uint64_t CPOWTimeDelta = 0; + if (isMonitoringCPOW_ && runtime->performanceMonitoring.isMonitoringCPOW()) { + // We were monitoring CPOW when we entered and we still are. + const uint64_t CPOWTimeEnd = runtime->performanceMonitoring.totalCPOWTime; + CPOWTimeDelta = getDelta(CPOWTimeEnd, CPOWTimeStart_); + } + return addToGroups(cyclesDelta, CPOWTimeDelta); +} + +void +AutoStopwatch::updateTelemetry(const cpuid_t& cpuStart_, const cpuid_t& cpuEnd) +{ + JSRuntime* runtime = cx_->runtime(); + + if (isSameCPU(cpuStart_, cpuEnd)) + runtime->performanceMonitoring.testCpuRescheduling.stayed += 1; + else + runtime->performanceMonitoring.testCpuRescheduling.moved += 1; +} + +PerformanceGroup* +AutoStopwatch::acquireGroup(PerformanceGroup* group) +{ + MOZ_ASSERT(group); + + if (group->isAcquired(iteration_)) + return nullptr; + + if (!group->isActive()) + return nullptr; + + group->acquire(iteration_, this); + return group; +} + +void +AutoStopwatch::releaseGroup(PerformanceGroup* group) +{ + MOZ_ASSERT(group); + group->release(iteration_, this); +} + +bool +AutoStopwatch::addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta) +{ + JSRuntime* runtime = cx_->runtime(); + + for (auto group = groups_.begin(); group < groups_.end(); ++group) { + if (!addToGroup(runtime, cyclesDelta, CPOWTimeDelta, *group)) + return false; + } + return true; +} + +bool +AutoStopwatch::addToGroup(JSRuntime* runtime, uint64_t cyclesDelta, uint64_t CPOWTimeDelta, PerformanceGroup* group) +{ + MOZ_ASSERT(group); + MOZ_ASSERT(group->isAcquired(iteration_, this)); + + if (!runtime->performanceMonitoring.addRecentGroup(group)) + return false; + group->addRecentTicks(iteration_, 1); + group->addRecentCycles(iteration_, cyclesDelta); + group->addRecentCPOW(iteration_, CPOWTimeDelta); + return true; +} + +uint64_t +AutoStopwatch::getDelta(const uint64_t end, const uint64_t start) const +{ + if (start >= end) + return 0; + return end - start; +} + +uint64_t +AutoStopwatch::getCycles() const +{ +#if defined(MOZ_HAVE_RDTSC) + return ReadTimestampCounter(); +#else + return 0; +#endif // defined(MOZ_HAVE_RDTSC) +} + +cpuid_t inline +AutoStopwatch::getCPU() const +{ +#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA + PROCESSOR_NUMBER proc; + GetCurrentProcessorNumberEx(&proc); + + cpuid_t result(proc.Group, proc.Number); + return result; +#elif defined(XP_LINUX) + return sched_getcpu(); +#else + return {}; +#endif // defined(XP_WIN) || defined(XP_LINUX) +} + +bool inline +AutoStopwatch::isSameCPU(const cpuid_t& a, const cpuid_t& b) const +{ +#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA + return a.group_ == b.group_ && a.number_ == b.number_; +#elif defined(XP_LINUX) + return a == b; +#else + return true; +#endif +} + +PerformanceGroup::PerformanceGroup() + : recentCycles_(0) + , recentTicks_(0) + , recentCPOW_(0) + , iteration_(0) + , isActive_(false) + , isUsedInThisIteration_(false) + , owner_(nullptr) + , refCount_(0) +{ } + +uint64_t +PerformanceGroup::iteration() const +{ + return iteration_; +} + + +bool +PerformanceGroup::isAcquired(uint64_t it) const +{ + return owner_ != nullptr && iteration_ == it; +} + +bool +PerformanceGroup::isAcquired(uint64_t it, const AutoStopwatch* owner) const +{ + return owner_ == owner && iteration_ == it; +} + +void +PerformanceGroup::acquire(uint64_t it, const AutoStopwatch* owner) +{ + if (iteration_ != it) { + // Any data that pretends to be recent is actually bound + // to an older iteration and therefore stale. + resetRecentData(); + } + iteration_ = it; + owner_ = owner; +} + +void +PerformanceGroup::release(uint64_t it, const AutoStopwatch* owner) +{ + if (iteration_ != it) + return; + + MOZ_ASSERT(owner == owner_ || owner_ == nullptr); + owner_ = nullptr; +} + +void +PerformanceGroup::resetRecentData() +{ + recentCycles_ = 0; + recentTicks_ = 0; + recentCPOW_ = 0; + isUsedInThisIteration_ = false; +} + + +uint64_t +PerformanceGroup::recentCycles(uint64_t iteration) const +{ + MOZ_ASSERT(iteration == iteration_); + return recentCycles_; + } + +void +PerformanceGroup::addRecentCycles(uint64_t iteration, uint64_t cycles) +{ + MOZ_ASSERT(iteration == iteration); + recentCycles_ += cycles; +} + +uint64_t +PerformanceGroup::recentTicks(uint64_t iteration) const +{ + MOZ_ASSERT(iteration == iteration_); + return recentTicks_; +} + +void +PerformanceGroup::addRecentTicks(uint64_t iteration, uint64_t ticks) +{ + MOZ_ASSERT(iteration == iteration); + recentTicks_ += ticks; +} + + +uint64_t +PerformanceGroup::recentCPOW(uint64_t iteration) const +{ + MOZ_ASSERT(iteration == iteration_); + return recentCPOW_; +} + +void +PerformanceGroup::addRecentCPOW(uint64_t iteration, uint64_t CPOW) +{ + MOZ_ASSERT(iteration == iteration); + recentCPOW_ += CPOW; +} + + +bool +PerformanceGroup::isActive() const +{ + return isActive_; +} + +void +PerformanceGroup::setIsActive(bool value) +{ + isActive_ = value; +} + +void +PerformanceGroup::setIsUsedInThisIteration(bool value) +{ + isUsedInThisIteration_ = value; +} +bool +PerformanceGroup::isUsedInThisIteration() const +{ + return isUsedInThisIteration_; +} + +void +PerformanceGroup::AddRef() +{ + ++refCount_; +} + +void +PerformanceGroup::Release() +{ + MOZ_ASSERT(refCount_ > 0); + --refCount_; + if (refCount_ > 0) + return; + + this->Delete(); +} + +JS_PUBLIC_API(bool) SetStopwatchStartCallback(JSRuntime* rt, StopwatchStartCallback cb, void* closure) +{ + rt->performanceMonitoring.setStopwatchStartCallback(cb, closure); + return true; +} + +JS_PUBLIC_API(bool) SetStopwatchCommitCallback(JSRuntime* rt, StopwatchCommitCallback cb, void* closure) +{ + rt->performanceMonitoring.setStopwatchCommitCallback(cb, closure); + return true; +} + +JS_PUBLIC_API(bool) SetGetPerformanceGroupsCallback(JSRuntime* rt, GetGroupsCallback cb, void* closure) +{ + rt->performanceMonitoring.setGetGroupsCallback(cb, closure); + return true; +} + +JS_PUBLIC_API(bool) +FlushPerformanceMonitoring(JSRuntime* rt) +{ + return rt->performanceMonitoring.commit(); +} +JS_PUBLIC_API(void) +ResetPerformanceMonitoring(JSRuntime* rt) +{ + return rt->performanceMonitoring.reset(); +} +JS_PUBLIC_API(void) +DisposePerformanceMonitoring(JSRuntime* rt) +{ + return rt->performanceMonitoring.dispose(rt); +} + +JS_PUBLIC_API(bool) +SetStopwatchIsMonitoringJank(JSRuntime* rt, bool value) +{ + return rt->performanceMonitoring.setIsMonitoringJank(value); +} +JS_PUBLIC_API(bool) +GetStopwatchIsMonitoringJank(JSRuntime* rt) +{ + return rt->performanceMonitoring.isMonitoringJank(); +} + +JS_PUBLIC_API(bool) +SetStopwatchIsMonitoringCPOW(JSRuntime* rt, bool value) +{ + return rt->performanceMonitoring.setIsMonitoringCPOW(value); +} +JS_PUBLIC_API(bool) +GetStopwatchIsMonitoringCPOW(JSRuntime* rt) +{ + return rt->performanceMonitoring.isMonitoringCPOW(); +} + +JS_PUBLIC_API(void) +GetPerfMonitoringTestCpuRescheduling(JSRuntime* rt, uint64_t* stayed, uint64_t* moved) +{ + *stayed = rt->performanceMonitoring.testCpuRescheduling.stayed; + *moved = rt->performanceMonitoring.testCpuRescheduling.moved; +} + +JS_PUBLIC_API(void) +AddCPOWPerformanceDelta(JSRuntime* rt, uint64_t delta) +{ + rt->performanceMonitoring.totalCPOWTime += delta; +} + + +} // namespace js + diff --git a/js/src/vm/Stopwatch.h b/js/src/vm/Stopwatch.h new file mode 100644 index 0000000000..89bf17d693 --- /dev/null +++ b/js/src/vm/Stopwatch.h @@ -0,0 +1,391 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_Stopwatch_h +#define vm_Stopwatch_h + +/* + An API for following in real-time the amount of CPU spent executing + webpages, add-ons, etc. +*/ + +namespace js { + +typedef mozilla::Vector> GroupVector; + +/** + * A container for performance groups. + * + * Performance monitoring deals with the execution duration of code + * that belongs to components, for a notion of components defined by + * the embedding. Typically, in a web browser, a component may be a + * webpage and/or a frame and/or a module and/or an add-on and/or a + * sandbox and/or a process etc. + * + * A PerformanceGroupHolder is owned y a JSCompartment and maps that + * compartment to all the components to which it belongs. + */ +struct PerformanceGroupHolder { + + /** + * Get the groups to which this compartment belongs. + * + * Pre-condition: Execution must have entered the compartment. + * + * May return `nullptr` if the embedding has not initialized + * support for performance groups. + */ + const GroupVector* getGroups(JSContext*); + + explicit PerformanceGroupHolder(JSRuntime* runtime) + : runtime_(runtime) + , initialized_(false) + { } + ~PerformanceGroupHolder(); + void unlink(); + private: + JSRuntime* runtime_; + + // `true` once a call to `getGroups` has succeeded. + bool initialized_; + + // The groups to which this compartment belongs. Filled if and only + // if `initialized_` is `true`. + GroupVector groups_; +}; + +/** + * Container class for everything related to performance monitoring. + */ +struct PerformanceMonitoring { + /** + * The number of the current iteration of the event loop. + */ + uint64_t iteration() { + return iteration_; + } + + explicit PerformanceMonitoring(JSRuntime* runtime) + : totalCPOWTime(0) + , stopwatchStartCallback(nullptr) + , stopwatchStartClosure(nullptr) + , stopwatchCommitCallback(nullptr) + , stopwatchCommitClosure(nullptr) + , getGroupsCallback(nullptr) + , getGroupsClosure(nullptr) + , isMonitoringJank_(false) + , isMonitoringCPOW_(false) + , iteration_(0) + , startedAtIteration_(0) + { } + + /** + * Reset the stopwatch. + * + * This method is meant to be called whenever we start + * processing an event, to ensure that we stop any ongoing + * measurement that would otherwise provide irrelevant + * results. + */ + void reset(); + + /** + * Start the stopwatch. + * + * This method is meant to be called once we know that the + * current event contains JavaScript code to execute. Calling + * this several times during the same iteration is idempotent. + */ + void start(); + + /** + * Commit the performance data collected since the last call + * to `start()`, unless `reset()` has been called since then. + */ + bool commit(); + + /** + * Liberate memory and references. + */ + void dispose(JSRuntime* rtx); + + /** + * Activate/deactivate stopwatch measurement of jank. + * + * Noop if `value` is `true` and the stopwatch is already + * measuring jank, or if `value` is `false` and the stopwatch + * is not measuring jank. + * + * Otherwise, any pending measurements are dropped, but previous + * measurements remain stored. + * + * May return `false` if the underlying hashtable cannot be allocated. + */ + bool setIsMonitoringJank(bool value) { + if (isMonitoringJank_ != value) + reset(); + + isMonitoringJank_ = value; + return true; + } + bool isMonitoringJank() const { + return isMonitoringJank_; + } + + /** + * Mark that a group has been used in this iteration. + */ + bool addRecentGroup(PerformanceGroup* group); + + /** + * Activate/deactivate stopwatch measurement of CPOW. + * + * Noop if `value` is `true` and the stopwatch is already + * measuring CPOW, or if `value` is `false` and the stopwatch + * is not measuring CPOW. + * + * Otherwise, any pending measurements are dropped, but previous + * measurements remain stored. + * + * May return `false` if the underlying hashtable cannot be allocated. + */ + bool setIsMonitoringCPOW(bool value) { + if (isMonitoringCPOW_ != value) + reset(); + + isMonitoringCPOW_ = value; + return true; + } + + bool isMonitoringCPOW() const { + return isMonitoringCPOW_; + } + + /** + * Callbacks called when we start executing an event/when we have + * run to completion (including enqueued microtasks). + * + * If there are no nested event loops, each call to + * `stopwatchStartCallback` is followed by a call to + * `stopwatchCommitCallback`. However, embedders should not assume + * that this will always be the case, unless they take measures to + * prevent nested event loops. + * + * In presence of nested event loops, several calls to + * `stopwatchStartCallback` may occur before a call to + * `stopwatchCommitCallback`. Embedders should assume that a + * second call to `stopwatchStartCallback` cancels any measure + * started by the previous calls to `stopwatchStartCallback` and + * which have not been committed by `stopwatchCommitCallback`. + */ + void setStopwatchStartCallback(js::StopwatchStartCallback cb, void* closure) { + stopwatchStartCallback = cb; + stopwatchStartClosure = closure; + } + void setStopwatchCommitCallback(js::StopwatchCommitCallback cb, void* closure) { + stopwatchCommitCallback = cb; + stopwatchCommitClosure = closure; + } + + /** + * Callback called to associate a JSCompartment to the set of + * `PerformanceGroup`s that represent the components to which + * it belongs. + */ + void setGetGroupsCallback(js::GetGroupsCallback cb, void* closure) { + getGroupsCallback = cb; + getGroupsClosure = closure; + } + + /** + * The total amount of time spent waiting on CPOWs since the + * start of the process, in microseconds. + */ + uint64_t totalCPOWTime; + + /** + * Data extracted by the AutoStopwatch to determine how often + * we reschedule the process to a different CPU during the + * execution of JS. + * + * Warning: These values are incremented *only* on platforms + * that offer a syscall/libcall to check on which CPU a + * process is currently executed. + */ + struct TestCpuRescheduling + { + // Incremented once we have finished executing code + // in a group, if the CPU on which we started + // execution is the same as the CPU on which + // we finished. + uint64_t stayed; + // Incremented once we have finished executing code + // in a group, if the CPU on which we started + // execution is different from the CPU on which + // we finished. + uint64_t moved; + TestCpuRescheduling() + : stayed(0), + moved(0) + { } + }; + TestCpuRescheduling testCpuRescheduling; + private: + PerformanceMonitoring(const PerformanceMonitoring&) = delete; + PerformanceMonitoring& operator=(const PerformanceMonitoring&) = delete; + + private: + friend struct PerformanceGroupHolder; + js::StopwatchStartCallback stopwatchStartCallback; + void* stopwatchStartClosure; + js::StopwatchCommitCallback stopwatchCommitCallback; + void* stopwatchCommitClosure; + + js::GetGroupsCallback getGroupsCallback; + void* getGroupsClosure; + + /** + * `true` if stopwatch monitoring is active for Jank, `false` otherwise. + */ + bool isMonitoringJank_; + /** + * `true` if stopwatch monitoring is active for CPOW, `false` otherwise. + */ + bool isMonitoringCPOW_; + + /** + * The number of times we have entered the event loop. + * Used to reset counters whenever we enter the loop, + * which may be caused either by having completed the + * previous run of the event loop, or by entering a + * nested loop. + * + * Always incremented by 1, may safely overflow. + */ + uint64_t iteration_; + + /** + * The iteration at which the stopwatch was last started. + * + * Used both to avoid starting the stopwatch several times + * during the same event loop and to avoid committing stale + * stopwatch results. + */ + uint64_t startedAtIteration_; + + /** + * Groups used in the current iteration. + */ + GroupVector recentGroups_; +}; + +#if WINVER >= 0x0600 +struct cpuid_t { + WORD group_; + BYTE number_; + cpuid_t(WORD group, BYTE number) + : group_(group), + number_(number) + { } + cpuid_t() + : group_(0), + number_(0) + { } +}; +#elif defined(__linux__) + typedef int cpuid_t; +#else + typedef struct {} cpuid_t; +#endif // defined(WINVER >= 0x0600) || defined(__linux__) + +/** + * RAII class to start/stop measuring performance when + * entering/leaving a compartment. + */ +class AutoStopwatch final { + // The context with which this object was initialized. + // Non-null. + JSContext* const cx_; + + // An indication of the number of times we have entered the event + // loop. Used only for comparison. + uint64_t iteration_; + + // `true` if we are monitoring jank, `false` otherwise. + bool isMonitoringJank_; + // `true` if we are monitoring CPOW, `false` otherwise. + bool isMonitoringCPOW_; + + // Timestamps captured while starting the stopwatch. + uint64_t cyclesStart_; + uint64_t CPOWTimeStart_; + + // The CPU on which we started the measure. Defined only + // if `isMonitoringJank_` is `true`. + cpuid_t cpuStart_; + + mozilla::Vector> groups_; + + public: + // If the stopwatch is active, constructing an instance of + // AutoStopwatch causes it to become the current owner of the + // stopwatch. + // + // Previous owner is restored upon destruction. + explicit AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~AutoStopwatch(); + private: + void inline enter(); + + bool inline exit(); + + // Attempt to acquire a group + // If the group is inactive or if the group already has a stopwatch, + // do nothing and return `null`. + // Otherwise, bind the group to `this` for the current iteration + // and return `group`. + PerformanceGroup* acquireGroup(PerformanceGroup* group); + + // Release a group. Noop if `this` is not the stopwatch of + // `group` for the current iteration. + void releaseGroup(PerformanceGroup* group); + + // Add recent changes to all the groups owned by this stopwatch. + // Mark the groups as changed recently. + bool addToGroups(uint64_t cyclesDelta, uint64_t CPOWTimeDelta); + + // Add recent changes to a single group. Mark the group as changed recently. + bool addToGroup(JSRuntime* runtime, uint64_t cyclesDelta, uint64_t CPOWTimeDelta, PerformanceGroup* group); + + // Update telemetry statistics. + void updateTelemetry(const cpuid_t& a, const cpuid_t& b); + + // Perform a subtraction for a quantity that should be monotonic + // but is not guaranteed to be so. + // + // If `start <= end`, return `end - start`. + // Otherwise, return `0`. + uint64_t inline getDelta(const uint64_t end, const uint64_t start) const; + + // Return the value of the Timestamp Counter, as provided by the CPU. + // 0 on platforms for which we do not have access to a Timestamp Counter. + uint64_t inline getCycles() const; + + + // Return the identifier of the current CPU, on platforms for which we have + // access to the current CPU. + cpuid_t inline getCPU() const; + + // Compare two CPU identifiers. + bool inline isSameCPU(const cpuid_t& a, const cpuid_t& b) const; + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; +}; + + +} // namespace js + +#endif // vm_Stopwatch_h diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index b1601b1c0a..2e2d3d2c32 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -2088,7 +2088,7 @@ JS_StructuredClone(JSContext* cx, HandleValue value, MutableHandleValue vp, JSAutoStructuredCloneBuffer::JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer&& other) { ownTransferables_ = other.ownTransferables_; - other.steal(&data_, &nbytes_, &version_); + other.steal(&data_, &nbytes_, &version_, &callbacks_, &closure_); } JSAutoStructuredCloneBuffer& @@ -2097,7 +2097,7 @@ JSAutoStructuredCloneBuffer::operator=(JSAutoStructuredCloneBuffer&& other) MOZ_ASSERT(&other != this); clear(); ownTransferables_ = other.ownTransferables_; - other.steal(&data_, &nbytes_, &version_); + other.steal(&data_, &nbytes_, &version_, &callbacks_, &closure_); return *this; } @@ -2122,7 +2122,9 @@ JSAutoStructuredCloneBuffer::clear(const JSStructuredCloneCallbacks* optionalCal } bool -JSAutoStructuredCloneBuffer::copy(const uint64_t* srcData, size_t nbytes, uint32_t version) +JSAutoStructuredCloneBuffer::copy(const uint64_t* srcData, size_t nbytes, uint32_t version, + const JSStructuredCloneCallbacks* callbacks, + void* closure) { // transferable objects cannot be copied if (StructuredCloneHasTransferObjects(data_, nbytes_)) @@ -2138,31 +2140,45 @@ JSAutoStructuredCloneBuffer::copy(const uint64_t* srcData, size_t nbytes, uint32 data_ = newData; nbytes_ = nbytes; version_ = version; + callbacks_ = callbacks; + closure_ = closure; ownTransferables_ = NoTransferables; return true; } void -JSAutoStructuredCloneBuffer::adopt(uint64_t* data, size_t nbytes, uint32_t version) +JSAutoStructuredCloneBuffer::adopt(uint64_t* data, size_t nbytes, uint32_t version, + const JSStructuredCloneCallbacks* callbacks, + void* closure) { clear(); data_ = data; nbytes_ = nbytes; version_ = version; + callbacks_ = callbacks; + closure_ = closure; ownTransferables_ = OwnsTransferablesIfAny; } void -JSAutoStructuredCloneBuffer::steal(uint64_t** datap, size_t* nbytesp, uint32_t* versionp) +JSAutoStructuredCloneBuffer::steal(uint64_t** datap, size_t* nbytesp, uint32_t* versionp, + const JSStructuredCloneCallbacks** callbacks, + void** closure) { *datap = data_; *nbytesp = nbytes_; if (versionp) *versionp = version_; + if (callbacks) + *callbacks = callbacks_; + if (closure) + *closure = closure_; data_ = nullptr; nbytes_ = 0; version_ = 0; + callbacks_ = 0; + closure_ = 0; ownTransferables_ = NoTransferables; } diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index ebfd2bf3da..0a8ce4bbca 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,11 +29,11 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 302; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 304; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 414, +static_assert(JSErr_Limit == 418, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 118ae5d782..0598462f03 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -3062,7 +3062,7 @@ ReadSourceFromFilename(JSContext* cx, const char* filename, char16_t** src, size rv = NS_NewChannel(getter_AddRefs(scriptChannel), uri, nsContentUtils::GetSystemPrincipal(), - nsILoadInfo::SEC_NORMAL, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER); NS_ENSURE_SUCCESS(rv, rv); @@ -3081,7 +3081,7 @@ ReadSourceFromFilename(JSContext* cx, const char* filename, char16_t** src, size scriptChannel->SetContentType(NS_LITERAL_CSTRING("text/plain")); nsCOMPtr scriptStream; - rv = scriptChannel->Open(getter_AddRefs(scriptStream)); + rv = scriptChannel->Open2(getter_AddRefs(scriptStream)); NS_ENSURE_SUCCESS(rv, rv); uint64_t rawLen; @@ -3156,47 +3156,6 @@ static const JSWrapObjectCallbacks WrapObjectCallbacks = { xpc::WrapperFactory::PrepareForWrapping }; -/** - * Group JSCompartments into PerformanceGroups. - * - * - All JSCompartments from the same add-on belong to the same - * PerformanceGroup. - * - All JSCompartments from the same same webpage (including - * frames) belong to the same PerformanceGroup. - * - All other JSCompartments (normally, system add-ons) - * belong to to a big uncategorized PerformanceGroup. - */ -static void* -GetCurrentPerfGroupCallback(JSContext* cx) { - RootedObject global(cx, CurrentGlobalOrNull(cx)); - if (!global) { - // This can happen for the atom compartments, which is system - // code. - return nullptr; - } - - JSAddonId* addonId = AddonIdOfObject(global); - if (addonId) { - // If this is an add-on, use the id as key. - return addonId; - } - - // If the compartment belongs to a webpage, use the address of the - // topmost scriptable window, hence regrouping all frames of a - // window. - RefPtr win = WindowOrNull(global); - if (win) { - nsCOMPtr top = win->GetScriptableTop(); - if (!top) { - return nullptr; - } - return top.get(); - } - - // Otherwise, this is platform code, use `nullptr` as key. - return nullptr; -} - XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) : CycleCollectedJSRuntime(nullptr, JS::DefaultHeapMaxBytes, JS::DefaultNurseryBytes), mJSContextStack(new XPCJSContextStack(this)), @@ -3374,8 +3333,6 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) // Watch for the JS boolean options. ReloadPrefsCallback(nullptr, this); Preferences::RegisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this); - - JS_SetCurrentPerfGroupCallback(runtime, ::GetCurrentPerfGroupCallback); } // static diff --git a/layout/xul/tree/nsTreeBodyFrame.cpp b/layout/xul/tree/nsTreeBodyFrame.cpp index 34920a76aa..0664491218 100644 --- a/layout/xul/tree/nsTreeBodyFrame.cpp +++ b/layout/xul/tree/nsTreeBodyFrame.cpp @@ -44,6 +44,7 @@ #include "nsContainerFrame.h" #include "nsView.h" #include "nsViewManager.h" +#include "nsVariant.h" #include "nsWidgetsCID.h" #include "nsBoxFrame.h" #include "nsIURL.h" @@ -4631,10 +4632,7 @@ nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount) // Set 'count' data - the number of changed rows. propBag->SetPropertyAsInt32(NS_LITERAL_STRING("count"), aCount); - nsCOMPtr detailVariant( - do_CreateInstance("@mozilla.org/variant;1")); - if (!detailVariant) - return; + RefPtr detailVariant(new nsVariant()); detailVariant->SetAsISupports(propBag); treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeRowCountChanged"), @@ -4703,10 +4701,7 @@ nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx, int32_t aEndRowIdx, endColIdx); } - nsCOMPtr detailVariant( - do_CreateInstance("@mozilla.org/variant;1")); - if (!detailVariant) - return; + RefPtr detailVariant(new nsVariant()); detailVariant->SetAsISupports(propBag); treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeInvalidated"), diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 7e9eaf5ea6..1f99b1204c 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -429,6 +429,7 @@ pref("media.peerconnection.video.min_bitrate", 200); pref("media.peerconnection.video.start_bitrate", 300); pref("media.peerconnection.video.max_bitrate", 2000); #endif +pref("media.navigator.audio.fake_frequency", 1000); pref("media.navigator.permission.disabled", false); pref("media.peerconnection.default_iceservers", "[]"); pref("media.peerconnection.ice.loopback", false); // Set only for testing in offline environments. @@ -4446,6 +4447,8 @@ pref("webgl.angle.force-d3d11", false); pref("webgl.angle.force-warp", false); #endif +pref("gfx.offscreencanvas.enabled", false); + #ifdef MOZ_WIDGET_GONK pref("gfx.gralloc.fence-with-readpixels", false); #endif diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index 9f995ed2a2..f1c76081b3 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -52,7 +52,7 @@ using namespace mozilla; NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsAppStartup, Init) -NS_GENERIC_FACTORY_CONSTRUCTOR(nsPerformanceStatsService) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPerformanceStatsService, Init) #if defined(MOZ_HAS_TERMINATOR) NS_GENERIC_FACTORY_CONSTRUCTOR(nsTerminator) diff --git a/toolkit/components/osfile/NativeOSFileInternals.cpp b/toolkit/components/osfile/NativeOSFileInternals.cpp index 10822a5fed..ea5a4809c6 100644 --- a/toolkit/components/osfile/NativeOSFileInternals.cpp +++ b/toolkit/components/osfile/NativeOSFileInternals.cpp @@ -407,8 +407,8 @@ public: * alread_AddRefed to ensure that we do not manipulate main-thread * only refcounters off the main thread. */ - ErrorEvent(already_AddRefed&& aOnSuccess, - already_AddRefed&& aOnError, + ErrorEvent(nsMainThreadPtrHandle& aOnSuccess, + nsMainThreadPtrHandle& aOnError, already_AddRefed& aDiscardedResult, const nsACString& aOperation, int32_t aOSError) @@ -433,13 +433,13 @@ public: return NS_OK; } private: - // The callbacks. Maintained as nsRefPtr as they are generally + // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally // xpconnect values, which cannot be manipulated with nsCOMPtr off // the main thread. We store both the success callback and the // error callback to ensure that they are safely released on the // main thread. - RefPtr mOnSuccess; - RefPtr mOnError; + nsMainThreadPtrHandle mOnSuccess; + nsMainThreadPtrHandle mOnError; RefPtr mDiscardedResult; int32_t mOSError; nsCString mOperation; @@ -461,8 +461,8 @@ public: * we do not manipulate xpconnect refcounters off the main thread * (which is illegal). */ - SuccessEvent(already_AddRefed&& aOnSuccess, - already_AddRefed&& aOnError, + SuccessEvent(nsMainThreadPtrHandle& aOnSuccess, + nsMainThreadPtrHandle& aOnError, already_AddRefed& aResult) : mOnSuccess(aOnSuccess) , mOnError(aOnError) @@ -483,13 +483,13 @@ public: return NS_OK; } private: - // The callbacks. Maintained as nsRefPtr as they are generally + // The callbacks. Maintained as nsMainThreadPtrHandle as they are generally // xpconnect values, which cannot be manipulated with nsCOMPtr off // the main thread. We store both the success callback and the // error callback to ensure that they are safely released on the // main thread. - RefPtr mOnSuccess; - RefPtr mOnError; + nsMainThreadPtrHandle mOnSuccess; + nsMainThreadPtrHandle mOnError; RefPtr mResult; }; @@ -501,8 +501,8 @@ public: */ class AbstractDoEvent: public nsRunnable { public: - AbstractDoEvent(already_AddRefed& aOnSuccess, - already_AddRefed& aOnError) + AbstractDoEvent(nsMainThreadPtrHandle& aOnSuccess, + nsMainThreadPtrHandle& aOnError) : mOnSuccess(aOnSuccess) , mOnError(aOnError) #if defined(DEBUG) @@ -519,8 +519,8 @@ public: already_AddRefed&& aDiscardedResult, int32_t aOSError = 0) { Resolve(); - RefPtr event = new ErrorEvent(mOnSuccess.forget(), - mOnError.forget(), + RefPtr event = new ErrorEvent(mOnSuccess, + mOnError, aDiscardedResult, aOperation, aOSError); @@ -539,8 +539,8 @@ public: */ void Succeed(already_AddRefed&& aResult) { Resolve(); - RefPtr event = new SuccessEvent(mOnSuccess.forget(), - mOnError.forget(), + RefPtr event = new SuccessEvent(mOnSuccess, + mOnError, aResult); nsresult rv = NS_DispatchToMainThread(event); if (NS_FAILED(rv)) { @@ -566,8 +566,8 @@ private: } private: - RefPtr mOnSuccess; - RefPtr mOnError; + nsMainThreadPtrHandle mOnSuccess; + nsMainThreadPtrHandle mOnError; #if defined(DEBUG) // |true| once the action is complete bool mResolved; @@ -587,8 +587,8 @@ public: */ AbstractReadEvent(const nsAString& aPath, const uint64_t aBytes, - already_AddRefed& aOnSuccess, - already_AddRefed& aOnError) + nsMainThreadPtrHandle& aOnSuccess, + nsMainThreadPtrHandle& aOnError) : AbstractDoEvent(aOnSuccess, aOnError) , mPath(aPath) , mBytes(aBytes) @@ -736,8 +736,8 @@ class DoReadToTypedArrayEvent final : public AbstractReadEvent { public: DoReadToTypedArrayEvent(const nsAString& aPath, const uint32_t aBytes, - already_AddRefed&& aOnSuccess, - already_AddRefed&& aOnError) + nsMainThreadPtrHandle& aOnSuccess, + nsMainThreadPtrHandle& aOnError) : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) , mResult(new TypedArrayResult(TimeStamp::Now())) @@ -774,8 +774,8 @@ public: DoReadToStringEvent(const nsAString& aPath, const nsACString& aEncoding, const uint32_t aBytes, - already_AddRefed&& aOnSuccess, - already_AddRefed&& aOnError) + nsMainThreadPtrHandle& aOnSuccess, + nsMainThreadPtrHandle& aOnError) : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) , mEncoding(aEncoding) , mResult(new StringResult(TimeStamp::Now())) @@ -890,17 +890,21 @@ NativeOSFileInternalsService::Read(const nsAString& aPath, // Prepare the off main thread event and dispatch it nsCOMPtr onSuccess(aOnSuccess); + nsMainThreadPtrHandle onSuccessHandle( + new nsMainThreadPtrHolder(onSuccess)); nsCOMPtr onError(aOnError); + nsMainThreadPtrHandle onErrorHandle( + new nsMainThreadPtrHolder(onError)); RefPtr event; if (encoding.IsEmpty()) { event = new DoReadToTypedArrayEvent(aPath, bytes, - onSuccess.forget(), - onError.forget()); + onSuccessHandle, + onErrorHandle); } else { event = new DoReadToStringEvent(aPath, encoding, bytes, - onSuccess.forget(), - onError.forget()); + onSuccessHandle, + onErrorHandle); } nsresult rv; diff --git a/toolkit/components/perfmonitoring/PerformanceStats.jsm b/toolkit/components/perfmonitoring/PerformanceStats.jsm index cee430374c..4fe04c268c 100644 --- a/toolkit/components/perfmonitoring/PerformanceStats.jsm +++ b/toolkit/components/perfmonitoring/PerformanceStats.jsm @@ -49,7 +49,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "finalizer", // and that we can release/close the probes it holds. const FINALIZATION_TOPIC = "performancemonitor-finalize"; -const PROPERTIES_META_IMMUTABLE = ["addonId", "isSystem", "isChildProcess", "groupId"]; +const PROPERTIES_META_IMMUTABLE = ["addonId", "isSystem", "isChildProcess", "groupId", "processId"]; const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title", "name"]; // How long we wait for children processes to respond. diff --git a/toolkit/components/perfmonitoring/nsIPerformanceStats.idl b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl index dc1d11ba84..d0e9e6dfbf 100644 --- a/toolkit/components/perfmonitoring/nsIPerformanceStats.idl +++ b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl @@ -22,7 +22,7 @@ * All values are monotonic and are updated only when * `nsIPerformanceStatsService.isStopwatchActive` is `true`. */ -[scriptable, uuid(1bc2d016-e9ae-4186-97c6-9478eddda245)] +[scriptable, uuid(f171c901-1888-4087-a002-c2751e510f92)] interface nsIPerformanceStats: nsISupports { /** * An identifier unique to the component. @@ -33,21 +33,7 @@ interface nsIPerformanceStats: nsISupports { readonly attribute AString groupId; /** - * If this component is part of a larger component, the larger - * component. Otherwise, null. - * - * As of this writing, there can be at most two levels of components: - * - compartments (a single module, iframe, etc.); - * - groups (an entire add-on, an entire webpage, etc.). - */ - readonly attribute AString parentId; - - /** - * The name of the component: - * - for the process itself, ""; - * - for platform code, ""; - * - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar"); - * - for a webpage, the url of the page. + * A somewhat human-readable name for the component. */ readonly attribute AString name; @@ -63,12 +49,6 @@ interface nsIPerformanceStats: nsISupports { */ readonly attribute uint64_t windowId; - /** - * If the component is code executed in a window, the title of the topmost - * window (i.e. the tab), otherwise an empty string. - */ - readonly attribute AString title; - /** * Total amount of time spent executing code in this group, in * microseconds. @@ -91,6 +71,11 @@ interface nsIPerformanceStats: nsISupports { */ readonly attribute bool isSystem; + /** + * The process running this group. + */ + readonly attribute unsigned long long processId; + /** * Jank indicator. * @@ -104,7 +89,7 @@ interface nsIPerformanceStats: nsISupports { /** * A snapshot of the performance data of the process. */ -[scriptable, uuid(29ecebd0-908a-4b34-8f62-a6015dea1141)] +[scriptable, uuid(0ac38e2a-2613-4e3f-9f21-95f085c177de)] interface nsIPerformanceSnapshot: nsISupports { /** * Data on all individual components. @@ -122,7 +107,7 @@ interface nsIPerformanceSnapshot: nsISupports { nsIPerformanceStats getProcessData(); }; -[scriptable, builtinclass, uuid(60973d54-13e2-455c-a3c6-84dea5dfc8b9)] +[scriptable, builtinclass, uuid(aad18f7c-9ff7-4e22-8cd1-60ab0b57c698)] interface nsIPerformanceStatsService : nsISupports { /** * `true` if we should monitor CPOW, `false` otherwise. diff --git a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp index 7093e51555..aca248bf0d 100644 --- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp +++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp @@ -12,6 +12,7 @@ #include "nsCOMArray.h" #include "nsIMutableArray.h" +#include "nsReadableUtils.h" #include "jsapi.h" #include "nsJSUtils.h" @@ -22,131 +23,291 @@ #include "nsGlobalWindow.h" #include "mozilla/unused.h" +#include "mozilla/ArrayUtils.h" #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #if defined(XP_WIN) -#include "windows.h" +#include +#include #else #include -#endif +#endif // defined(XP_WIN) -class nsPerformanceStats: public nsIPerformanceStats { +#if defined(XP_MACOSX) +#include +#include +#include +#include +#include +#include +#endif // defined(XP_MACOSX) + +#if defined(XP_LINUX) +#include +#include +#endif // defined(XP_LINUX) +/* ------------------------------------------------------ + * + * Utility functions. + * + */ + +namespace { + +/** + * Get the private window for the current compartment. + * + * @return null if the code is not executed in a window or in + * case of error, a nsPIDOMWindow otherwise. + */ +already_AddRefed +GetPrivateWindow(JSContext* cx) { + nsCOMPtr win = xpc::CurrentWindowOrNull(cx); + if (!win) { + return nullptr; + } + + win = win->GetOuterWindow(); + if (!win) { + return nullptr; + } + + nsCOMPtr top = win->GetTop(); + if (!top) { + return nullptr; + } + + return top.forget(); +} + +bool +URLForGlobal(JSContext* cx, JS::Handle global, nsAString& url) { + nsCOMPtr principal = nsContentUtils::ObjectPrincipal(global); + if (!principal) { + return false; + } + + nsCOMPtr uri; + nsresult rv = principal->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv) || !uri) { + return false; + } + + nsAutoCString spec; + rv = uri->GetSpec(spec); + if (NS_FAILED(rv)) { + return false; + } + + url.Assign(NS_ConvertUTF8toUTF16(spec)); + return true; +} + +/** + * Extract a somewhat human-readable name from the current context. + */ +void +CompartmentName(JSContext* cx, JS::Handle global, nsAString& name) { + // Attempt to use the URL as name. + if (URLForGlobal(cx, global, name)) { + return; + } + + // Otherwise, fallback to XPConnect's less readable but more + // complete naming scheme. + nsAutoCString cname; + xpc::GetCurrentCompartmentName(cx, cname); + name.Assign(NS_ConvertUTF8toUTF16(cname)); +} + +/** + * Generate a unique-to-the-application identifier for a group. + */ +void +GenerateUniqueGroupId(const JSRuntime* rt, uint64_t uid, uint64_t processId, nsAString& groupId) { + uint64_t runtimeId = reinterpret_cast(rt); + + groupId.AssignLiteral("process: "); + groupId.AppendInt(processId); + groupId.AppendLiteral(", thread: "); + groupId.AppendInt(runtimeId); + groupId.AppendLiteral(", group: "); + groupId.AppendInt(uid); +} + +} // namespace + + + +/* ------------------------------------------------------ + * + * struct PerformanceData + * + */ + +PerformanceData::PerformanceData() + : mTotalUserTime(0) + , mTotalSystemTime(0) + , mTotalCPOWTime(0) + , mTicks(0) +{ + mozilla::PodArrayZero(mDurations); +} + +/* ------------------------------------------------------ + * + * class nsPerformanceGroupDetails + * + */ + +const nsAString& +nsPerformanceGroupDetails::Name() const { + return mName; +} + +const nsAString& +nsPerformanceGroupDetails::GroupId() const { + return mGroupId; +} + +const nsAString& +nsPerformanceGroupDetails::AddonId() const { + return mAddonId; +} + +uint64_t +nsPerformanceGroupDetails::WindowId() const { + return mWindowId; +} + +uint64_t +nsPerformanceGroupDetails::ProcessId() const { + return mProcessId; +} + +bool +nsPerformanceGroupDetails::IsSystem() const { + return mIsSystem; +} + +bool +nsPerformanceGroupDetails::IsAddon() const { + return mAddonId.Length() != 0; +} + +bool +nsPerformanceGroupDetails::IsWindow() const { + return mWindowId != 0; +} + +/* ------------------------------------------------------ + * + * struct nsPerformanceStats + * + */ + +class nsPerformanceStats: public nsIPerformanceStats, + public nsPerformanceGroupDetails +{ public: nsPerformanceStats(const nsAString& aName, - nsIPerformanceStats* aParent, const nsAString& aGroupId, const nsAString& aAddonId, - const nsAString& aTitle, const uint64_t aWindowId, + const uint64_t aProcessId, const bool aIsSystem, - const js::PerformanceData& aPerformanceData) - : mName(aName) - , mGroupId(aGroupId) - , mAddonId(aAddonId) - , mTitle(aTitle) - , mWindowId(aWindowId) - , mIsSystem(aIsSystem) + const PerformanceData& aPerformanceData) + : nsPerformanceGroupDetails(aName, aGroupId, aAddonId, aWindowId, aProcessId, aIsSystem) + , mPerformanceData(aPerformanceData) + { + } + nsPerformanceStats(const nsPerformanceGroupDetails& item, + const PerformanceData& aPerformanceData) + : nsPerformanceGroupDetails(item) , mPerformanceData(aPerformanceData) { - if (aParent) { - mozilla::DebugOnly rv = aParent->GetGroupId(mParentId); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - } } - explicit nsPerformanceStats() {} NS_DECL_ISUPPORTS /* readonly attribute AString name; */ NS_IMETHOD GetName(nsAString& aName) override { - aName.Assign(mName); + aName.Assign(nsPerformanceGroupDetails::Name()); return NS_OK; }; /* readonly attribute AString groupId; */ NS_IMETHOD GetGroupId(nsAString& aGroupId) override { - aGroupId.Assign(mGroupId); - return NS_OK; - }; - - /* readonly attribute AString parentId; */ - NS_IMETHOD GetParentId(nsAString& aParentId) override { - aParentId.Assign(mParentId); + aGroupId.Assign(nsPerformanceGroupDetails::GroupId()); return NS_OK; }; /* readonly attribute AString addonId; */ NS_IMETHOD GetAddonId(nsAString& aAddonId) override { - aAddonId.Assign(mAddonId); + aAddonId.Assign(nsPerformanceGroupDetails::AddonId()); return NS_OK; }; /* readonly attribute uint64_t windowId; */ NS_IMETHOD GetWindowId(uint64_t *aWindowId) override { - *aWindowId = mWindowId; - return NS_OK; - } - - /* readonly attribute AString title; */ - NS_IMETHOD GetTitle(nsAString & aTitle) override { - aTitle.Assign(mTitle); + *aWindowId = nsPerformanceGroupDetails::WindowId(); return NS_OK; } /* readonly attribute bool isSystem; */ NS_IMETHOD GetIsSystem(bool *_retval) override { - *_retval = mIsSystem; + *_retval = nsPerformanceGroupDetails::IsSystem(); return NS_OK; } /* readonly attribute unsigned long long totalUserTime; */ NS_IMETHOD GetTotalUserTime(uint64_t *aTotalUserTime) override { - *aTotalUserTime = mPerformanceData.totalUserTime; + *aTotalUserTime = mPerformanceData.mTotalUserTime; return NS_OK; }; /* readonly attribute unsigned long long totalSystemTime; */ NS_IMETHOD GetTotalSystemTime(uint64_t *aTotalSystemTime) override { - *aTotalSystemTime = mPerformanceData.totalSystemTime; + *aTotalSystemTime = mPerformanceData.mTotalSystemTime; return NS_OK; }; /* readonly attribute unsigned long long totalCPOWTime; */ NS_IMETHOD GetTotalCPOWTime(uint64_t *aCpowTime) override { - *aCpowTime = mPerformanceData.totalCPOWTime; + *aCpowTime = mPerformanceData.mTotalCPOWTime; return NS_OK; }; /* readonly attribute unsigned long long ticks; */ NS_IMETHOD GetTicks(uint64_t *aTicks) override { - *aTicks = mPerformanceData.ticks; + *aTicks = mPerformanceData.mTicks; return NS_OK; }; /* void getDurations (out unsigned long aCount, [array, size_is (aCount), retval] out unsigned long long aNumberOfOccurrences); */ - NS_IMETHODIMP GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) override { - const size_t length = mozilla::ArrayLength(mPerformanceData.durations); + NS_IMETHOD GetDurations(uint32_t *aCount, uint64_t **aNumberOfOccurrences) override { + const size_t length = mozilla::ArrayLength(mPerformanceData.mDurations); if (aCount) { *aCount = length; } *aNumberOfOccurrences = new uint64_t[length]; for (size_t i = 0; i < length; ++i) { - (*aNumberOfOccurrences)[i] = mPerformanceData.durations[i]; + (*aNumberOfOccurrences)[i] = mPerformanceData.mDurations[i]; } return NS_OK; }; -private: - nsString mName; - nsString mParentId; - nsString mGroupId; - nsString mAddonId; - nsString mTitle; - uint64_t mWindowId; - bool mIsSystem; + /* + readonly attribute unsigned long long processId; + */ + NS_IMETHODIMP GetProcessId(uint64_t* processId) override { + *processId = nsPerformanceGroupDetails::ProcessId(); + return NS_OK; + } - js::PerformanceData mPerformanceData; +private: + PerformanceData mPerformanceData; virtual ~nsPerformanceStats() {} }; @@ -154,265 +315,51 @@ private: NS_IMPL_ISUPPORTS(nsPerformanceStats, nsIPerformanceStats) +/* ------------------------------------------------------ + * + * struct nsPerformanceSnapshot + * + */ + class nsPerformanceSnapshot : public nsIPerformanceSnapshot { public: NS_DECL_ISUPPORTS NS_DECL_NSIPERFORMANCESNAPSHOT - nsPerformanceSnapshot(); - nsresult Init(JSContext*, uint64_t processId); -private: - virtual ~nsPerformanceSnapshot(); + nsPerformanceSnapshot() {} /** - * Import a `js::PerformanceStats` as a `nsIPerformanceStats`. - * - * Precondition: this method assumes that we have entered the JSCompartment for which data `c` - * has been collected. - * - * `cx` may be `nullptr` if we are importing the statistics for the - * entire process, rather than the statistics for a specific set of - * compartments. + * Append statistics to the list of components data. */ - already_AddRefed ImportStats(JSContext* cx, const js::PerformanceData& data, uint64_t uid, nsIPerformanceStats* parent); + void AppendComponentsStats(nsIPerformanceStats* stats); /** - * Callbacks for iterating through the `PerformanceStats` of a runtime. + * Set the statistics attached to process data. */ - bool IterPerformanceStatsCallbackInternal(JSContext* cx, - const js::PerformanceData& ownStats, const uint64_t ownId, - const uint64_t* parentId); - static bool IterPerformanceStatsCallback(JSContext* cx, - const js::PerformanceData& ownStats, const uint64_t ownId, - const uint64_t* parentId, - void* self); - - // If the context represents a window, extract the title and window ID. - // Otherwise, extract "" and 0. - static void GetWindowData(JSContext*, - nsString& title, - uint64_t* windowId); - void GetGroupId(JSContext*, - uint64_t uid, - nsString& groupId); - - static void GetName(JSContext*, - JS::Handle global, - nsString& name); - - // If the context presents an add-on, extract the addon ID. - // Otherwise, extract "". - static void GetAddonId(JSContext*, - JS::Handle global, - nsAString& addonId); - - // Determine whether a context is part of the system principals. - static bool GetIsSystem(JSContext*, - JS::Handle global); + void SetProcessStats(nsIPerformanceStats* group); private: + virtual ~nsPerformanceSnapshot() {} + +private: + /** + * The data for all components. + */ nsCOMArray mComponentsData; + + /** + * The data for the process. + */ nsCOMPtr mProcessData; - nsBaseHashtable, nsCOMPtr > mCachedStats; - uint64_t mProcessId; }; NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot) -nsPerformanceSnapshot::nsPerformanceSnapshot() -{ -} -nsPerformanceSnapshot::~nsPerformanceSnapshot() -{ -} - -/* static */ void -nsPerformanceSnapshot::GetWindowData(JSContext* cx, - nsString& title, - uint64_t* windowId) -{ - MOZ_ASSERT(windowId); - - title.SetIsVoid(true); - *windowId = 0; - - nsCOMPtr win = xpc::CurrentWindowOrNull(cx); - if (!win) { - return; - } - - nsCOMPtr top; - top = win->GetTop(); - if (!top) { - return; - } - - nsCOMPtr ptop = do_QueryInterface(top); - if (!ptop) { - return; - } - - nsCOMPtr doc = ptop->GetExtantDoc(); - if (!doc) { - return; - } - - doc->GetTitle(title); - *windowId = ptop->WindowID(); -} - -/* static */ void -nsPerformanceSnapshot::GetAddonId(JSContext*, - JS::Handle global, - nsAString& addonId) -{ - addonId.AssignLiteral(""); - - JSAddonId* jsid = AddonIdOfObject(global); - if (!jsid) { - return; - } - AssignJSFlatString(addonId, (JSFlatString*)jsid); -} - -void -nsPerformanceSnapshot::GetGroupId(JSContext* cx, - uint64_t uid, - nsString& groupId) -{ - JSRuntime* rt = JS_GetRuntime(cx); - uint64_t runtimeId = reinterpret_cast(rt); - - groupId.AssignLiteral("process: "); - groupId.AppendInt(mProcessId); - groupId.AppendLiteral(", thread: "); - groupId.AppendInt(runtimeId); - groupId.AppendLiteral(", group: "); - groupId.AppendInt(uid); -} - -/* static */ bool -nsPerformanceSnapshot::GetIsSystem(JSContext*, - JS::Handle global) -{ - return nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global)); -} - -/* static */ -void -nsPerformanceSnapshot::GetName(JSContext* cx, - JS::Handle global, - nsString& name) -{ - nsAutoCString cname; - do { - // Attempt to use the URL as name. - nsCOMPtr principal = nsContentUtils::ObjectPrincipal(global); - if (!principal) { - break; - } - - nsCOMPtr uri; - nsresult rv = principal->GetURI(getter_AddRefs(uri)); - if (NS_FAILED(rv) || !uri) { - break; - } - - rv = uri->GetSpec(cname); - if (NS_FAILED(rv)) { - break; - } - - name.Assign(NS_ConvertUTF8toUTF16(cname)); - return; - } while(false); - xpc::GetCurrentCompartmentName(cx, cname); - name.Assign(NS_ConvertUTF8toUTF16(cname)); -} - -already_AddRefed -nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance, const uint64_t uid, nsIPerformanceStats* parent) { - if (performance.ticks == 0) { - // Ignore compartments with no activity. - return nullptr; - } - JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); - - if (!global) { - // While it is possible for a compartment to have no global - // (e.g. atoms), this compartment is not very interesting for us. - return nullptr; - } - - nsString groupId; - GetGroupId(cx, uid, groupId); - - nsString addonId; - GetAddonId(cx, global, addonId); - - nsString title; - uint64_t windowId; - GetWindowData(cx, title, &windowId); - - nsString name; - GetName(cx, global, name); - - bool isSystem = GetIsSystem(cx, global); - - nsCOMPtr result = - new nsPerformanceStats(name, parent, groupId, addonId, title, windowId, isSystem, performance); - return result.forget(); -} - -/*static*/ bool -nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx, - const js::PerformanceData& stats, const uint64_t id, - const uint64_t* parentId, - void* self) { - return reinterpret_cast(self)->IterPerformanceStatsCallbackInternal(cx, stats, id, parentId); -} - -bool -nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, - const js::PerformanceData& stats, const uint64_t id, - const uint64_t* parentId) { - - nsCOMPtr parent = parentId ? mCachedStats.Get(*parentId) : nullptr; - nsCOMPtr result = ImportStats(cx, stats, id, parent); - if (result) { - mComponentsData.AppendElement(result); - mCachedStats.Put(id, result); - } - - return true; -} - -nsresult -nsPerformanceSnapshot::Init(JSContext* cx, uint64_t processId) { - mProcessId = processId; - js::PerformanceData processStats; - if (!js::IterPerformanceStats(cx, nsPerformanceSnapshot::IterPerformanceStatsCallback, &processStats, this)) { - return NS_ERROR_UNEXPECTED; - } - - nsAutoString processGroupId; - processGroupId.AssignLiteral("process: "); - processGroupId.AppendInt(processId); - mProcessData = new nsPerformanceStats(NS_LITERAL_STRING(""), // name - nullptr, // parent - processGroupId, // group id - NS_LITERAL_STRING(""), // add-on id - NS_LITERAL_STRING(""), // title - 0, // window id - true, // isSystem - processStats); - return NS_OK; -} - - -NS_IMETHODIMP nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents) +/* nsIArray getComponentsData (); */ +NS_IMETHODIMP +nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents) { const size_t length = mComponentsData.Length(); nsCOMPtr components = do_CreateInstance(NS_ARRAY_CONTRACTID); @@ -425,12 +372,31 @@ NS_IMETHODIMP nsPerformanceSnapshot::GetComponentsData(nsIArray * *aComponents) return NS_OK; } -NS_IMETHODIMP nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess) +/* nsIPerformanceStats getProcessData (); */ +NS_IMETHODIMP +nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProcess) { NS_IF_ADDREF(*aProcess = mProcessData); return NS_OK; } +void +nsPerformanceSnapshot::AppendComponentsStats(nsIPerformanceStats* stats) +{ + mComponentsData.AppendElement(stats); +} + +void +nsPerformanceSnapshot::SetProcessStats(nsIPerformanceStats* stats) +{ + mProcessData = stats; +} + +/* ------------------------------------------------------ + * + * class nsPerformanceStatsService + * + */ NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService, nsIObserver) @@ -440,27 +406,144 @@ nsPerformanceStatsService::nsPerformanceStatsService() #else : mProcessId(getpid()) #endif + , mRuntime(xpc::GetJSRuntime()) + , mUIdCounter(0) + , mTopGroup(nsPerformanceGroup::Make(mRuntime, + this, + NS_LITERAL_STRING(""), // name + NS_LITERAL_STRING(""), // addonid + 0, // windowId + mProcessId, + true, // isSystem + nsPerformanceGroup::GroupScope::RUNTIME // scope + )) , mProcessStayed(0) , mProcessMoved(0) -{ - nsCOMPtr obs = mozilla::services::GetObserverService(); - if (obs) { - mozilla::unused << obs->AddObserver(this, "profile-before-shutdown", false); - } -} + , mProcessUpdateCounter(0) + , mIsMonitoringPerCompartment(false) + +{ } nsPerformanceStatsService::~nsPerformanceStatsService() +{ } + +/** + * Clean up the service. + * + * Called during shutdown. Idempotent. + */ +void +nsPerformanceStatsService::Dispose() { + // Make sure that we do not accidentally destroy `this` while we are + // cleaning up back references. + RefPtr kungFuDeathGrip(this); + + // Disconnect from nsIObserverService. + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "profile-before-change"); + obs->RemoveObserver(this, "quit-application"); + obs->RemoveObserver(this, "quit-application-granted"); + obs->RemoveObserver(this, "content-child-shutdown"); + } + + // Clear up and disconnect from JSAPI. + js::DisposePerformanceMonitoring(mRuntime); + + mozilla::unused << js::SetStopwatchIsMonitoringCPOW(mRuntime, false); + mozilla::unused << js::SetStopwatchIsMonitoringJank(mRuntime, false); + + mozilla::unused << js::SetStopwatchStartCallback(mRuntime, nullptr, nullptr); + mozilla::unused << js::SetStopwatchCommitCallback(mRuntime, nullptr, nullptr); + mozilla::unused << js::SetGetPerformanceGroupsCallback(mRuntime, nullptr, nullptr); + + // At this stage, the JS VM may still be holding references to + // instances of PerformanceGroup on the stack. To let the service be + // collected, we need to break the references from these groups to + // `this`. + mTopGroup->Dispose(); + + // Copy references to the groups to a vector to ensure that we do + // not modify the hashtable while iterating it. + GroupVector groups; + for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) { + groups.append(iter.Get()->GetKey()); + } + for (auto iter = groups.begin(); iter < groups.end(); iter++) { + RefPtr group = *iter; + group->Dispose(); + } + + // Any remaining references to PerformanceGroup will be released as + // the VM unrolls the stack. If there are any nested event loops, + // this may take time. } -//[implicit_jscontext] attribute bool isMonitoringCPOW; -NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive) +nsresult +nsPerformanceStatsService::Init() +{ + nsresult rv = InitInternal(); + if (NS_FAILED(rv)) { + // Attempt to clean up. + Dispose(); + } + return rv; +} + +nsresult +nsPerformanceStatsService::InitInternal() +{ + // Make sure that we release everything during shutdown. + // We are a bit defensive here, as we know that some strange behavior can break the + // regular shutdown order. + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "profile-before-change", false); + obs->AddObserver(this, "quit-application-granted", false); + obs->AddObserver(this, "quit-application", false); + obs->AddObserver(this, "content-child-shutdown", false); + } + + // Connect to JSAPI. + if (!js::SetStopwatchStartCallback(mRuntime, StopwatchStartCallback, this)) { + return NS_ERROR_UNEXPECTED; + } + if (!js::SetStopwatchCommitCallback(mRuntime, StopwatchCommitCallback, this)) { + return NS_ERROR_UNEXPECTED; + } + if (!js::SetGetPerformanceGroupsCallback(mRuntime, GetPerformanceGroupsCallback, this)) { + return NS_ERROR_UNEXPECTED; + } + + mTopGroup->setIsActive(true); + return NS_OK; +} + +// Observe shutdown events. +NS_IMETHODIMP +nsPerformanceStatsService::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *aData) +{ + MOZ_ASSERT(strcmp(aTopic, "profile-before-change") == 0 + || strcmp(aTopic, "quit-application") == 0 + || strcmp(aTopic, "quit-application-granted") == 0 + || strcmp(aTopic, "content-child-shutdown") == 0); + + Dispose(); + return NS_OK; +} + +/* [implicit_jscontext] attribute bool isMonitoringCPOW; */ +NS_IMETHODIMP +nsPerformanceStatsService::GetIsMonitoringCPOW(JSContext* cx, bool *aIsStopwatchActive) { JSRuntime *runtime = JS_GetRuntime(cx); *aIsStopwatchActive = js::GetStopwatchIsMonitoringCPOW(runtime); return NS_OK; } -NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive) +NS_IMETHODIMP +nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool aIsStopwatchActive) { JSRuntime *runtime = JS_GetRuntime(cx); if (!js::SetStopwatchIsMonitoringCPOW(runtime, aIsStopwatchActive)) { @@ -468,13 +551,17 @@ NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringCPOW(JSContext* cx, bool } return NS_OK; } -NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive) + +/* [implicit_jscontext] attribute bool isMonitoringJank; */ +NS_IMETHODIMP +nsPerformanceStatsService::GetIsMonitoringJank(JSContext* cx, bool *aIsStopwatchActive) { JSRuntime *runtime = JS_GetRuntime(cx); *aIsStopwatchActive = js::GetStopwatchIsMonitoringJank(runtime); return NS_OK; } -NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive) +NS_IMETHODIMP +nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive) { JSRuntime *runtime = JS_GetRuntime(cx); if (!js::SetStopwatchIsMonitoringJank(runtime, aIsStopwatchActive)) { @@ -482,51 +569,450 @@ NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool } return NS_OK; } -NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext* cx, bool *aIsStopwatchActive) + +/* [implicit_jscontext] attribute bool isMonitoringPerCompartment; */ +NS_IMETHODIMP +nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext*, bool *aIsMonitoringPerCompartment) { - JSRuntime *runtime = JS_GetRuntime(cx); - *aIsStopwatchActive = js::GetStopwatchIsMonitoringPerCompartment(runtime); + *aIsMonitoringPerCompartment = mIsMonitoringPerCompartment; return NS_OK; } -NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext* cx, bool aIsStopwatchActive) +NS_IMETHODIMP +nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext*, bool aIsMonitoringPerCompartment) { - JSRuntime *runtime = JS_GetRuntime(cx); - if (!js::SetStopwatchIsMonitoringPerCompartment(runtime, aIsStopwatchActive)) { - return NS_ERROR_OUT_OF_MEMORY; + if (aIsMonitoringPerCompartment == mIsMonitoringPerCompartment) { + return NS_OK; } + + // Relatively slow update: walk the entire lost of performance groups, + // update the active flag of those that have changed. + // + // Alternative strategies could be envisioned to make the update + // much faster, at the expense of the speed of calling `isActive()`, + // (e.g. deferring `isActive()` to the nsPerformanceStatsService), + // but we expect that `isActive()` can be called thousands of times + // per second, while `SetIsMonitoringPerCompartment` is not called + // at all during most Firefox runs. + + for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) { + RefPtr group = iter.Get()->GetKey(); + if (group->Scope() == nsPerformanceGroup::GroupScope::COMPARTMENT) { + group->setIsActive(aIsMonitoringPerCompartment); + } + } + mIsMonitoringPerCompartment = aIsMonitoringPerCompartment; return NS_OK; } -NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot) +nsresult +nsPerformanceStatsService::UpdateTelemetry() +{ + // Promote everything to floating-point explicitly before dividing. + const double processStayed = mProcessStayed; + const double processMoved = mProcessMoved; + + if (processStayed <= 0 || processMoved <= 0 || processStayed + processMoved <= 0) { + // Overflow/underflow/nothing to report + return NS_OK; + } + + const double proportion = (100 * processStayed) / (processStayed + processMoved); + if (proportion < 0 || proportion > 100) { + // Overflow/underflow + return NS_OK; + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED, (uint32_t)proportion); + return NS_OK; +} + + +/* static */ nsIPerformanceStats* +nsPerformanceStatsService::GetStatsForGroup(const js::PerformanceGroup* group) +{ + return GetStatsForGroup(nsPerformanceGroup::Get(group)); +} + +/* static */ nsIPerformanceStats* +nsPerformanceStatsService::GetStatsForGroup(const nsPerformanceGroup* group) +{ + return new nsPerformanceStats(*group, group->data); +} + +/* [implicit_jscontext] nsIPerformanceSnapshot getSnapshot (); */ +NS_IMETHODIMP +nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot) { RefPtr snapshot = new nsPerformanceSnapshot(); - nsresult rv = snapshot->Init(cx, mProcessId); - if (NS_FAILED(rv)) { - return rv; + snapshot->SetProcessStats(GetStatsForGroup(mTopGroup)); + + for (auto iter = mGroups.Iter(); !iter.Done(); iter.Next()) { + auto* entry = iter.Get(); + nsPerformanceGroup* group = entry->GetKey(); + if (group->isActive()) { + snapshot->AppendComponentsStats(GetStatsForGroup(group)); + } } js::GetPerfMonitoringTestCpuRescheduling(JS_GetRuntime(cx), &mProcessStayed, &mProcessMoved); + + if (++mProcessUpdateCounter % 10 == 0) { + mozilla::unused << UpdateTelemetry(); + } + snapshot.forget(aSnapshot); return NS_OK; } +uint64_t +nsPerformanceStatsService::GetNextId() { + return ++mUIdCounter; +} -/* void observe (in nsISupports aSubject, in string aTopic, in wstring aData); */ -NS_IMETHODIMP nsPerformanceStatsService::Observe(nsISupports *, const char *, const char16_t *) +/* static*/ bool +nsPerformanceStatsService::GetPerformanceGroupsCallback(JSContext* cx, JSGroupVector& out, void* closure) { + RefPtr self = reinterpret_cast(closure); + return self->GetPerformanceGroups(cx, out); +} + +bool +nsPerformanceStatsService::GetPerformanceGroups(JSContext* cx, JSGroupVector& out) { + JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + if (!global) { + // While it is possible for a compartment to have no global + // (e.g. atoms), this compartment is not very interesting for us. + return true; + } + + // All compartments belong to the top group. + out.append(mTopGroup); + + nsAutoString name; + CompartmentName(cx, global, name); + bool isSystem = nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global)); + + // Find out if the compartment is executed by an add-on. If so, its + // duration should count towards the total duration of the add-on. + JSAddonId* jsaddonId = AddonIdOfObject(global); + nsString addonId; + if (jsaddonId) { + AssignJSFlatString(addonId, (JSFlatString*)jsaddonId); + auto entry = mAddonIdToGroup.PutEntry(addonId); + if (!entry->mGroup) { + nsString addonName = name; + addonName.AppendLiteral(" (as addon "); + addonName.Append(addonId); + addonName.AppendLiteral(")"); + entry->mGroup = + nsPerformanceGroup::Make(mRuntime, this, + addonName, addonId, 0, + mProcessId, isSystem, + nsPerformanceGroup::GroupScope::ADDON); + } + out.append(entry->mGroup); + } + + // Find out if the compartment is executed by a window. If so, its + // duration should count towards the total duration of the window. + nsCOMPtr ptop = GetPrivateWindow(cx); + uint64_t windowId = 0; + if (ptop) { + windowId = ptop->WindowID(); + auto entry = mWindowIdToGroup.PutEntry(windowId); + if (!entry->mGroup) { + nsString windowName = name; + windowName.AppendLiteral(" (as window "); + windowName.AppendInt(windowId); + windowName.AppendLiteral(")"); + entry->mGroup = + nsPerformanceGroup::Make(mRuntime, this, + windowName, EmptyString(), windowId, + mProcessId, isSystem, + nsPerformanceGroup::GroupScope::WINDOW); + } + out.append(entry->mGroup); + } + + // All compartments have their own group. + auto group = + nsPerformanceGroup::Make(mRuntime, this, + name, addonId, windowId, + mProcessId, isSystem, + nsPerformanceGroup::GroupScope::COMPARTMENT); + out.append(group); + + return true; +} + +/*static*/ bool +nsPerformanceStatsService::StopwatchStartCallback(uint64_t iteration, void* closure) { + RefPtr self = reinterpret_cast(closure); + return self->StopwatchStart(iteration); +} + +bool +nsPerformanceStatsService::StopwatchStart(uint64_t iteration) { + mIteration = iteration; + + nsresult rv = GetResources(&mUserTimeStart, &mSystemTimeStart); + if (NS_FAILED(rv)) { + return false; + } + + return true; +} + +/*static*/ bool +nsPerformanceStatsService::StopwatchCommitCallback(uint64_t iteration, JSGroupVector& recentGroups, void* closure) { + RefPtr self = reinterpret_cast(closure); + return self->StopwatchCommit(iteration, recentGroups); +} + +bool +nsPerformanceStatsService::StopwatchCommit(uint64_t iteration, JSGroupVector& recentGroups) { - // Upload telemetry - nsCOMPtr obs = mozilla::services::GetObserverService(); - if (obs) { - mozilla::unused << obs->RemoveObserver(this, "profile-before-shutdown"); + MOZ_ASSERT(iteration == mIteration); + MOZ_ASSERT(recentGroups.length() > 0); + + uint64_t userTimeStop, systemTimeStop; + nsresult rv = GetResources(&userTimeStop, &systemTimeStop); + if (NS_FAILED(rv)) { + return false; } - if (mProcessStayed + mProcessMoved == 0) { - // Nothing to report. - return NS_OK; + // `GetResources` is not guaranteed to be monotonic, so round up + // any negative result to 0 milliseconds. + uint64_t userTimeDelta = 0; + if (userTimeStop > mUserTimeStart) + userTimeDelta = userTimeStop - mUserTimeStart; + + uint64_t systemTimeDelta = 0; + if (systemTimeStop > mSystemTimeStart) + systemTimeDelta = systemTimeStop - mSystemTimeStart; + + MOZ_ASSERT(mTopGroup->isUsedInThisIteration()); + const uint64_t totalRecentCycles = mTopGroup->recentCycles(iteration); + + // We should only reach this stage if `group` has had some activity. + MOZ_ASSERT(mTopGroup->recentTicks(iteration) > 0); + for (auto iter = recentGroups.begin(); iter != recentGroups.end(); ++iter) { + RefPtr group = nsPerformanceGroup::Get(*iter); + CommitGroup(iteration, userTimeDelta, systemTimeDelta, totalRecentCycles, group); } - const uint32_t proportion = ( 100 * mProcessStayed ) / ( mProcessStayed + mProcessMoved ); - mozilla::Telemetry::Accumulate("PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED", proportion); + + // Make sure that `group` was treated along with the other items of `recentGroups`. + MOZ_ASSERT(!mTopGroup->isUsedInThisIteration()); + MOZ_ASSERT(mTopGroup->recentTicks(iteration) == 0); + return true; +} + +void +nsPerformanceStatsService::CommitGroup(uint64_t iteration, + uint64_t totalUserTimeDelta, uint64_t totalSystemTimeDelta, + uint64_t totalCyclesDelta, nsPerformanceGroup* group) { + + MOZ_ASSERT(group->isUsedInThisIteration()); + + const uint64_t ticksDelta = group->recentTicks(iteration); + const uint64_t cpowTimeDelta = group->recentCPOW(iteration); + const uint64_t cyclesDelta = group->recentCycles(iteration); + group->resetRecentData(); + + // We have now performed all cleanup and may `return` at any time without fear of leaks. + + if (group->iteration() != iteration) { + // Stale data, don't commit. + return; + } + + // When we add a group as changed, we immediately set its + // `recentTicks` from 0 to 1. If we have `ticksDelta == 0` at + // this stage, we have already called `resetRecentData` but we + // haven't removed it from the list. + MOZ_ASSERT(ticksDelta != 0); + MOZ_ASSERT(cyclesDelta <= totalCyclesDelta); + if (cyclesDelta == 0 || totalCyclesDelta == 0) { + // Nothing useful, don't commit. + return; + } + + double proportion = (double)cyclesDelta / (double)totalCyclesDelta; + MOZ_ASSERT(proportion <= 1); + + const uint64_t userTimeDelta = proportion * totalUserTimeDelta; + const uint64_t systemTimeDelta = proportion * totalSystemTimeDelta; + + group->data.mTotalUserTime += userTimeDelta; + group->data.mTotalSystemTime += systemTimeDelta; + group->data.mTotalCPOWTime += cpowTimeDelta; + group->data.mTicks += ticksDelta; + + const uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta; + uint64_t duration = 1000; // 1ms in µs + for (size_t i = 0; + i < mozilla::ArrayLength(group->data.mDurations) && duration < totalTimeDelta; + ++i, duration *= 2) { + group->data.mDurations[i]++; + } +} + +nsresult +nsPerformanceStatsService::GetResources(uint64_t* userTime, + uint64_t* systemTime) const { + MOZ_ASSERT(userTime); + MOZ_ASSERT(systemTime); + +#if defined(XP_MACOSX) + // On MacOS X, to get we per-thread data, we need to + // reach into the kernel. + + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + thread_basic_info_data_t info; + mach_port_t port = mach_thread_self(); + kern_return_t err = + thread_info(/* [in] targeted thread*/ port, + /* [in] nature of information*/ THREAD_BASIC_INFO, + /* [out] thread information */ (thread_info_t)&info, + /* [inout] number of items */ &count); + + // We do not need ability to communicate with the thread, so + // let's release the port. + mach_port_deallocate(mach_task_self(), port); + + if (err != KERN_SUCCESS) + return NS_ERROR_FAILURE; + + *userTime = info.user_time.microseconds + info.user_time.seconds * 1000000; + *systemTime = info.system_time.microseconds + info.system_time.seconds * 1000000; + +#elif defined(XP_UNIX) + struct rusage rusage; +#if defined(RUSAGE_THREAD) + // Under Linux, we can obtain per-thread statistics + int err = getrusage(RUSAGE_THREAD, &rusage); +#else + // Under other Unices, we need to do with more noisy + // per-process statistics. + int err = getrusage(RUSAGE_SELF, &rusage); +#endif // defined(RUSAGE_THREAD) + + if (err) + return NS_ERROR_FAILURE; + + *userTime = rusage.ru_utime.tv_usec + rusage.ru_utime.tv_sec * 1000000; + *systemTime = rusage.ru_stime.tv_usec + rusage.ru_stime.tv_sec * 1000000; + +#elif defined(XP_WIN) + // Under Windows, we can obtain per-thread statistics. Experience + // seems to suggest that they are not very accurate under Windows + // XP, though. + FILETIME creationFileTime; // Ignored + FILETIME exitFileTime; // Ignored + FILETIME kernelFileTime; + FILETIME userFileTime; + BOOL success = GetThreadTimes(GetCurrentThread(), + &creationFileTime, &exitFileTime, + &kernelFileTime, &userFileTime); + + if (!success) + return NS_ERROR_FAILURE; + + ULARGE_INTEGER kernelTimeInt; + kernelTimeInt.LowPart = kernelFileTime.dwLowDateTime; + kernelTimeInt.HighPart = kernelFileTime.dwHighDateTime; + // Convert 100 ns to 1 us. + *systemTime = kernelTimeInt.QuadPart / 10; + + ULARGE_INTEGER userTimeInt; + userTimeInt.LowPart = userFileTime.dwLowDateTime; + userTimeInt.HighPart = userFileTime.dwHighDateTime; + // Convert 100 ns to 1 us. + *userTime = userTimeInt.QuadPart / 10; + +#endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN) return NS_OK; } + + +/* ------------------------------------------------------ + * + * Class nsPerformanceGroup + * + */ + +/*static*/ nsPerformanceGroup* +nsPerformanceGroup::Make(JSRuntime* rt, + nsPerformanceStatsService* service, + const nsAString& name, + const nsAString& addonId, + uint64_t windowId, + uint64_t processId, + bool isSystem, + GroupScope scope) +{ + nsString groupId; + ::GenerateUniqueGroupId(rt, service->GetNextId(), processId, groupId); + return new nsPerformanceGroup(service, name, groupId, addonId, windowId, processId, isSystem, scope); +} + +nsPerformanceGroup::nsPerformanceGroup(nsPerformanceStatsService* service, + const nsAString& name, + const nsAString& groupId, + const nsAString& addonId, + uint64_t windowId, + uint64_t processId, + bool isSystem, + GroupScope scope) + : nsPerformanceGroupDetails(name, groupId, addonId, windowId, processId, isSystem) + , mService(service) + , mScope(scope) +{ + mozilla::unused << mService->mGroups.PutEntry(this); + +#if defined(DEBUG) + if (scope == GroupScope::ADDON) { + MOZ_ASSERT(IsAddon()); + MOZ_ASSERT(!IsWindow()); + } else if (scope == GroupScope::WINDOW) { + MOZ_ASSERT(IsWindow()); + MOZ_ASSERT(!IsAddon()); + } else if (scope == GroupScope::RUNTIME) { + MOZ_ASSERT(!IsWindow()); + MOZ_ASSERT(!IsAddon()); + } +#endif // defined(DEBUG) + setIsActive(mScope != GroupScope::COMPARTMENT || mService->mIsMonitoringPerCompartment); +} + +void +nsPerformanceGroup::Dispose() { + if (!mService) { + // We have already called `Dispose()`. + return; + } + + // Remove any reference to the service + RefPtr service; + service.swap(mService); + + service->mGroups.RemoveEntry(this); + + if (mScope == GroupScope::ADDON) { + MOZ_ASSERT(IsAddon()); + service->mAddonIdToGroup.RemoveEntry(AddonId()); + } else if (mScope == GroupScope::WINDOW) { + MOZ_ASSERT(IsWindow()); + service->mWindowIdToGroup.RemoveEntry(WindowId()); + } +} + +nsPerformanceGroup::~nsPerformanceGroup() { + Dispose(); +} + +nsPerformanceGroup::GroupScope +nsPerformanceGroup::Scope() const { + return mScope; +} diff --git a/toolkit/components/perfmonitoring/nsPerformanceStats.h b/toolkit/components/perfmonitoring/nsPerformanceStats.h index 9da6c23d60..4f9efd5456 100644 --- a/toolkit/components/perfmonitoring/nsPerformanceStats.h +++ b/toolkit/components/perfmonitoring/nsPerformanceStats.h @@ -6,11 +6,28 @@ #ifndef nsPerformanceStats_h #define nsPerformanceStats_h +#include "jsapi.h" + +#include "nsHashKeys.h" +#include "nsTHashtable.h" + #include "nsIObserver.h" +#include "nsPIDOMWindow.h" #include "nsIPerformanceStats.h" -class nsPerformanceStatsService : public nsIPerformanceStatsService, nsIObserver +class nsPerformanceGroup; + +typedef mozilla::Vector> JSGroupVector; +typedef mozilla::Vector> GroupVector; + +/** + * An implementation of the nsIPerformanceStatsService. + * + * Note that this implementation is not thread-safe. + */ +class nsPerformanceStatsService : public nsIPerformanceStatsService, + public nsIObserver { public: NS_DECL_ISUPPORTS @@ -18,14 +35,458 @@ public: NS_DECL_NSIOBSERVER nsPerformanceStatsService(); + nsresult Init(); private: + nsresult InitInternal(); + void Dispose(); virtual ~nsPerformanceStatsService(); +protected: + friend nsPerformanceGroup; + + /** + * A unique identifier for the process. + * + * Process HANDLE under Windows, pid under Unix. + */ const uint64_t mProcessId; + + /** + * The JS Runtime for the main thread. + */ + JSRuntime* const mRuntime; + + /** + * Generate unique identifiers. + */ + uint64_t GetNextId(); + uint64_t mUIdCounter; + + + + /** + * Extract a snapshot of performance statistics from a performance group. + */ + static nsIPerformanceStats* GetStatsForGroup(const js::PerformanceGroup* group); + static nsIPerformanceStats* GetStatsForGroup(const nsPerformanceGroup* group); + + + + /** + * Get the performance groups associated to a given JS compartment. + * + * A compartment is typically associated to the following groups: + * - the top group, shared by the entire process; + * - the window group, if the code is executed in a window, shared + * by all compartments for that window (typically, all frames); + * - the add-on group, if the code is executed as an add-on, shared + * by all compartments for that add-on (typically, all modules); + * - the compartment's own group. + * + * Pre-condition: the VM must have entered the JS compartment. + * + * The caller is expected to cache the results of this method, as + * calling it more than once may not return the same instances of + * performance groups. + */ + bool GetPerformanceGroups(JSContext* cx, JSGroupVector&); + static bool GetPerformanceGroupsCallback(JSContext* cx, JSGroupVector&, void* closure); + + + + /********************************************************** + * + * Sets of all performance groups, indexed by several keys. + * + * These sets do not keep the performance groups alive. Rather, a + * performance group is inserted in the relevant sets upon + * construction and removed from the sets upon destruction or when + * we Dispose() of the service. + * + * A `nsPerformanceGroup` is typically kept alive (as a + * `js::PerformanceGroup`) by the JSCompartment to which it is + * associated. It may also temporarily be kept alive by the JS + * stack, in particular in case of nested event loops. + */ + + /** + * Set of performance groups associated to add-ons, indexed + * by add-on id. Each item is shared by all the compartments + * that belong to the add-on. + */ + struct AddonIdToGroup: public nsStringHashKey { + explicit AddonIdToGroup(const nsAString* key) + : nsStringHashKey(key) + , mGroup(nullptr) + { } + nsPerformanceGroup* mGroup; + }; + nsTHashtable mAddonIdToGroup; + + /** + * Set of performance groups associated to windows, indexed by outer + * window id. Each item is shared by all the compartments that + * belong to the window. + */ + struct WindowIdToGroup: public nsUint64HashKey { + explicit WindowIdToGroup(const uint64_t* key) + : nsUint64HashKey(key) + , mGroup(nullptr) + {} + nsPerformanceGroup* mGroup; + }; + nsTHashtable mWindowIdToGroup; + + /** + * Set of all performance groups. + */ + struct Groups: public nsPtrHashKey { + explicit Groups(const nsPerformanceGroup* key) + : nsPtrHashKey(key) + {} + }; + nsTHashtable mGroups; + + /** + * The performance group representing the runtime itself. All + * compartments are associated to this group. + */ + RefPtr mTopGroup; + + /********************************************************** + * + * Measuring and recording the CPU use of the system. + * + */ + + /** + * Get the OS-reported time spent in userland/systemland, in + * microseconds. On most platforms, this data is per-thread, + * but on some platforms we need to fall back to per-process. + * + * Data is not guaranteed to be monotonic. + */ + nsresult GetResources(uint64_t* userTime, uint64_t* systemTime) const; + + /** + * Amount of user/system CPU time used by the thread (or process, + * for platforms that don't support per-thread measure) since start. + * Updated by `StopwatchStart` at most once per event. + * + * Unit: microseconds. + */ + uint64_t mUserTimeStart; + uint64_t mSystemTimeStart; + + + /********************************************************** + * + * Callbacks triggered by the JS VM when execution of JavaScript + * code starts/completes. + * + * As measures of user CPU time/system CPU time have low resolution + * (and are somewhat slow), we measure both only during the calls to + * `StopwatchStart`/`StopwatchCommit` and we make the assumption + * that each group's user/system CPU time is proportional to the + * number of clock cycles spent executing code in the group between + * `StopwatchStart`/`StopwatchCommit`. + * + * The results may be skewed by the thread being rescheduled to a + * different CPU during the measure, but we expect that on average, + * the skew will have limited effects, and will generally tend to + * make already-slow executions appear slower. + */ + + /** + * Execution of JavaScript code has started. This may happen several + * times in succession if the JavaScript code contains nested event + * loops, in which case only the innermost call will receive + * `StopwatchCommitCallback`. + * + * @param iteration The number of times we have started executing + * JavaScript code. + */ + static bool StopwatchStartCallback(uint64_t iteration, void* closure); + bool StopwatchStart(uint64_t iteration); + + /** + * Execution of JavaScript code has reached completion (including + * enqueued microtasks). In cse of tested event loops, any ongoing + * measurement on outer loops is silently cancelled without any call + * to this method. + * + * @param iteration The number of times we have started executing + * JavaScript code. + * @param recentGroups The groups that have seen activity during this + * event. + */ + static bool StopwatchCommitCallback(uint64_t iteration, JSGroupVector& recentGroups, void* closure); + bool StopwatchCommit(uint64_t iteration, JSGroupVector& recentGroups); + + /** + * The number of times we have started executing JavaScript code. + */ + uint64_t mIteration; + + /** + * Commit performance measures of a single group. + * + * Data is transfered from `group->recent*` to `group->data`. + * + * + * @param iteration The current iteration. + * @param userTime The total user CPU time for this thread (or + * process, if per-thread data is not available) between the + * calls to `StopwatchStart` and `StopwatchCommit`. + * @param systemTime The total system CPU time for this thread (or + * process, if per-thread data is not available) between the + * calls to `StopwatchStart` and `StopwatchCommit`. + * @param cycles The total number of cycles for this thread + * between the calls to `StopwatchStart` and `StopwatchCommit`. + * @param group The group containing the data to commit. + */ + void CommitGroup(uint64_t iteration, + uint64_t userTime, uint64_t systemTime, uint64_t cycles, + nsPerformanceGroup* group); + + + + + /********************************************************** + * + * To check whether our algorithm makes sense, we keep count of the + * number of times the process has been rescheduled to another CPU + * while we were monitoring the performance of a group and we upload + * this data through Telemetry. + */ + nsresult UpdateTelemetry(); + uint64_t mProcessStayed; uint64_t mProcessMoved; + uint32_t mProcessUpdateCounter; + + /********************************************************** + * + * Options controlling measurements. + */ + + /** + * Determine if we are measuring the performance of every individual + * compartment (in particular, every individual module, frame, + * sandbox). Note that this makes measurements noticeably slower. + */ + bool mIsMonitoringPerCompartment; +}; + + + +/** + * Container for performance data. + * + * All values are monotonic. + * + * All values are updated after running to completion. + */ +struct PerformanceData { + /** + * Number of times we have spent at least 2^n consecutive + * milliseconds executing code in this group. + * durations[0] is increased whenever we spend at least 1 ms + * executing code in this group + * durations[1] whenever we spend 2ms+ + * ... + * durations[i] whenever we spend 2^ims+ + */ + uint64_t mDurations[10]; + + /** + * Total amount of time spent executing code in this group, in + * microseconds. + */ + uint64_t mTotalUserTime; + uint64_t mTotalSystemTime; + uint64_t mTotalCPOWTime; + + /** + * Total number of times code execution entered this group, since + * process launch. This may be greater than the number of times we + * have entered the event loop. + */ + uint64_t mTicks; + + PerformanceData(); + PerformanceData(const PerformanceData& from) = default; + PerformanceData& operator=(const PerformanceData& from) = default; +}; + + + +/** + * Identification information for an item that can hold performance + * data. + */ +class nsPerformanceGroupDetails { +public: + nsPerformanceGroupDetails(const nsAString& aName, + const nsAString& aGroupId, + const nsAString& aAddonId, + const uint64_t aWindowId, + const uint64_t aProcessId, + const bool aIsSystem) + : mName(aName) + , mGroupId(aGroupId) + , mAddonId(aAddonId) + , mWindowId(aWindowId) + , mProcessId(aProcessId) + , mIsSystem(aIsSystem) + { } +public: + const nsAString& Name() const; + const nsAString& GroupId() const; + const nsAString& AddonId() const; + uint64_t WindowId() const; + uint64_t ProcessId() const; + bool IsAddon() const; + bool IsWindow() const; + bool IsSystem() const; +private: + const nsString mName; + const nsString mGroupId; + const nsString mAddonId; + const uint64_t mWindowId; + const uint64_t mProcessId; + const bool mIsSystem; +}; + +/** + * The kind of compartments represented by this group. + */ +enum class PerformanceGroupScope { + /** + * This group represents the entire runtime (i.e. the thread). + */ + RUNTIME, + + /** + * This group represents all the compartments executed in a window. + */ + WINDOW, + + /** + * This group represents all the compartments provided by an addon. + */ + ADDON, + + /** + * This group represents a single compartment. + */ + COMPARTMENT, +}; + +/** + * A concrete implementation of `js::PerformanceGroup`, also holding + * performance data. Instances may represent individual compartments, + * windows, addons or the entire runtime. + * + * This class is intended to be the sole implementation of + * `js::PerformanceGroup`. + */ +class nsPerformanceGroup final: public js::PerformanceGroup, + public nsPerformanceGroupDetails +{ +public: + + // Ideally, we would define the enum class in nsPerformanceGroup, + // but this seems to choke some versions of gcc. + typedef PerformanceGroupScope GroupScope; + + /** + * Construct a performance group. + * + * @param rt The container runtime. Used to generate a unique identifier. + * @param service The performance service. Used during destruction to + * cleanup the hash tables. + * @param name A name for the group, designed mostly for debugging purposes, + * so it should be at least somewhat human-readable. + * @param addonId The identifier of the add-on. Should be "" when the + * group is not part of an add-on, + * @param windowId The identifier of the window. Should be 0 when the + * group is not part of a window. + * @param processId A unique identifier for the process. + * @param isSystem `true` if the code of the group is executed with + * system credentials, `false` otherwise. + * @param scope the scope of this group. + */ + static nsPerformanceGroup* + Make(JSRuntime* rt, + nsPerformanceStatsService* service, + const nsAString& name, + const nsAString& addonId, + uint64_t windowId, + uint64_t processId, + bool isSystem, + GroupScope scope); + + /** + * Utility: type-safer conversion from js::PerformanceGroup to nsPerformanceGroup. + */ + static inline nsPerformanceGroup* Get(js::PerformanceGroup* self) { + return static_cast(self); + } + static inline const nsPerformanceGroup* Get(const js::PerformanceGroup* self) { + return static_cast(self); + } + + /** + * The performance data committed to this group. + */ + PerformanceData data; + + /** + * The scope of this group. Used to determine whether the group + * should be (de)activated. + */ + GroupScope Scope() const; + + /** + * Cleanup any references. + */ + void Dispose(); protected: + nsPerformanceGroup(nsPerformanceStatsService* service, + const nsAString& name, + const nsAString& groupId, + const nsAString& addonId, + uint64_t windowId, + uint64_t processId, + bool isSystem, + GroupScope scope); + + + /** + * Virtual implementation of `delete`, to make sure that objects are + * destoyed with an implementation of `delete` compatible with the + * implementation of `new` used to allocate them. + * + * Called by SpiderMonkey. + */ + virtual void Delete() override { + delete this; + } + virtual ~nsPerformanceGroup(); + +private: + /** + * The stats service. Used to perform cleanup during destruction. + */ + RefPtr mService; + + /** + * The scope of this group. Used to determine whether the group + * should be (de)activated. + */ + const GroupScope mScope; }; #endif diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js index 4492a4c55c..63f94d5772 100644 --- a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js +++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js @@ -9,12 +9,16 @@ * to the top window. */ Cu.import("resource://gre/modules/PerformanceStats.jsm", this); +Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://testing-common/ContentTask.jsm", this); + const URL = "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html?test=" + Math.random(); const PARENT_TITLE = `Main frame for test browser_compartments.js ${Math.random()}`; const FRAME_TITLE = `Subframe for test browser_compartments.js ${Math.random()}`; +const PARENT_PID = Services.appinfo.processID; + // This function is injected as source as a frameScript function frameScript() { try { @@ -22,7 +26,7 @@ function frameScript() { const { utils: Cu, classes: Cc, interfaces: Ci } = Components; Cu.import("resource://gre/modules/PerformanceStats.jsm"); - + Cu.import("resource://gre/modules/Services.jsm"); let performanceStatsService = Cc["@mozilla.org/toolkit/performance-stats-service;1"]. getService(Ci.nsIPerformanceStatsService); @@ -33,7 +37,7 @@ function frameScript() { addMessageListener("compartments-test:getStatistics", () => { try { monitor.promiseSnapshot().then(snapshot => { - sendAsyncMessage("compartments-test:getStatistics", snapshot); + sendAsyncMessage("compartments-test:getStatistics", {snapshot, pid: Services.appinfo.processID}); }); } catch (ex) { Cu.reportError("Error in content (getStatistics): " + ex); @@ -135,19 +139,21 @@ function monotinicity_tester(source, testName) { return; } let name = `${testName}: ${iteration++}`; - let snapshot = yield source(); - if (!snapshot) { + let result = yield source(); + if (!result) { // This can happen at the end of the test when we attempt // to communicate too late with the content process. window.clearInterval(interval); return; } + let {pid, snapshot} = result; // Sanity check on the process data. sanityCheck(previous.processData, snapshot.processData); SilentAssert.equal(snapshot.processData.isSystem, true); SilentAssert.equal(snapshot.processData.name, ""); SilentAssert.equal(snapshot.processData.addonId, ""); + SilentAssert.equal(snapshot.processData.processId, pid); previous.procesData = snapshot.processData; // Sanity check on components data. @@ -160,10 +166,14 @@ function monotinicity_tester(source, testName) { ]) { // Note that we cannot expect components data to be always smaller // than process data, as `getrusage` & co are not monotonic. - SilentAssert.leq(item[probe][k], 2 * snapshot.processData[probe][k], - `Sanity check (${testName}): ${k} of component is not impossibly larger than that of process`); + SilentAssert.leq(item[probe][k], 3 * snapshot.processData[probe][k], + `Sanity check (${name}): ${k} of component is not impossibly larger than that of process`); } + let isCorrectPid = (item.processId == pid && !item.isChildProcess) + || (item.processId != pid && item.isChildProcess); + SilentAssert.ok(isCorrectPid, `Pid check (${name}): the item comes from the right process`); + let key = item.groupId; if (map.has(key)) { let old = map.get(key); @@ -222,7 +232,7 @@ add_task(function* test() { info("Deactivating sanity checks under Windows (bug 1151240)"); } else { info("Setting up sanity checks"); - monotinicity_tester(() => monitor.promiseSnapshot(), "parent process"); + monotinicity_tester(() => monitor.promiseSnapshot().then(snapshot => ({snapshot, pid: PARENT_PID})), "parent process"); monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" ); } @@ -242,7 +252,7 @@ add_task(function* test() { }); info("Titles set"); - let stats = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null)); + let {snapshot: stats} = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null)); let titles = [for(stat of stats.componentsData) stat.title]; diff --git a/toolkit/components/places/Database.cpp b/toolkit/components/places/Database.cpp index e1446e5ebd..c474f9428a 100644 --- a/toolkit/components/places/Database.cpp +++ b/toolkit/components/places/Database.cpp @@ -18,6 +18,7 @@ #include "nsPlacesIndexes.h" #include "nsPlacesTriggers.h" #include "nsPlacesMacros.h" +#include "nsVariant.h" #include "SQLFunctions.h" #include "Helpers.h" diff --git a/toolkit/components/places/SQLFunctions.cpp b/toolkit/components/places/SQLFunctions.cpp index cc620d6ff7..f7b12a16c6 100644 --- a/toolkit/components/places/SQLFunctions.cpp +++ b/toolkit/components/places/SQLFunctions.cpp @@ -16,6 +16,7 @@ #include "nsPrintfCString.h" #include "nsNavHistory.h" #include "mozilla/Likely.h" +#include "nsVariant.h" using namespace mozilla::storage; @@ -703,9 +704,7 @@ namespace places { nsAutoString src; aArguments->GetString(0, src); - nsCOMPtr result = - do_CreateInstance("@mozilla.org/variant;1"); - NS_ENSURE_STATE(result); + RefPtr result = new nsVariant(); if (src.Length()>1) { src.Truncate(src.Length() - 1); @@ -761,9 +760,7 @@ namespace places { nsAutoString src; aArguments->GetString(0, src); - nsCOMPtr result = - do_CreateInstance("@mozilla.org/variant;1"); - NS_ENSURE_STATE(result); + RefPtr result = new nsVariant(); // Remove common URL hostname prefixes if (StringBeginsWith(src, NS_LITERAL_STRING("www."))) { @@ -828,9 +825,7 @@ namespace places { navHistory->DispatchFrecencyChangedNotification(spec, newFrecency, guid, hidden, lastVisitDate); - nsCOMPtr result = - do_CreateInstance("@mozilla.org/variant;1"); - NS_ENSURE_STATE(result); + RefPtr result = new nsVariant(); rv = result->SetAsInt32(newFrecency); NS_ENSURE_SUCCESS(rv, rv); result.forget(_result); diff --git a/toolkit/components/places/nsNavHistoryQuery.cpp b/toolkit/components/places/nsNavHistoryQuery.cpp index 9ad331daf3..41a4d2e78c 100644 --- a/toolkit/components/places/nsNavHistoryQuery.cpp +++ b/toolkit/components/places/nsNavHistoryQuery.cpp @@ -18,6 +18,7 @@ #include "nsNetUtil.h" #include "nsTArray.h" #include "prprf.h" +#include "nsVariant.h" using namespace mozilla; @@ -1117,13 +1118,11 @@ NS_IMETHODIMP nsNavHistoryQuery::GetTags(nsIVariant **aTags) { NS_ENSURE_ARG_POINTER(aTags); - nsresult rv; - nsCOMPtr out = do_CreateInstance(NS_VARIANT_CONTRACTID, - &rv); - NS_ENSURE_SUCCESS(rv, rv); + RefPtr out = new nsVariant(); uint32_t arrayLen = mTags.Length(); + nsresult rv; if (arrayLen == 0) rv = out->SetAsEmptyArray(); else { diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 069b7d45f0..7334171f3e 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7145,7 +7145,7 @@ }, "PERF_MONITORING_TEST_CPU_RESCHEDULING_PROPORTION_MOVED": { "alert_emails": ["dteller@mozilla.com"], - "expires_in_version": "44", + "expires_in_version": "48", "kind": "linear", "high": "100", "n_buckets": "20", diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp index 50e2f063c5..d88b898f33 100644 --- a/toolkit/xre/nsUpdateDriver.cpp +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -11,7 +11,7 @@ #include "nsAppRunner.h" #include "nsIWritablePropertyBag.h" #include "nsIFile.h" -#include "nsIVariant.h" +#include "nsVariant.h" #include "nsCOMPtr.h" #include "nsString.h" #include "prproces.h" @@ -640,12 +640,7 @@ SetOSApplyToDir(nsIUpdate* update, const nsACString& osApplyToDir) return; } - nsCOMPtr variant = - do_CreateInstance("@mozilla.org/variant;1", &rv); - if (NS_FAILED(rv)) { - return; - } - + RefPtr variant = new nsVariant(); rv = variant->SetAsACString(osApplyToDir); if (NS_FAILED(rv)) { return; diff --git a/uriloader/exthandler/win/nsMIMEInfoWin.cpp b/uriloader/exthandler/win/nsMIMEInfoWin.cpp index 102c2f75b3..1fca6c03ec 100644 --- a/uriloader/exthandler/win/nsMIMEInfoWin.cpp +++ b/uriloader/exthandler/win/nsMIMEInfoWin.cpp @@ -7,7 +7,6 @@ #include "nsArrayEnumerator.h" #include "nsCOMArray.h" #include "nsIFile.h" -#include "nsIVariant.h" #include "nsMIMEInfoWin.h" #include "nsNetUtil.h" #include @@ -22,6 +21,7 @@ #include "nsOSHelperAppService.h" #include "nsUnicharUtils.h" #include "nsITextToSubURI.h" +#include "nsVariant.h" #define RUNDLL32_EXE L"\\rundll32.exe" @@ -172,15 +172,13 @@ nsMIMEInfoWin::GetEnumerator(nsISimpleEnumerator* *_retval) static nsresult GetIconURLVariant(nsIFile* aApplication, nsIVariant* *_retval) { - nsresult rv = CallCreateInstance("@mozilla.org/variant;1", _retval); - if (NS_FAILED(rv)) - return rv; nsAutoCString fileURLSpec; NS_GetURLSpecFromFile(aApplication, fileURLSpec); nsAutoCString iconURLSpec; iconURLSpec.AssignLiteral("moz-icon://"); iconURLSpec += fileURLSpec; - nsCOMPtr writable(do_QueryInterface(*_retval)); + RefPtr writable(new nsVariant()); writable->SetAsAUTF8String(iconURLSpec); + writable.forget(_retval); return NS_OK; }