From e3d7542d66d595a30be9b3cf5da559fd9428bfc4 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Fri, 23 Oct 2020 11:56:28 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1145326 - String#normalize form parameter might not be an atom. r=Waldo (291a9d9c3) - 1156886 - Optimize toLowerCase and toUpperCase on ASCII characters. r=luke (6dad527a9) - Bug 1102219 - Part 0: Combine WARNED_* slots in GlobalObject and turn it into a bitset. r=till (1496c25c4) - Bug 1102219 - Part 1: Add `String.prototype.includes`; keep `String.prototype.contains` around as an alias with a (non-release builds only) warning. r=till (8502ce4c4) - Bug 1102219 - Part 3: Replace more `String.prototype.contains` with `String.prototype.includes` in JS code. r=till (d2d3e3455) - Bug 1102219 - followup for Part 0: Fix code alignment and argument name in GlobalObject.h. r=me (df2063dba) - Bug 863515 - Expose hasContentOpener on nsITabParent. r=smaug. (0e1c18870) - Bug 1157454 - Satisfy TSan by removing the unneeded mEarlyRv read in WebCryptoTask::CalculateResult() r=rbarnes (ba5fca0ae) - Bug 1106087 - Recreate newly generated ECDH private keys with an CKA_EC_POINT attribute to support JWK and PKCS8 export r=rbarnes (07634e876) - Bug 1158927 - Calls to CryptoKey::PrivateKeyToPkcs8() and ::PublicKeyToSpki() should check return values r=rbarnes (03787e2fb) - Bug 1050175 - Add raw import/export for EC public keys to the WebCrypto API r=rbarnes,smaug (168a93425) - Bug 968520 - Add mozilla::fallible to Fallible{Auto,}TArray::SetLength calls. r=froydnj (f85ec3e02) - Bug 977586 - omit quotes in top-level strings logged via console.log(), and omit extra spaces when custom styles (%c) are used. r=past,baku (58391e3d0) - Bug 1167423 - patch 1 - Handle return values of FallibleTArray functions in Console API, r=smaug (7e36592a4) - Bug 1167423 - patch 2 - Handle return values of FallibleTArray functions in WebSocket, r=smaug (286dc7745) - Bug 1167423 - patch 3 - Handle return values of FallibleTArray functions in MutationObserver, r=smaug (e4cc88cfb) - Bug 1167423 - patch 4 - Handle return values of FallibleTArray functions in CanvasRenderingContext2D, r=smaug (d02bfff6c) - Bug 1167423 - patch 5 - Handle return values of FallibleTArray functions in WebGL2Context, r=smaug (789aac4ea) - Bug 1167423 - patch 6 - Handle return values of FallibleTArray functions in WebCryptTask, r=smaug (7ca60765a) - Bug 968520 - Use FallibleTArray::TruncateLength instead of SetLength where possible. r=froydnj (bb8e960b4) - Bug 1167423 - patch 7 - Handle return values of FallibleTArray functions in DataStore API, r=smaug (e207e7371) - Bug 1167423 - patch 8 - Handle return values of FallibleTArray functions in HTMLInputElement, r=smaug (f87b9d4fb) - Bug 1167423 - patch 9 - Handle return values of FallibleTArray functions in MediaSource, r=jya (9d2905c5a) - Bug 1167423 - patch 10 - Handle return values of FallibleTArray functions in MobileMessage, r=smaug (6e8b7ce0f) - Bug 1167418 - Check fallible AppendElements call in FontFaceSet. r=poiru (cd64d2b69) - Bug 968520 - Add mozilla::fallible to FallibleTArray::AppendElements calls. r=froydnj (a16f43ea9) - Bug 947854 part 2 - Avoid exiting fullscreen mode when exit from DOM fullscreen. r=smaug,dao (4fed384bc) (partly) - Bug 947854 part 3 - Include testing MozExitedFullscreen event in existing chrome test. r=smaug (b463bf2e8) - Bug 1105939 part 1 - Backout workaround from bug 740923. r=smichaud (08ae588b7) - Bug 1105939 part 2 - Suppress windows animation when hiding chrome on OS X. r=smichaud (f1792f1e8) - Bug 1105939 part 3 - Save and restore window collection behavior when showing/hiding window chrome. r=mstange (359c2d820) - Bug 1105939 part 4 - Treat cocoa window without titlebar as fullscreen to avoid drawing rounded corners on it. r=mstange (40a921643) - Bug 1105939 part 5 - Rename parameter aRequireTrust of SetFullScreenInternal to aFullscreenMode for increased clarity. r=smaug (650f229c3) - Bug 1105939 part 6 - Add a method to nsIWidget for widget to distinguish between fullscreen mode and DOM fullscreen. r=roc (e1d03316d) - Bug 1105939 part 7 - Use tranditional fullscreen on Mac for DOM fullscreen. r=smichaud (8e737b365) - Bug 1164625 - Don't redraw the titlebar for unified toolbar height changes when the titlebar is under Gecko's control. r=smichaud (683c87ff4) - Bug 1171210 - Add a telemetry probe for how long it takes to clear plugin cookies during sanitize. r=vladan (26cd118c5) (partly) - Bug 1148012 - Add a run ID for plugins to differentiate subsequent runs of the same plugins. r=jimm. (dd89a0278) - Bug 1148012 - Expose run ID through nsIObjectLoadingContent.idl. r=josh,smaug. (82080018e) - Bug 1148012 - Send the run ID and plugin name along with the plugin-crashed observer notification. r=josh. (d0f26b99a) - Bug 1152395: Ensure that NP_Shutdown respects async plugin init; r=jimm (6c94915e5) - Bug 1146955 - Unify pluginID for GMP and runID for NPAPI plugins to use the same internal incrementor. r=jesup, mrbkap. (b5db7ac02) - Bug 1146955 - Dispatch PluginCrashed event in content process on GMP crash for PeerConnection. r=jesup. (3eedefe8c) - Bug 1123759: Set low integrity on NPAPI processes for Windows sandboxing policy level >= 2. r=bbondy, r=bsmedberg (bc795adba) - bits of Bug 1008435 - Let the Gecko Profiler work with child processes. (7db2f4f82) - Bug 1116188 - Add async ProfileGatherer as the mechanism for gathering profiles from subprocesses. r=bgirard,bz (23aed75ce) - add ProcessHangMonitor.jsm as of Bug 1120650: add telemetry probe for slow script notices. (7501c04d7) - Bug 1160142 - For e10s plugin hangs take the minidump of the browser process before we message the chrome UI about the hang. r=billm (557ae5892) - Bug 1175975 - Null crash fix in ProcessHangMonitor (r=jimm) (52fce4c7c) - Bug 1164543 - Add HasLocalInstance support (r=jimm) (572e9e17e) - Bug 699860 - Removed useJSTransfer and deleteAllLike usage from ForgetAboutSite.jsm. r=paolo (e3f9013bc) - Bug 1189967 - Add cmath to the wrapped STL headers. r=nfroyd Somehow, TimeStamp.cpp fails to build with MSVC 2015 without this. (692e3d17d) - Bug 1249167 - Fix dependencies involving stdc++compat and clang-plugin. r=mshal (96af922d7) - Bug 1248416 - add symbols for bad_function_call exception for C++ runtimes. r=nfroyd (fa28f7568) - Bug 1228641. Add a polyfill of std::initializer_list. r=froydnj (5625de4ac) - Bug 1163329 - Add a utility for expanding a tuple into a variadic argument list to MFBT. r=froydnj (ff41a27b7) - Bug 1255540 - Properly run the clang-plugin tests. r=gps (bdf4d9ac4) - Bug 1152759 - Regroup Performance Monitoring modules/components;r=yoric (cfdc3c13a) - Bug 1151750 - about:performance now recapitulates alerts. r=mossop (e9d2a3a42) - Bug 1150863 - added play and pause button to about:performance in addition to a dropdown list to select the refresh rate. r=Yoric (944b00cfc) - Bug 1149486 - Extract a window title and window ID for PerformanceStats. r=mossop (c57e65841) - fix getTop() (df8bdbc5c) - Bug 1152950 - AddonWatcher should not inform the user at the first infraction. r=mossop (2cf902efe) - Bug 1157471 - AddonWatcher console noise. r=yoric (44c67d528) - Bug 1154239 - Rework PerformanceStats.jsm for modularity and asynchronicity. r=Mossop (4f9ba2236) - Bug 1156264 - Activate/deactivate jank and CPOW monitoring separately (high-level). r=mossop (9fcb6ca88) - Bug 1188616 - CPOW monitoring should use JS_Now instead of PR_IntervalNow. r=jandem (45cc23a05) - Bug 1157870 - Performance Groups should have a unique ID (low-level). r=jandem (ead7d288e) - Bug 1157870 - Performance Groups should have a unique ID (high-level). r=mossop (20c9fa6df) - Bug 1157870 - Cross compilation fixup. (627c99d4c) - Bug 1169890 - Check return values for CryptoBuffer.Assign() calls r=rbarnes (681f04148) - Bug 1172785 - RTCCertificate implementation, r=rbarnes (c30068f20) - Bug 1155898 - Expose fetch on JS sandbox. r=gabor, r=peterv (b965210af) - Bug 1181262 - Disabling more code under --disable-webrtc, r=dholbert,bwc (0e93112cb) - fix mispatch (f09b69b91) - Bug 1172785 - RTCCertificate interfaces, r=peterv (9cbcbbffb) - backport some profiler stuff (b7d68cafc) - Bug 1137245 - ServiceWorkerManager should set WorkerPrivate::LoadInfo::mIndexedDBAllowed correctly. r=bent, bkelly (58ef4c286) - Bug 1176434 - Enabling indexedDB for content JS sandboxes, r=bent (c891b518f) - Bug 1158399 - Expose the [[DateValue]] field in Date objects only through a ClippedTime class that enforces prior TimeClip-ing on the given value. r=evilpie, r=bz, r=dhylands, r=mt, r=froydnj, r=khuey, r=baku, r=smaug Bug 1158399 - Ensure/assert that DateObject::setUTCTime never stores a non-TimeClip'd value in the reserved slot. r=evilpie (9c06bf765) - Bug 861219 - Part 0: Make ClassSpec be able to delegate to another ClassSpec. r=bholley (85d88193e) - Bug 861219 - Part 1: Make Date.prototype not be a Date object. r=bholley (19355939b) - Bug 1177907 - Handle ObjectClassIs exception in date_toString. r=till (a2adb9c10) --- browser/app/profile/palemoon.js | 6 +- browser/modules/ProcessHangMonitor.jsm | 321 +++++++++++ build/clang-plugin/tests/Makefile.in | 5 +- build/clang-plugin/tests/moz.build | 3 + build/templates.mozbuild | 10 +- config/external/nss/nss.def | 1 + config/moz.build | 5 +- config/recurse.mk | 5 +- config/stl-headers | 3 + config/system-headers | 3 + dom/base/Console.cpp | 147 +++-- dom/base/Console.h | 4 +- dom/base/File.cpp | 2 +- dom/base/StructuredCloneTags.h | 2 + dom/base/WebSocket.cpp | 6 +- dom/base/nsDOMMutationObserver.cpp | 9 +- dom/base/nsDOMMutationObserver.h | 3 +- dom/base/nsDocument.cpp | 9 + dom/base/nsGlobalWindow.cpp | 29 +- dom/base/nsGlobalWindow.h | 3 +- dom/base/nsIObjectLoadingContent.idl | 10 +- dom/base/nsJSEnvironment.cpp | 36 ++ dom/base/nsJSTimeoutHandler.cpp | 2 +- dom/base/nsObjectLoadingContent.cpp | 31 + dom/base/nsObjectLoadingContent.h | 15 + dom/base/nsPIDOMWindow.h | 7 +- dom/bindings/Date.cpp | 21 +- dom/bindings/Date.h | 27 +- dom/canvas/CanvasRenderingContext2D.cpp | 25 +- dom/canvas/CanvasRenderingContext2D.h | 5 +- dom/canvas/WebGL2Context.h | 6 +- dom/canvas/WebGL2ContextFramebuffers.cpp | 37 +- dom/canvas/WebGLElementArrayCache.cpp | 4 +- dom/crypto/CryptoKey.cpp | 258 +++++++-- dom/crypto/CryptoKey.h | 8 + dom/crypto/KeyAlgorithmProxy.h | 18 +- dom/crypto/WebCryptoCommon.h | 16 +- dom/crypto/WebCryptoTask.cpp | 499 +++++++++------- dom/crypto/WebCryptoTask.h | 25 + dom/crypto/test/test-vectors.js | 38 +- dom/crypto/test/test_WebCrypto_ECDH.html | 108 ++++ dom/crypto/test/test_WebCrypto_ECDSA.html | 34 ++ dom/datastore/DataStoreDB.cpp | 5 +- dom/datastore/DataStoreService.cpp | 4 +- dom/devicestorage/nsDeviceStorage.cpp | 2 +- dom/filehandle/MetadataHelper.cpp | 3 +- dom/html/HTMLInputElement.cpp | 32 +- dom/indexedDB/ActorsParent.cpp | 11 +- dom/indexedDB/IDBFactory.cpp | 67 ++- dom/indexedDB/IDBFactory.h | 10 +- dom/indexedDB/IDBObjectStore.cpp | 8 +- dom/indexedDB/IndexedDatabaseManager.cpp | 20 +- dom/indexedDB/Key.cpp | 7 +- dom/indexedDB/test/mochitest.ini | 6 + dom/indexedDB/test/service_worker.js | 10 + dom/indexedDB/test/service_worker_client.html | 28 + dom/indexedDB/test/test_sandbox.html | 101 ++++ dom/indexedDB/test/test_serviceworker.html | 71 +++ dom/indexedDB/test/unit/test_sandbox.js | 78 +++ dom/indexedDB/test/unit/xpcshell-shared.ini | 1 + dom/interfaces/base/nsITabParent.idl | 4 +- dom/ipc/Blob.cpp | 2 +- dom/ipc/ContentChild.cpp | 9 +- dom/ipc/ContentChild.h | 2 +- dom/ipc/ContentParent.cpp | 65 ++- dom/ipc/ContentParent.h | 10 +- dom/ipc/PContent.ipdl | 8 +- dom/ipc/ProcessHangMonitor.cpp | 115 +++- dom/ipc/TabParent.cpp | 19 + dom/ipc/TabParent.h | 3 + dom/ipc/nsIHangReport.idl | 6 +- dom/media/MediaData.cpp | 1 + dom/media/PeerConnection.js | 57 +- dom/media/gmp/GMPContentParent.h | 6 +- dom/media/gmp/GMPDecryptorParent.cpp | 2 +- dom/media/gmp/GMPDecryptorParent.h | 4 +- dom/media/gmp/GMPDecryptorProxy.h | 2 +- dom/media/gmp/GMPParent.cpp | 10 +- dom/media/gmp/GMPParent.h | 4 +- dom/media/gmp/GMPService.cpp | 27 +- dom/media/gmp/GMPService.h | 15 +- dom/media/gmp/GMPServiceChild.cpp | 2 +- dom/media/gmp/GMPServiceParent.cpp | 2 +- dom/media/gmp/GMPServiceParent.h | 2 +- dom/media/gmp/GMPVideoEncoderParent.cpp | 6 + dom/media/gmp/GMPVideoEncoderParent.h | 2 +- dom/media/gmp/GMPVideoEncoderProxy.h | 2 +- dom/media/gmp/PGMPService.ipdl | 2 +- dom/media/mediasource/ContainerParser.cpp | 12 +- dom/media/mediasource/ResourceQueue.cpp | 16 +- dom/media/mediasource/ResourceQueue.h | 7 +- dom/media/mediasource/SourceBuffer.cpp | 2 +- .../mediasource/SourceBufferResource.cpp | 9 +- dom/media/mediasource/SourceBufferResource.h | 5 +- dom/media/mediasource/TrackBuffer.cpp | 22 +- dom/media/webaudio/AudioDestinationNode.cpp | 2 +- dom/media/webaudio/DelayBuffer.cpp | 2 +- dom/media/webaudio/MediaBufferDecoder.cpp | 2 +- dom/media/webrtc/RTCCertificate.cpp | 443 ++++++++++++++ dom/media/webrtc/RTCCertificate.h | 99 ++++ dom/media/webrtc/moz.build | 1 + dom/mobilemessage/MobileMessageManager.cpp | 12 +- dom/plugins/base/PluginPRLibrary.h | 2 + dom/plugins/base/nsNPAPIPluginInstance.cpp | 19 + dom/plugins/base/nsNPAPIPluginInstance.h | 2 + dom/plugins/base/nsPluginHost.cpp | 13 + dom/plugins/base/nsPluginTags.cpp | 1 + dom/plugins/base/nsPluginTags.h | 4 + dom/plugins/ipc/PPluginModule.ipdl | 5 +- dom/plugins/ipc/PluginBridge.h | 4 +- dom/plugins/ipc/PluginHangUIParent.cpp | 2 +- dom/plugins/ipc/PluginLibrary.h | 2 + dom/plugins/ipc/PluginModuleChild.cpp | 10 +- dom/plugins/ipc/PluginModuleChild.h | 2 +- dom/plugins/ipc/PluginModuleParent.cpp | 157 ++++- dom/plugins/ipc/PluginModuleParent.h | 46 +- dom/plugins/ipc/PluginProcessParent.cpp | 18 +- .../ipc/PluginScriptableObjectChild.cpp | 6 +- .../ipc/PluginScriptableObjectParent.cpp | 6 +- dom/svg/DOMSVGLengthList.cpp | 2 +- dom/svg/DOMSVGNumberList.cpp | 2 +- dom/svg/DOMSVGPathSegList.cpp | 3 +- dom/svg/DOMSVGPointList.cpp | 2 +- dom/svg/DOMSVGTransformList.cpp | 2 +- dom/svg/SVGLengthList.h | 2 +- dom/svg/SVGNumberList.h | 2 +- dom/svg/SVGPathData.cpp | 2 +- dom/svg/SVGPathData.h | 2 +- dom/svg/SVGPointList.h | 2 +- dom/svg/SVGStringList.h | 2 +- dom/svg/SVGTransformList.h | 2 +- ...chrome.xul => MozDomFullscreen_chrome.xul} | 14 +- dom/tests/mochitest/chrome/chrome.ini | 4 +- ...nt.xul => test_MozDomFullscreen_event.xul} | 2 +- ...screen.html => file_MozDomFullscreen.html} | 0 dom/tests/mochitest/general/mochitest.ini | 2 +- .../mochitest/general/test_interfaces.html | 28 +- dom/time/TimeManager.cpp | 2 +- dom/webidl/CanvasRenderingContext2D.webidl | 4 +- dom/webidl/HTMLObjectElement.webidl | 3 + dom/webidl/MutationObserver.webidl | 2 +- dom/webidl/PeerConnectionImpl.webidl | 8 +- dom/webidl/PluginCrashedEvent.webidl | 2 + dom/webidl/RTCCertificate.webidl | 12 + dom/webidl/RTCConfiguration.webidl | 1 + dom/webidl/RTCPeerConnection.webidl | 3 + dom/webidl/SubtleCrypto.webidl | 4 + dom/webidl/WebGL2RenderingContext.webidl | 5 + dom/webidl/moz.build | 1 + dom/workers/DataStore.cpp | 2 +- dom/workers/ServiceWorkerManager.cpp | 4 + gfx/thebes/gfxContext.cpp | 2 +- gfx/thebes/gfxCoreTextShaper.cpp | 2 +- gfx/thebes/gfxFontUtils.cpp | 2 +- gfx/thebes/gfxGraphiteShaper.cpp | 8 +- gfx/thebes/gfxHarfBuzzShaper.cpp | 2 +- gfx/thebes/gfxUserFontSet.cpp | 2 +- intl/hyphenation/nsHyphenator.cpp | 2 +- ipc/glue/GeckoChildProcessHost.cpp | 11 + ipc/glue/GeckoChildProcessHost.h | 4 + ipc/glue/IPCMessageUtils.h | 2 +- ipc/ipdl/ipdl/lower.py | 2 +- js/ipc/CPOWTimer.cpp | 13 +- js/ipc/CPOWTimer.h | 2 +- js/public/Class.h | 69 ++- js/public/Date.h | 109 +++- .../jit-test/tests/auto-regress/bug771946.js | 2 +- js/src/jit-test/tests/basic/bug1177907.js | 4 + js/src/jit-test/tests/basic/eval-scopes.js | 4 +- js/src/jit-test/tests/basic/function-gname.js | 4 +- .../basic/testCrossCompartmentTransparency.js | 12 +- js/src/js.msg | 1 + js/src/jsapi.cpp | 7 +- js/src/jsapi.h | 15 +- js/src/jsdate.cpp | 200 ++++--- js/src/jsdate.h | 7 +- js/src/jsstr.cpp | 25 +- js/src/tests/ecma/Date/15.9.5.2-2-n.js | 50 -- js/src/tests/ecma/Date/15.9.5.js | 11 +- js/src/tests/ecma/extensions/15.9.5.js | 42 -- js/src/tests/ecma_2/Exceptions/date-001.js | 60 -- ...ltin-methods-reject-null-undefined-this.js | 2 +- js/src/tests/ecma_6/Date/browser.js | 0 .../ecma_6/Date/prototype-is-not-a-date.js | 15 + js/src/tests/ecma_6/Date/toString-generic.js | 13 + js/src/tests/ecma_6/String/IsRegExp.js | 4 +- .../ecma_6/String/normalize-form-non-atom.js | 18 + js/src/tests/js1_4/Regress/date-001-n.js | 42 -- js/src/vm/DateObject.h | 15 +- js/src/vm/DateTime.h | 13 - js/src/vm/GlobalObject.cpp | 34 +- js/src/vm/GlobalObject.h | 23 +- js/src/vm/Runtime.cpp | 11 +- js/src/vm/Runtime.h | 13 + js/src/vm/SelfHosting.cpp | 3 +- js/src/vm/StructuredClone.cpp | 7 +- js/src/vm/TypedArrayObject.cpp | 28 +- js/src/vm/Unicode.h | 30 + js/src/vm/Xdr.h | 4 +- js/xpconnect/src/Sandbox.cpp | 89 ++- js/xpconnect/src/xpcprivate.h | 1 + js/xpconnect/tests/mochitest/mochitest.ini | 3 + .../tests/mochitest/test_sandbox_fetch.html | 54 ++ js/xpconnect/tests/unit/test_bug809652.js | 1 - js/xpconnect/wrappers/XrayWrapper.cpp | 8 +- layout/generic/nsTextFrame.cpp | 15 +- layout/style/FontFaceSet.cpp | 3 +- layout/svg/nsSVGUtils.cpp | 2 +- media/libstagefright/binding/BufferStream.cpp | 2 +- .../media/libstagefright/MPEG4Extractor.cpp | 4 +- .../av/media/libstagefright/MediaBuffer.cpp | 2 +- .../src/peerconnection/PeerConnectionImpl.cpp | 7 +- .../src/peerconnection/PeerConnectionImpl.h | 5 +- .../WebrtcGlobalInformation.cpp | 2 +- memory/mozalloc/msvc_raise_wrappers.cpp | 6 + memory/mozalloc/msvc_raise_wrappers.h | 7 +- memory/mozalloc/throw_gcc.h | 6 + mfbt/IndexSequence.h | 143 +++++ mfbt/InitializerList.h | 62 ++ mfbt/moz.build | 2 + mfbt/tests/TestInitializerList.cpp | 24 + mfbt/tests/moz.build | 1 + netwerk/base/MemoryDownloader.cpp | 2 +- netwerk/base/nsUDPSocket.cpp | 2 +- .../win/src/sandboxbroker/sandboxBroker.cpp | 106 ++-- storage/Variant.h | 2 +- testing/cppunittest.ini | 1 + .../content/aboutPerformance.js | 215 ++++++- .../content/aboutPerformance.xhtml | 20 +- toolkit/components/aboutperformance/moz.build | 23 - .../tests/browser/browser.ini | 9 +- .../tests/browser/browser_aboutperformance.js | 91 +-- .../tests/browser/browser_compartments.js | 161 ------ toolkit/components/build/moz.build | 2 +- toolkit/components/moz.build | 1 + .../perfmonitoring/AddonWatcher.jsm | 267 +++++++++ .../perfmonitoring/PerformanceStats.jsm | 538 ++++++++++++++++++ toolkit/components/perfmonitoring/moz.build | 35 ++ .../nsIPerformanceStats.idl | 10 +- .../nsPerformanceStats.cpp | 75 ++- .../nsPerformanceStats.h | 1 + .../perfmonitoring/tests/browser/browser.ini | 10 + .../tests/browser/browser_AddonWatcher.js | 8 +- .../tests/browser/browser_Addons_sample.xpi | Bin 0 -> 2133 bytes .../tests/browser/browser_compartments.html | 20 + .../tests/browser/browser_compartments.js | 226 ++++++++ .../browser/browser_compartments_frame.html | 12 + .../browser/browser_compartments_script.js | 29 + .../perfmonitoring/tests/browser/head.js | 50 ++ .../tests/xpcshell/test_compartments.js | 51 +- .../tests/xpcshell/xpcshell.ini | 0 toolkit/components/startup/nsAppStartup.cpp | 3 +- toolkit/components/telemetry/Histograms.json | 18 + toolkit/devtools/server/actors/script.js | 4 - toolkit/devtools/webconsole/console-output.js | 30 +- toolkit/forgetaboutsite/ForgetAboutSite.jsm | 66 +-- .../test/unit/head_forgetaboutsite.js | 12 - .../test/unit/test_removeDataFromDomain.js | 119 ---- toolkit/modules/AddonWatcher.jsm | 204 ------- toolkit/modules/PerformanceStats.jsm | 205 ------- toolkit/modules/moz.build | 2 - tools/profiler/GeckoProfiler.h | 11 +- tools/profiler/GeckoProfilerFunc.h | 9 +- tools/profiler/GeckoProfilerImpl.h | 7 + tools/profiler/ProfileEntry.cpp | 3 - tools/profiler/ProfileGatherer.cpp | 110 ++++ tools/profiler/ProfileGatherer.h | 38 ++ tools/profiler/TableTicker.cpp | 121 ++++ tools/profiler/TableTicker.h | 112 +--- tools/profiler/moz.build | 4 + tools/profiler/nsIProfiler.idl | 5 +- tools/profiler/nsProfiler.cpp | 32 ++ tools/profiler/platform.cpp | 11 + widget/cocoa/nsChildView.mm | 3 +- widget/cocoa/nsCocoaWindow.h | 17 +- widget/cocoa/nsCocoaWindow.mm | 133 +++-- widget/nsBaseWidget.h | 1 + widget/nsIWidget.h | 17 +- 278 files changed, 6186 insertions(+), 2198 deletions(-) create mode 100644 browser/modules/ProcessHangMonitor.jsm create mode 100644 dom/indexedDB/test/service_worker.js create mode 100644 dom/indexedDB/test/service_worker_client.html create mode 100644 dom/indexedDB/test/test_sandbox.html create mode 100644 dom/indexedDB/test/test_serviceworker.html create mode 100644 dom/indexedDB/test/unit/test_sandbox.js create mode 100644 dom/media/webrtc/RTCCertificate.cpp create mode 100644 dom/media/webrtc/RTCCertificate.h rename dom/tests/mochitest/chrome/{MozEnteredDomFullscreen_chrome.xul => MozDomFullscreen_chrome.xul} (85%) rename dom/tests/mochitest/chrome/{test_MozEnteredDomFullscreen_event.xul => test_MozDomFullscreen_event.xul} (93%) rename dom/tests/mochitest/general/{file_MozEnteredDomFullscreen.html => file_MozDomFullscreen.html} (100%) create mode 100644 dom/webidl/RTCCertificate.webidl create mode 100644 js/src/jit-test/tests/basic/bug1177907.js delete mode 100644 js/src/tests/ecma/Date/15.9.5.2-2-n.js delete mode 100644 js/src/tests/ecma/extensions/15.9.5.js delete mode 100644 js/src/tests/ecma_2/Exceptions/date-001.js create mode 100644 js/src/tests/ecma_6/Date/browser.js create mode 100644 js/src/tests/ecma_6/Date/prototype-is-not-a-date.js create mode 100644 js/src/tests/ecma_6/Date/toString-generic.js create mode 100644 js/src/tests/ecma_6/String/normalize-form-non-atom.js delete mode 100644 js/src/tests/js1_4/Regress/date-001-n.js create mode 100644 js/xpconnect/tests/mochitest/test_sandbox_fetch.html create mode 100644 mfbt/IndexSequence.h create mode 100644 mfbt/InitializerList.h create mode 100644 mfbt/tests/TestInitializerList.cpp delete mode 100644 toolkit/components/aboutperformance/tests/browser/browser_compartments.js create mode 100644 toolkit/components/perfmonitoring/AddonWatcher.jsm create mode 100644 toolkit/components/perfmonitoring/PerformanceStats.jsm create mode 100644 toolkit/components/perfmonitoring/moz.build rename toolkit/components/{aboutperformance => perfmonitoring}/nsIPerformanceStats.idl (93%) rename toolkit/components/{aboutperformance => perfmonitoring}/nsPerformanceStats.cpp (84%) rename toolkit/components/{aboutperformance => perfmonitoring}/nsPerformanceStats.h (95%) create mode 100644 toolkit/components/perfmonitoring/tests/browser/browser.ini rename toolkit/{modules => components/perfmonitoring}/tests/browser/browser_AddonWatcher.js (95%) create mode 100644 toolkit/components/perfmonitoring/tests/browser/browser_Addons_sample.xpi create mode 100644 toolkit/components/perfmonitoring/tests/browser/browser_compartments.html create mode 100644 toolkit/components/perfmonitoring/tests/browser/browser_compartments.js create mode 100644 toolkit/components/perfmonitoring/tests/browser/browser_compartments_frame.html create mode 100644 toolkit/components/perfmonitoring/tests/browser/browser_compartments_script.js create mode 100644 toolkit/components/perfmonitoring/tests/browser/head.js rename toolkit/components/{aboutperformance => perfmonitoring}/tests/xpcshell/test_compartments.js (75%) rename toolkit/components/{aboutperformance => perfmonitoring}/tests/xpcshell/xpcshell.ini (100%) delete mode 100644 toolkit/modules/AddonWatcher.jsm delete mode 100644 toolkit/modules/PerformanceStats.jsm create mode 100644 tools/profiler/ProfileGatherer.cpp create mode 100644 tools/profiler/ProfileGatherer.h diff --git a/browser/app/profile/palemoon.js b/browser/app/profile/palemoon.js index 1328eedf2c..4be883a2cf 100644 --- a/browser/app/profile/palemoon.js +++ b/browser/app/profile/palemoon.js @@ -924,8 +924,10 @@ pref("security.sandbox.windows.log", false); // On windows these levels are: // 0 - sandbox with USER_NON_ADMIN access token level // 1 - a more strict sandbox, which causes problems in specific areas -// 2 - a policy that we can reasonably call an effective sandbox -// 3 - an equivalent basic policy to the Chromium renderer processes +// 2 - a more strict sandbox, which might cause functionality issues. This now +// includes running at low integrity. +// 3 - the strongest settings we seem to be able to use without breaking +// everything, but will probably cause some functionality restrictions pref("security.sandbox.content.level", 0); // ID (a UUID when set by gecko) that is used as a per profile suffix to a low diff --git a/browser/modules/ProcessHangMonitor.jsm b/browser/modules/ProcessHangMonitor.jsm new file mode 100644 index 0000000000..d23668fbeb --- /dev/null +++ b/browser/modules/ProcessHangMonitor.jsm @@ -0,0 +1,321 @@ +/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 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/. */ + +"use strict"; + +let Cc = Components.classes; +let Ci = Components.interfaces; +let Cu = Components.utils; + +this.EXPORTED_SYMBOLS = ["ProcessHangMonitor"]; + +Cu.import("resource://gre/modules/Services.jsm"); + +/** + * This JSM is responsible for observing content process hang reports + * and asking the user what to do about them. See nsIHangReport for + * the platform interface. + */ + +/** + * If a hang hasn't been reported for more than 10 seconds, assume the + * content process has gotten unstuck (and hide the hang notification). + */ +const HANG_EXPIRATION_TIME = 10000; + +let ProcessHangMonitor = { + /** + * Collection of hang reports that haven't expired or been dismissed + * by the user. The keys are nsIHangReports and values keys are + * timers. Each time the hang is reported, the timer is refreshed so + * it expires after HANG_EXPIRATION_TIME. + */ + _activeReports: new Map(), + + /** + * Initialize hang reporting. Called once in the parent process. + */ + init: function() { + Services.obs.addObserver(this, "process-hang-report", false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + Services.ww.registerNotification(this); + }, + + /** + * Terminate JavaScript associated with the hang being reported for + * the selected browser in |win|. + */ + terminateScript: function(win) { + this.handleUserInput(win, report => report.terminateScript()); + }, + + /** + * Start devtools debugger for JavaScript associated with the hang + * being reported for the selected browser in |win|. + */ + debugScript: function(win) { + this.handleUserInput(win, report => { + function callback() { + report.endStartingDebugger(); + } + + report.beginStartingDebugger(); + + let svc = Cc["@mozilla.org/dom/slow-script-debug;1"].getService(Ci.nsISlowScriptDebug); + let handler = svc.remoteActivationHandler; + handler.handleSlowScriptDebug(report.scriptBrowser, callback); + }); + }, + + /** + * Kill the plugin process causing the hang being reported for the + * selected browser in |win|. + */ + terminatePlugin: function(win) { + this.handleUserInput(win, report => report.terminatePlugin()); + }, + + /** + * Kill the content process causing the hang being reported for the selected + * browser in |win|. + */ + terminateProcess: function(win) { + this.handleUserInput(win, report => report.terminateProcess()); + }, + + /** + * Update the "Options" pop-up menu for the hang notification + * associated with the selected browser in |win|. The menu should + * display only options that are relevant to the given report. + */ + refreshMenu: function(win) { + let report = this.findReport(win.gBrowser.selectedBrowser); + if (!report) { + return; + } + + function setVisible(id, visible) { + let item = win.document.getElementById(id); + item.hidden = !visible; + } + + if (report.hangType == report.SLOW_SCRIPT) { + setVisible("processHangTerminateScript", true); + setVisible("processHangDebugScript", true); + setVisible("processHangTerminatePlugin", false); + } else if (report.hangType == report.PLUGIN_HANG) { + setVisible("processHangTerminateScript", false); + setVisible("processHangDebugScript", false); + setVisible("processHangTerminatePlugin", true); + } + }, + + /** + * If there is a hang report associated with the selected browser in + * |win|, invoke |func| on that report and stop notifying the user + * about it. + */ + handleUserInput: function(win, func) { + let report = this.findReport(win.gBrowser.selectedBrowser); + if (!report) { + return; + } + this.removeReport(report); + + return func(report); + }, + + observe: function(subject, topic, data) { + switch (topic) { + case "xpcom-shutdown": + Services.obs.removeObserver(this, "xpcom-shutdown"); + Services.obs.removeObserver(this, "process-hang-report"); + Services.ww.unregisterNotification(this); + break; + + case "process-hang-report": + this.reportHang(subject.QueryInterface(Ci.nsIHangReport)); + break; + + case "domwindowopened": + // Install event listeners on the new window in case one of + // its tabs is already hung. + let win = subject.QueryInterface(Ci.nsIDOMWindow); + let listener = (ev) => { + win.removeEventListener("load", listener, true); + this.updateWindows(); + }; + win.addEventListener("load", listener, true); + break; + } + }, + + /** + * Find any active hang reports for the given element. + */ + findReport: function(browser) { + let frameLoader = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; + for (let [report, timer] of this._activeReports) { + if (report.isReportForBrowser(frameLoader)) { + return report; + } + } + return null; + }, + + /** + * Iterate over all XUL windows and ensure that the proper hang + * reports are shown for each one. Also install event handlers in + * each window to watch for events that would cause a different hang + * report to be displayed. + */ + updateWindows: function() { + let e = Services.wm.getEnumerator("navigator:browser"); + while (e.hasMoreElements()) { + let win = e.getNext(); + + this.updateWindow(win); + + // Only listen for these events if there are active hang reports. + if (this._activeReports.size) { + this.trackWindow(win); + } else { + this.untrackWindow(win); + } + } + }, + + /** + * If there is a hang report for the current tab in |win|, display it. + */ + updateWindow: function(win) { + let report = this.findReport(win.gBrowser.selectedBrowser); + + if (report) { + this.showNotification(win, report); + } else { + this.hideNotification(win); + } + }, + + /** + * Show the notification for a hang. + */ + showNotification: function(win, report) { + let nb = win.document.getElementById("high-priority-global-notificationbox"); + let notification = nb.getNotificationWithValue("process-hang"); + if (notification) { + return; + } + + let bundle = win.gNavigatorBundle; + let brandBundle = win.document.getElementById("bundle_brand"); + let appName = brandBundle.getString("brandShortName"); + let message = bundle.getFormattedString( + "processHang.message", + [appName]); + + let buttons = [{ + label: bundle.getString("processHang.button.label"), + accessKey: bundle.getString("processHang.button.accessKey"), + popup: "processHangOptions", + callback: null, + }]; + + nb.appendNotification(message, "process-hang", + "chrome://browser/content/aboutRobots-icon.png", + nb.PRIORITY_WARNING_HIGH, buttons); + }, + + /** + * Ensure that no hang notifications are visible in |win|. + */ + hideNotification: function(win) { + let nb = win.document.getElementById("high-priority-global-notificationbox"); + let notification = nb.getNotificationWithValue("process-hang"); + if (notification) { + nb.removeNotification(notification); + } + }, + + /** + * Install event handlers on |win| to watch for events that would + * cause a different hang report to be displayed. + */ + trackWindow: function(win) { + win.gBrowser.tabContainer.addEventListener("TabSelect", this, true); + win.gBrowser.tabContainer.addEventListener("TabRemotenessChange", this, true); + }, + + untrackWindow: function(win) { + win.gBrowser.tabContainer.removeEventListener("TabSelect", this, true); + win.gBrowser.tabContainer.removeEventListener("TabRemotenessChange", this, true); + }, + + handleEvent: function(event) { + let win = event.target.ownerDocument.defaultView; + + // If a new tab is selected or if a tab changes remoteness, then + // we may need to show or hide a hang notification. + + if (event.type == "TabSelect" || event.type == "TabRemotenessChange") { + this.updateWindow(win); + } + }, + + /** + * Handle a potentially new hang report. If it hasn't been seen + * before, show a notification for it in all open XUL windows. + */ + reportHang: function(report) { + // If this hang was already reported, then reset the timer for it. + if (this._activeReports.has(report)) { + let timer = this._activeReports.get(report); + timer.cancel(); + timer.initWithCallback(this, HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT); + return; + } + + // On e10s this counts slow-script/hanged-plugin notice only once. + // This code is not reached on non-e10s. + if (report.hangType == report.SLOW_SCRIPT) { + // On non-e10s, SLOW_SCRIPT_NOTICE_COUNT is probed at nsGlobalWindow.cpp + Services.telemetry.getHistogramById("SLOW_SCRIPT_NOTICE_COUNT").add(); + } else if (report.hangType == report.PLUGIN_HANG) { + // On non-e10s we have sufficient plugin telemetry probes, + // so PLUGIN_HANG_NOTICE_COUNT is only probed on e10s. + Services.telemetry.getHistogramById("PLUGIN_HANG_NOTICE_COUNT").add(); + } + + // Otherwise create a new timer and display the report. + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback(this, HANG_EXPIRATION_TIME, timer.TYPE_ONE_SHOT); + + this._activeReports.set(report, timer); + this.updateWindows(); + }, + + /** + * Dismiss a hang report because the user closed the notification + * for it or the report expired. + */ + removeReport: function(report) { + this._activeReports.delete(report); + this.updateWindows(); + }, + + /** + * Callback for when HANG_EXPIRATION_TIME has elapsed. + */ + notify: function(timer) { + for (let [otherReport, otherTimer] of this._activeReports) { + if (otherTimer === timer) { + this.removeReport(otherReport); + otherReport.userCanceled(); + break; + } + } + }, +}; diff --git a/build/clang-plugin/tests/Makefile.in b/build/clang-plugin/tests/Makefile.in index 47393978c7..cf2708a44a 100644 --- a/build/clang-plugin/tests/Makefile.in +++ b/build/clang-plugin/tests/Makefile.in @@ -9,7 +9,10 @@ OS_CXXFLAGS := $(filter-out -W%,$(OS_CXXFLAGS)) -fsyntax-only -Xclang -verify -f include $(topsrcdir)/config/rules.mk -export:: $(OBJS) +target:: $(OBJS) # We don't actually build anything. .PHONY: $(OBJS) + +# Don't actually build a library, since we don't actually build objects. +$(LIBRARY): EXPAND_LIBS_GEN=true diff --git a/build/clang-plugin/tests/moz.build b/build/clang-plugin/tests/moz.build index 5a6a26b503..c52fc5c7e5 100644 --- a/build/clang-plugin/tests/moz.build +++ b/build/clang-plugin/tests/moz.build @@ -4,6 +4,9 @@ # 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/. +# dummy library name to avoid skipping building the sources here. +Library('clang-plugin-tests') + SOURCES += [ 'TestBadImplicitConversionCtor.cpp', 'TestCustomHeap.cpp', diff --git a/build/templates.mozbuild b/build/templates.mozbuild index 1d99f3a27d..f160e76982 100644 --- a/build/templates.mozbuild +++ b/build/templates.mozbuild @@ -89,11 +89,17 @@ def HostStdCppCompat(): @template -def HostProgram(name): +def HostProgram(name, c_only=False): '''Template for build tools executables.''' HOST_PROGRAM = name - HostStdCppCompat() + # With context-based templates, this won't be needed anymore, and will + # work better than relying on the caller setting it, but at the moment, + # this is the best we have. And it doesn't matter /that/ much, so there's + # really only one place using this, where it does matter to avoid the + # extra dependency (because it creates a circular one). + if not c_only: + HostStdCppCompat() @template diff --git a/config/external/nss/nss.def b/config/external/nss/nss.def index de1772f83e..c74f4a8671 100644 --- a/config/external/nss/nss.def +++ b/config/external/nss/nss.def @@ -23,6 +23,7 @@ CERT_CacheOCSPResponseFromSideChannel CERT_CertChainFromCert CERT_CertificateRequestTemplate DATA CERT_CertificateTemplate DATA +CERT_CertListFromCert CERT_ChangeCertTrust CERT_CheckCertUsage CERT_CheckCertValidTimes diff --git a/config/moz.build b/config/moz.build index dbf819cb98..ab3a56e816 100644 --- a/config/moz.build +++ b/config/moz.build @@ -24,7 +24,10 @@ if CONFIG['HOST_OS_ARCH'] != 'WINNT': 'nsinstall.c', 'pathsub.c', ] - HostProgram('nsinstall_real') + # stdc++compat depends on config/export, so avoid a circular + # dependency added by HostProgram depending on stdc++compat, + # while the program here is in C. + HostProgram('nsinstall_real', c_only=True) if CONFIG['GKMEDIAS_SHARED_LIBRARY']: DEFINES['GKMEDIAS_SHARED_LIBRARY'] = True diff --git a/config/recurse.mk b/config/recurse.mk index 4d5730ce4f..2774445409 100644 --- a/config/recurse.mk +++ b/config/recurse.mk @@ -148,7 +148,7 @@ accessible/xpcom/export: xpcom/xpidl/export widget/android/bindings/export: build/annotationProcessors/export ifdef ENABLE_CLANG_PLUGIN -$(filter-out build/clang-plugin/%,$(compile_targets)): build/clang-plugin/target build/clang-plugin/tests/target +$(filter-out config/host build/unix/stdc++compat/% build/clang-plugin/%,$(compile_targets)): build/clang-plugin/target build/clang-plugin/tests/target build/clang-plugin/tests/target: build/clang-plugin/target endif @@ -172,3 +172,6 @@ endif # happen at the same time (bug #1146738) js/src/target: js/src/host endif +# Most things are built during compile (target/host), but some things happen during export +# Those need to depend on config/export for system wrappers. +$(addprefix build/unix/stdc++compat/,target host) build/clang-plugin/target: config/export diff --git a/config/stl-headers b/config/stl-headers index dc7f6f0fb7..723c42b33b 100644 --- a/config/stl-headers +++ b/config/stl-headers @@ -20,9 +20,11 @@ new algorithm atomic deque +functional ios iosfwd iostream +istream iterator limits list @@ -36,6 +38,7 @@ utility vector cassert climits +cmath cstdarg cstdio cstdlib diff --git a/config/system-headers b/config/system-headers index c1e2f41a14..49ef5bafe4 100644 --- a/config/system-headers +++ b/config/system-headers @@ -491,6 +491,7 @@ fstream fstream.h ft2build.h fts.h +functional gconf/gconf-client.h Gdiplus.h gdk/gdk.h @@ -552,6 +553,7 @@ image.h imagehlp.h imm.h initguid.h +initializer_list InterfaceDefs.h InternetConfig.h IntlResources.h @@ -568,6 +570,7 @@ ios iosfwd iostream iostream.h +istream iterator JavaControl.h JavaEmbedding/JavaControl.h diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp index 6a8e8d31b1..8ee636e4a4 100644 --- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -170,7 +170,9 @@ public: mMethodString = aString; for (uint32_t i = 0; i < aArguments.Length(); ++i) { - mArguments.AppendElement(aArguments[i]); + if (!mArguments.AppendElement(aArguments[i])) { + return; + } } } @@ -604,7 +606,9 @@ private: return; } - arguments.AppendElement(value); + if (!arguments.AppendElement(value)) { + return; + } } mConsole->ProfileMethod(aCx, mAction, arguments); @@ -763,8 +767,8 @@ Console::Time(JSContext* aCx, const JS::Handle aTime) Sequence data; SequenceRooter rooter(aCx, &data); - if (!aTime.isUndefined()) { - data.AppendElement(aTime); + if (!aTime.isUndefined() && !data.AppendElement(aTime)) { + return; } Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data); @@ -776,8 +780,8 @@ Console::TimeEnd(JSContext* aCx, const JS::Handle aTime) Sequence data; SequenceRooter rooter(aCx, &data); - if (!aTime.isUndefined()) { - data.AppendElement(aTime); + if (!aTime.isUndefined() && !data.AppendElement(aTime)) { + return; } Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data); @@ -789,8 +793,8 @@ Console::TimeStamp(JSContext* aCx, const JS::Handle aData) Sequence data; SequenceRooter rooter(aCx, &data); - if (aData.isString()) { - data.AppendElement(aData); + if (aData.isString() && !data.AppendElement(aData)) { + return; } Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data); @@ -829,7 +833,9 @@ Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, Sequence& sequence = event.mArguments.Value(); for (uint32_t i = 0; i < aData.Length(); ++i) { - sequence.AppendElement(aData[i]); + if (!sequence.AppendElement(aData[i])) { + return; + } } JS::Rooted eventValue(aCx); @@ -1234,13 +1240,18 @@ Console::ProcessCallData(ConsoleCallData* aData) case MethodAssert: event.mArguments.Construct(); event.mStyles.Construct(); - ProcessArguments(cx, aData->mArguments, event.mArguments.Value(), - event.mStyles.Value()); + if (!ProcessArguments(cx, aData->mArguments, event.mArguments.Value(), + event.mStyles.Value())) { + return; + } + break; default: event.mArguments.Construct(); - ArgumentsToValueList(aData->mArguments, event.mArguments.Value()); + if (!ArgumentsToValueList(aData->mArguments, event.mArguments.Value())) { + return; + } } if (aData->mMethodName == MethodGroup || @@ -1356,30 +1367,55 @@ Console::ProcessCallData(ConsoleCallData* aData) } } -void +namespace { + +// Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence. +bool +FlushOutput(JSContext* aCx, Sequence& aSequence, nsString &aOutput) +{ + if (!aOutput.IsEmpty()) { + JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, + aOutput.get(), + aOutput.Length())); + if (!str) { + return false; + } + + if (!aSequence.AppendElement(JS::StringValue(str))) { + return false; + } + + aOutput.Truncate(); + } + + return true; +} + +} // anonymous namespace + +bool Console::ProcessArguments(JSContext* aCx, const nsTArray>& aData, Sequence& aSequence, Sequence& aStyles) { if (aData.IsEmpty()) { - return; + return true; } if (aData.Length() == 1 || !aData[0].isString()) { - ArgumentsToValueList(aData, aSequence); - return; + return ArgumentsToValueList(aData, aSequence); } JS::Rooted format(aCx, aData[0]); JS::Rooted jsString(aCx, JS::ToString(aCx, format)); if (!jsString) { - return; + return false; } nsAutoJSString string; if (!string.init(aCx, jsString)) { - return; + return false; } nsString::const_iterator start, end; @@ -1467,16 +1503,8 @@ Console::ProcessArguments(JSContext* aCx, case 'o': case 'O': { - if (!output.IsEmpty()) { - JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, - output.get(), - output.Length())); - if (!str) { - return; - } - - aSequence.AppendElement(JS::StringValue(str)); - output.Truncate(); + if (!FlushOutput(aCx, aSequence, output)) { + return false; } JS::Rooted v(aCx); @@ -1484,38 +1512,38 @@ Console::ProcessArguments(JSContext* aCx, v = aData[index++]; } - aSequence.AppendElement(v); + if (!aSequence.AppendElement(v)) { + return false; + } + break; } case 'c': { - if (!output.IsEmpty()) { - JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, - output.get(), - output.Length())); - if (!str) { - return; - } - - aSequence.AppendElement(JS::StringValue(str)); - output.Truncate(); + if (!FlushOutput(aCx, aSequence, output)) { + return false; } if (index < aData.Length()) { JS::Rooted v(aCx, aData[index++]); JS::Rooted jsString(aCx, JS::ToString(aCx, v)); if (!jsString) { - return; + return false; } int32_t diff = aSequence.Length() - aStyles.Length(); if (diff > 0) { for (int32_t i = 0; i < diff; i++) { - aStyles.AppendElement(JS::NullValue()); + if (!aStyles.AppendElement(JS::NullValue())) { + return false; + } } } - aStyles.AppendElement(JS::StringValue(jsString)); + + if (!aStyles.AppendElement(JS::StringValue(jsString))) { + return false; + } } break; } @@ -1525,12 +1553,12 @@ Console::ProcessArguments(JSContext* aCx, JS::Rooted value(aCx, aData[index++]); JS::Rooted jsString(aCx, JS::ToString(aCx, value)); if (!jsString) { - return; + return false; } nsAutoJSString v; if (!v.init(aCx, jsString)) { - return; + return false; } output.Append(v); @@ -1544,7 +1572,7 @@ Console::ProcessArguments(JSContext* aCx, int32_t v; if (!JS::ToInt32(aCx, value, &v)) { - return; + return false; } nsCString format; @@ -1559,7 +1587,7 @@ Console::ProcessArguments(JSContext* aCx, double v; if (!JS::ToNumber(aCx, value, &v)) { - return; + return false; } nsCString format; @@ -1574,20 +1602,23 @@ Console::ProcessArguments(JSContext* aCx, } } - if (!output.IsEmpty()) { - JS::Rooted str(aCx, JS_NewUCStringCopyN(aCx, output.get(), - output.Length())); - if (!str) { - return; - } + if (!FlushOutput(aCx, aSequence, output)) { + return false; + } - aSequence.AppendElement(JS::StringValue(str)); + // Discard trailing style element if there is no output to apply it to. + if (aStyles.Length() > aSequence.Length()) { + aStyles.TruncateLength(aSequence.Length()); } // The rest of the array, if unused by the format string. for (; index < aData.Length(); ++index) { - aSequence.AppendElement(aData[index]); + if (!aSequence.AppendElement(aData[index])) { + return false; + } } + + return true; } void @@ -1713,13 +1744,17 @@ Console::StopTimer(JSContext* aCx, const JS::Value& aName, return value; } -void +bool Console::ArgumentsToValueList(const nsTArray>& aData, Sequence& aSequence) { for (uint32_t i = 0; i < aData.Length(); ++i) { - aSequence.AppendElement(aData[i]); + if (!aSequence.AppendElement(aData[i])) { + return false; + } } + + return true; } JS::Value diff --git a/dom/base/Console.h b/dom/base/Console.h index f72f273803..2bb14c81da 100644 --- a/dom/base/Console.h +++ b/dom/base/Console.h @@ -154,7 +154,7 @@ private: // finds based the format string. The index of the styles matches the indexes // of elements that need the custom styling from aSequence. For elements with // no custom styling the array is padded with null elements. - void + bool ProcessArguments(JSContext* aCx, const nsTArray>& aData, Sequence& aSequence, Sequence& aStyles); @@ -178,7 +178,7 @@ private: DOMHighResTimeStamp aTimestamp); // The method populates a Sequence from an array of JS::Value. - void + bool ArgumentsToValueList(const nsTArray>& aData, Sequence& aSequence); diff --git a/dom/base/File.cpp b/dom/base/File.cpp index c2d9175c3c..29798da8d4 100644 --- a/dom/base/File.cpp +++ b/dom/base/File.cpp @@ -557,7 +557,7 @@ File::GetLastModifiedDate(ErrorResult& aRv) return Date(); } - return Date(value); + return Date(JS::TimeClip(value)); } int64_t diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index 9b279eed47..637b9bd566 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -43,6 +43,8 @@ enum StructuredCloneTags { SCTAG_DOM_NFC_NDEF, + SCTAG_DOM_RTC_CERTIFICATE, + SCTAG_DOM_MAX }; diff --git a/dom/base/WebSocket.cpp b/dom/base/WebSocket.cpp index 610e42d945..b830ffe6f0 100644 --- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -1004,7 +1004,11 @@ WebSocket::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv) { Sequence protocols; - protocols.AppendElement(aProtocol); + if (!protocols.AppendElement(aProtocol)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv); } diff --git a/dom/base/nsDOMMutationObserver.cpp b/dom/base/nsDOMMutationObserver.cpp index fefbfe4d04..ddf5a6a61f 100644 --- a/dom/base/nsDOMMutationObserver.cpp +++ b/dom/base/nsDOMMutationObserver.cpp @@ -676,7 +676,9 @@ nsDOMMutationObserver::TakeRecords( } void -nsDOMMutationObserver::GetObservingInfo(nsTArray >& aResult) +nsDOMMutationObserver::GetObservingInfo( + nsTArray>& aResult, + mozilla::ErrorResult& aRv) { aResult.SetCapacity(mReceivers.Count()); for (int32_t i = 0; i < mReceivers.Count(); ++i) { @@ -695,7 +697,10 @@ nsDOMMutationObserver::GetObservingInfo(nsTArray mozilla::dom::Sequence& filtersAsStrings = info.mAttributeFilter.Value(); for (int32_t j = 0; j < filters.Count(); ++j) { - filtersAsStrings.AppendElement(nsDependentAtomString(filters[j])); + if (!filtersAsStrings.AppendElement(nsDependentAtomString(filters[j]))) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } } } info.mObservedNode = mr->Target(); diff --git a/dom/base/nsDOMMutationObserver.h b/dom/base/nsDOMMutationObserver.h index 251dbf24b5..91ccfede41 100644 --- a/dom/base/nsDOMMutationObserver.h +++ b/dom/base/nsDOMMutationObserver.h @@ -481,7 +481,8 @@ public: void HandleMutation(); - void GetObservingInfo(nsTArray >& aResult); + void GetObservingInfo(nsTArray>& aResult, + mozilla::ErrorResult& aRv); mozilla::dom::MutationCallback* MutationCallback() { return mCallback; } diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index b05fcbf89e..75948814a0 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -11250,6 +11250,12 @@ ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) NS_ASSERTION(!root->IsFullScreenDoc(), "Fullscreen root should no longer be a fullscreen doc..."); + // Dispatch MozExitedDomFullscreen to the last document in + // the list since we want this event to follow the same path + // MozEnteredDomFullscreen dispatched. + nsRefPtr asyncDispatcher = new AsyncEventDispatcher( + changed.LastElement(), NS_LITERAL_STRING("MozExitedDomFullscreen"), true, true); + asyncDispatcher->PostDOMEvent(); // Move the top-level window out of fullscreen mode. SetWindowFullScreen(root, false); } @@ -11406,6 +11412,9 @@ nsDocument::RestorePreviousFullScreenState() // move the top-level window out of fullscreen mode. NS_ASSERTION(!GetFullscreenRootDocument(this)->IsFullScreenDoc(), "Should have cleared all docs' stacks"); + nsRefPtr asyncDispatcher = new AsyncEventDispatcher( + this, NS_LITERAL_STRING("MozExitedDomFullscreen"), true, true); + asyncDispatcher->PostDOMEvent(); SetWindowFullScreen(this, false); } } diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index e429379583..b375531ae7 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1084,6 +1084,7 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) mAddActiveEventFuzzTime(true), mIsFrozen(false), mFullScreen(false), + mFullscreenMode(false), mIsClosed(false), mInClose(false), mHavePendingClose(false), @@ -6097,16 +6098,17 @@ nsGlobalWindow::SetFullScreen(bool aFullScreen) } nsresult -nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust, gfx::VRHMDInfo* aHMD) +nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aFullscreenMode, + gfx::VRHMDInfo* aHMD) { MOZ_ASSERT(IsOuterWindow()); NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); - // Only chrome can change our fullScreen mode, unless we're running in - // untrusted mode. + // Only chrome can change our fullscreen mode. Otherwise, the state + // can only be changed for DOM fullscreen. if (aFullScreen == FullScreen() || - (aRequireTrust && !nsContentUtils::IsCallerChrome())) { + (aFullscreenMode && !nsContentUtils::IsCallerChrome())) { return NS_OK; } @@ -6119,7 +6121,7 @@ nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust, gfx: if (!window) return NS_ERROR_FAILURE; if (rootItem != mDocShell) - return window->SetFullScreenInternal(aFullScreen, aRequireTrust, aHMD); + return window->SetFullScreenInternal(aFullScreen, aFullscreenMode, aHMD); // make sure we don't try to set full screen on a non-chrome window, // which might happen in embedding world @@ -6130,6 +6132,20 @@ nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust, gfx: if (mFullScreen == aFullScreen) return NS_OK; + // Note that although entering DOM fullscreen could also cause + // consequential calls to this method, those calls will be skipped + // at the condition above. + if (aFullscreenMode) { + mFullscreenMode = aFullScreen; + } else { + // If we are exiting from DOM fullscreen while we + // initially make the window fullscreen because of + // fullscreen mode, don't restore the window. + if (!aFullScreen && mFullscreenMode) { + return NS_OK; + } + } + // dispatch a "fullscreen" DOM event so that XUL apps can // respond visually if we are kicked into full screen mode if (!DispatchCustomEvent(NS_LITERAL_STRING("fullscreen"))) { @@ -6159,6 +6175,9 @@ nsGlobalWindow::SetFullScreenInternal(bool aFullScreen, bool aRequireTrust, gfx: if (aHMD) { screen = aHMD->GetScreen(); } + if (!aFullscreenMode) { + widget->PrepareForDOMFullscreenTransition(); + } widget->MakeFullScreen(aFullScreen, screen); } } diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index c631e1a776..dc565da584 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -488,7 +488,7 @@ public: virtual void RefreshCompartmentPrincipal() override; // Outer windows only. - virtual nsresult SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust, + virtual nsresult SetFullScreenInternal(bool aIsFullscreen, bool aFullscreenMode, mozilla::gfx::VRHMDInfo *aHMD = nullptr) override; bool FullScreen() const; @@ -1537,6 +1537,7 @@ protected: // These members are only used on outer window objects. Make sure // you never set any of these on an inner object! bool mFullScreen : 1; + bool mFullscreenMode : 1; bool mIsClosed : 1; bool mInClose : 1; // mHavePendingClose means we've got a termination function set to diff --git a/dom/base/nsIObjectLoadingContent.idl b/dom/base/nsIObjectLoadingContent.idl index d253e25ac2..6cf0c535f1 100644 --- a/dom/base/nsIObjectLoadingContent.idl +++ b/dom/base/nsIObjectLoadingContent.idl @@ -25,7 +25,7 @@ class nsNPAPIPluginInstance; * interface to mirror this interface when changing it. */ -[scriptable, uuid(16c14177-52eb-49d3-9842-a1a0b92be11a)] +[scriptable, uuid(5efbd411-5bbe-4de1-9f3a-1c3459696eb2)] interface nsIObjectLoadingContent : nsISupports { /** @@ -188,4 +188,12 @@ interface nsIObjectLoadingContent : nsISupports * This method will disable the play-preview plugin state. */ void cancelPlayPreview(); + + /** + * If this plugin runs out-of-process, it has a runID to differentiate + * between different times the plugin process has been instantiated. + * + * This throws NS_ERROR_NOT_IMPLEMENTED for in-process plugins. + */ + readonly attribute unsigned long runID; }; diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index dd14feee99..3bde923ee0 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -62,6 +62,10 @@ #ifdef MOZ_NFC #include "mozilla/dom/MozNDEFRecord.h" #endif // MOZ_NFC +#ifdef MOZ_WEBRTC +#include "mozilla/dom/RTCCertificate.h" +#include "mozilla/dom/RTCCertificateBinding.h" +#endif #include "mozilla/dom/StructuredClone.h" #include "mozilla/dom/SubtleCryptoBinding.h" #include "mozilla/ipc/BackgroundUtils.h" @@ -2551,6 +2555,29 @@ NS_DOMReadStructuredClone(JSContext* cx, #endif } + if (tag == SCTAG_DOM_RTC_CERTIFICATE) { +#ifdef MOZ_WEBRTC + nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx)); + if (!global) { + return nullptr; + } + + // Prevent the return value from being trashed by a GC during ~nsRefPtr. + JS::Rooted result(cx); + { + nsRefPtr cert = new RTCCertificate(global); + if (!cert->ReadStructuredClone(reader)) { + result = nullptr; + } else { + result = cert->WrapObject(cx, nullptr); + } + } + return result; +#else + return nullptr; +#endif + } + // Don't know what this is. Bail. xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR); return nullptr; @@ -2575,6 +2602,15 @@ NS_DOMWriteStructuredClone(JSContext* cx, key->WriteStructuredClone(writer); } +#ifdef MOZ_WEBRTC + // Handle WebRTC Certificate cloning + RTCCertificate* cert; + if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, obj, cert))) { + return JS_WriteUint32Pair(writer, SCTAG_DOM_RTC_CERTIFICATE, 0) && + cert->WriteStructuredClone(writer); + } +#endif + if (xpc::IsReflector(obj)) { nsCOMPtr base = xpc::UnwrapReflectorToISupports(obj); nsCOMPtr principal = do_QueryInterface(base); diff --git a/dom/base/nsJSTimeoutHandler.cpp b/dom/base/nsJSTimeoutHandler.cpp index 5b8199c4e1..30a5cdef53 100644 --- a/dom/base/nsJSTimeoutHandler.cpp +++ b/dom/base/nsJSTimeoutHandler.cpp @@ -409,7 +409,7 @@ NS_CreateJSTimeoutHandler(nsGlobalWindow *aWindow, Function& aFunction, ErrorResult& aError) { FallibleTArray > args; - if (!args.AppendElements(aArguments)) { + if (!args.AppendElements(aArguments, fallible)) { aError.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp index 964bd68575..143d599e69 100644 --- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -698,6 +698,8 @@ nsObjectLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent) nsObjectLoadingContent::nsObjectLoadingContent() : mType(eType_Loading) , mFallbackType(eFallbackAlternate) + , mRunID(0) + , mHasRunID(false) , mChannelLoaded(false) , mInstantiating(false) , mNetworkCreated(true) @@ -812,6 +814,17 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading) mInstanceOwner = newOwner; + if (mInstanceOwner) { + nsRefPtr inst; + rv = mInstanceOwner->GetInstance(getter_AddRefs(inst)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = inst->GetRunID(&mRunID); + mHasRunID = NS_SUCCEEDED(rv); + } + // Ensure the frame did not change during instantiation re-entry (common). // HasNewFrame would not have mInstanceOwner yet, so the new frame would be // dangling. (Bug 854082) @@ -3148,6 +3161,24 @@ nsObjectLoadingContent::CancelPlayPreview() return NS_OK; } +NS_IMETHODIMP +nsObjectLoadingContent::GetRunID(uint32_t* aRunID) +{ + if (NS_WARN_IF(!nsContentUtils::IsCallerChrome())) { + return NS_ERROR_NOT_AVAILABLE; + } + if (NS_WARN_IF(!aRunID)) { + return NS_ERROR_INVALID_POINTER; + } + if (!mHasRunID) { + // The plugin instance must not have a run ID, so we must + // be running the plugin in-process. + return NS_ERROR_NOT_IMPLEMENTED; + } + *aRunID = mRunID; + return NS_OK; +} + static bool sPrefsInitialized; static uint32_t sSessionTimeoutMinutes; static uint32_t sPersistentTimeoutDays; diff --git a/dom/base/nsObjectLoadingContent.h b/dom/base/nsObjectLoadingContent.h index 6569407ceb..8a9de63808 100644 --- a/dom/base/nsObjectLoadingContent.h +++ b/dom/base/nsObjectLoadingContent.h @@ -237,6 +237,18 @@ class nsObjectLoadingContent : public nsImageLoadingContent JS::MutableHandle aRetval, mozilla::ErrorResult& aRv); + uint32_t GetRunID(mozilla::ErrorResult& aRv) + { + uint32_t runID; + nsresult rv = GetRunID(&runID); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return 0; + } + + return runID; + } + protected: /** * Begins loading the object when called @@ -583,6 +595,9 @@ class nsObjectLoadingContent : public nsImageLoadingContent // The type of fallback content we're showing (see ObjectState()) FallbackType mFallbackType : 8; + uint32_t mRunID; + bool mHasRunID; + // If true, we have opened a channel as the listener and it has reached // OnStartRequest. Does not get set for channels that are passed directly to // the plugin listener. diff --git a/dom/base/nsPIDOMWindow.h b/dom/base/nsPIDOMWindow.h index 53e877d2f8..dedbedceda 100644 --- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -481,15 +481,16 @@ public: /** * Moves the top-level window into fullscreen mode if aIsFullScreen is true, - * otherwise exits fullscreen. If aRequireTrust is true, this method only - * changes window state in a context trusted for write. + * otherwise exits fullscreen. If aFullscreenMode is true, this method is + * called for fullscreen mode instead of DOM fullscreen, which means it can + * only change window state in a context trusted for write. * * If aHMD is not null, the window is made full screen on the given VR HMD * device instead of its currrent display. * * Outer windows only. */ - virtual nsresult SetFullScreenInternal(bool aIsFullScreen, bool aRequireTrust, + virtual nsresult SetFullScreenInternal(bool aIsFullscreen, bool aFullscreenMode, mozilla::gfx::VRHMDInfo *aHMD = nullptr) = 0; /** diff --git a/dom/bindings/Date.cpp b/dom/bindings/Date.cpp index cb7263fd72..99c914f942 100644 --- a/dom/bindings/Date.cpp +++ b/dom/bindings/Date.cpp @@ -6,8 +6,9 @@ #include "mozilla/dom/Date.h" -#include "jsapi.h" // for JS_ObjectIsDate, JS_NewDateObjectMsec +#include "jsapi.h" // for JS_ObjectIsDate #include "jsfriendapi.h" // for DateGetMsecSinceEpoch +#include "js/Date.h" // for JS::NewDateObject, JS::ClippedTime, JS::TimeClip #include "js/RootingAPI.h" // for Rooted, MutableHandle #include "js/Value.h" // for Value #include "mozilla/FloatingPoint.h" // for IsNaN, UnspecifiedNaN @@ -15,30 +16,22 @@ namespace mozilla { namespace dom { -Date::Date() - : mMsecSinceEpoch(UnspecifiedNaN()) -{ -} - -bool -Date::IsUndefined() const -{ - return IsNaN(mMsecSinceEpoch); -} - bool Date::SetTimeStamp(JSContext* aCx, JSObject* aObject) { JS::Rooted obj(aCx, aObject); MOZ_ASSERT(JS_ObjectIsDate(aCx, obj)); - mMsecSinceEpoch = js::DateGetMsecSinceEpoch(aCx, obj); + double msecs = js::DateGetMsecSinceEpoch(aCx, obj); + JS::ClippedTime time = JS::TimeClip(msecs); + MOZ_ASSERT(NumbersAreIdentical(msecs, time.toDouble())); + mMsecSinceEpoch = time; return true; } bool Date::ToDateObject(JSContext* aCx, JS::MutableHandle aRval) const { - JSObject* obj = JS_NewDateObjectMsec(aCx, mMsecSinceEpoch); + JSObject* obj = JS::NewDateObject(aCx, mMsecSinceEpoch); if (!obj) { return false; } diff --git a/dom/bindings/Date.h b/dom/bindings/Date.h index b7db416fb3..66c893e4db 100644 --- a/dom/bindings/Date.h +++ b/dom/bindings/Date.h @@ -9,6 +9,7 @@ #ifndef mozilla_dom_Date_h #define mozilla_dom_Date_h +#include "js/Date.h" #include "js/TypeDecls.h" namespace mozilla { @@ -17,21 +18,33 @@ namespace dom { class Date { public: - // Not inlining much here to avoid the includes we'd need. - Date(); - explicit Date(double aMilliseconds) + Date() {} + explicit Date(JS::ClippedTime aMilliseconds) : mMsecSinceEpoch(aMilliseconds) {} - bool IsUndefined() const; - double TimeStamp() const + bool IsUndefined() const + { + return !mMsecSinceEpoch.isValid(); + } + + JS::ClippedTime TimeStamp() const { return mMsecSinceEpoch; } - void SetTimeStamp(double aMilliseconds) + + // Returns an integer in the range [-8.64e15, +8.64e15] (-0 excluded), *or* + // returns NaN. DO NOT ASSUME THIS IS FINITE! + double ToDouble() const + { + return mMsecSinceEpoch.toDouble(); + } + + void SetTimeStamp(JS::ClippedTime aMilliseconds) { mMsecSinceEpoch = aMilliseconds; } + // Can return false if CheckedUnwrap fails. This will NOT throw; // callers should do it as needed. bool SetTimeStamp(JSContext* aCx, JSObject* aObject); @@ -39,7 +52,7 @@ public: bool ToDateObject(JSContext* aCx, JS::MutableHandle aRval) const; private: - double mMsecSinceEpoch; + JS::ClippedTime mMsecSinceEpoch; }; } // namespace dom diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 79984a7a06..11260ebd4c 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -2724,7 +2724,8 @@ CanvasRenderingContext2D::Stroke(const CanvasPath& path) Redraw(); } -void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement) +void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement, + ErrorResult& aRv) { EnsureUserSpacePath(); @@ -2756,8 +2757,12 @@ void CanvasRenderingContext2D::DrawFocusIfNeeded(mozilla::dom::Element& aElement // set dashing for foreground FallibleTArray& dash = CurrentState().dash; - dash.AppendElement(1); - dash.AppendElement(1); + for (uint32_t i = 0; i < 2; ++i) { + if (!dash.AppendElement(1)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + } // set the foreground focus color CurrentState().SetColorStyle(Style::STROKE, NS_RGBA(0,0,0, 255)); @@ -4048,7 +4053,8 @@ CanvasRenderingContext2D::SetMozDashOffset(double mozDashOffset) } void -CanvasRenderingContext2D::SetLineDash(const Sequence& aSegments) +CanvasRenderingContext2D::SetLineDash(const Sequence& aSegments, + ErrorResult& aRv) { FallibleTArray dash; @@ -4058,11 +4064,18 @@ CanvasRenderingContext2D::SetLineDash(const Sequence& aSegments) // taken care of by WebIDL return; } - dash.AppendElement(aSegments[x]); + + if (!dash.AppendElement(aSegments[x])) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } } if (aSegments.Length() % 2) { // If the number of elements is odd, concatenate again for (uint32_t x = 0; x < aSegments.Length(); x++) { - dash.AppendElement(aSegments[x]); + if (!dash.AppendElement(aSegments[x])) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } } } diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 3626fffe21..ad4034e5e9 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -252,7 +252,7 @@ public: void Fill(const CanvasPath& path, const CanvasWindingRule& winding); void Stroke(); void Stroke(const CanvasPath& path); - void DrawFocusIfNeeded(mozilla::dom::Element& element); + void DrawFocusIfNeeded(mozilla::dom::Element& element, ErrorResult& aRv); bool DrawCustomFocusRing(mozilla::dom::Element& element); void Clip(const CanvasWindingRule& winding); void Clip(const CanvasPath& path, const CanvasWindingRule& winding); @@ -431,7 +431,8 @@ public: void SetMozDash(JSContext* cx, const JS::Value& mozDash, mozilla::ErrorResult& error); - void SetLineDash(const Sequence& mSegments); + void SetLineDash(const Sequence& mSegments, + mozilla::ErrorResult& aRv); void GetLineDash(nsTArray& mSegments) const; void SetLineDashOffset(double mOffset); diff --git a/dom/canvas/WebGL2Context.h b/dom/canvas/WebGL2Context.h index eef6e81541..b4fdee0e18 100644 --- a/dom/canvas/WebGL2Context.h +++ b/dom/canvas/WebGL2Context.h @@ -10,6 +10,7 @@ namespace mozilla { +class ErrorResult; class WebGLSampler; class WebGLSync; class WebGLTransformFeedback; @@ -55,9 +56,10 @@ public: GLbitfield mask, GLenum filter); void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); void GetInternalformatParameter(JSContext*, GLenum target, GLenum internalformat, GLenum pname, JS::MutableHandleValue retval); - void InvalidateFramebuffer(GLenum target, const dom::Sequence& attachments); + void InvalidateFramebuffer(GLenum target, const dom::Sequence& attachments, + ErrorResult& aRv); void InvalidateSubFramebuffer (GLenum target, const dom::Sequence& attachments, GLint x, GLint y, - GLsizei width, GLsizei height); + GLsizei width, GLsizei height, ErrorResult& aRv); void ReadBuffer(GLenum mode); void RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); diff --git a/dom/canvas/WebGL2ContextFramebuffers.cpp b/dom/canvas/WebGL2ContextFramebuffers.cpp index 8a10ead56d..66fca94612 100644 --- a/dom/canvas/WebGL2ContextFramebuffers.cpp +++ b/dom/canvas/WebGL2ContextFramebuffers.cpp @@ -343,26 +343,38 @@ WebGL2Context::GetInternalformatParameter(JSContext*, GLenum target, GLenum inte // Map attachments intended for the default buffer, to attachments for a non- // default buffer. -static void +static bool TranslateDefaultAttachments(const dom::Sequence& in, dom::Sequence* out) { for (size_t i = 0; i < in.Length(); i++) { switch (in[i]) { case LOCAL_GL_COLOR: - out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0); + if (!out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0)) { + return false; + } break; + case LOCAL_GL_DEPTH: - out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT); + if (!out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT)) { + return false; + } break; + case LOCAL_GL_STENCIL: - out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT); + if (!out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT)) { + return false; + } break; } } + + return true; } void -WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence& attachments) +WebGL2Context::InvalidateFramebuffer(GLenum target, + const dom::Sequence& attachments, + ErrorResult& aRv) { if (IsContextLost()) return; @@ -400,7 +412,11 @@ WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence& if (!fb && !isDefaultFB) { dom::Sequence tmpAttachments; - TranslateDefaultAttachments(attachments, &tmpAttachments); + if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + gl->fInvalidateFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements()); } else { gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements()); @@ -409,7 +425,8 @@ WebGL2Context::InvalidateFramebuffer(GLenum target, const dom::Sequence& void WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence& attachments, - GLint x, GLint y, GLsizei width, GLsizei height) + GLint x, GLint y, GLsizei width, GLsizei height, + ErrorResult& aRv) { if (IsContextLost()) return; @@ -447,7 +464,11 @@ WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence tmpAttachments; - TranslateDefaultAttachments(attachments, &tmpAttachments); + if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(), tmpAttachments.Elements(), x, y, width, height); } else { diff --git a/dom/canvas/WebGLElementArrayCache.cpp b/dom/canvas/WebGLElementArrayCache.cpp index 0c2502809a..7c4b3506c5 100644 --- a/dom/canvas/WebGLElementArrayCache.cpp +++ b/dom/canvas/WebGLElementArrayCache.cpp @@ -373,7 +373,7 @@ WebGLElementArrayCacheTree::Update(size_t firstByte, size_t lastByte) // Step #0: If needed, resize our tree data storage. if (requiredNumLeaves != NumLeaves()) { // See class comment for why we the tree storage size is 2 * numLeaves. - if (!mTreeData.SetLength(2 * requiredNumLeaves)) { + if (!mTreeData.SetLength(2 * requiredNumLeaves, fallible)) { mTreeData.SetLength(0); return false; } @@ -467,7 +467,7 @@ bool WebGLElementArrayCache::BufferData(const void* ptr, size_t byteLength) { if (mBytes.Length() != byteLength) { - if (!mBytes.SetLength(byteLength)) { + if (!mBytes.SetLength(byteLength, fallible)) { mBytes.SetLength(0); return false; } diff --git a/dom/crypto/CryptoKey.cpp b/dom/crypto/CryptoKey.cpp index ab5b5019cd..de9bd0de6b 100644 --- a/dom/crypto/CryptoKey.cpp +++ b/dom/crypto/CryptoKey.cpp @@ -63,6 +63,29 @@ StringToUsage(const nsString& aUsage, CryptoKey::KeyUsage& aUsageOut) return NS_OK; } +SECKEYPrivateKey* +PrivateKeyFromPrivateKeyTemplate(SECItem* aObjID, + CK_ATTRIBUTE* aTemplate, + CK_ULONG aTemplateSize) +{ + // Create a generic object with the contents of the key + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + return nullptr; + } + + ScopedPK11GenericObject obj(PK11_CreateGenericObject(slot, + aTemplate, + aTemplateSize, + PR_FALSE)); + if (!obj) { + return nullptr; + } + + // Have NSS translate the object to a private key. + return PK11_FindKeyByKeyID(slot, aObjID, nullptr); +} + CryptoKey::CryptoKey(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) , mAttributes(0) @@ -223,6 +246,73 @@ CryptoKey::SetExtractable(bool aExtractable) } } +// NSS exports private EC keys without the CKA_EC_POINT attribute, i.e. the +// public value. To properly export the private key to JWK or PKCS #8 we need +// the public key data though and so we use this method to augment a private +// key with data from the given public key. +nsresult +CryptoKey::AddPublicKeyData(SECKEYPublicKey* aPublicKey) +{ + // This should be a private key. + MOZ_ASSERT(GetKeyType() == PRIVATE); + // There should be a private NSS key with type 'EC'. + MOZ_ASSERT(mPrivateKey && mPrivateKey->keyType == ecKey); + // The given public key should have the same key type. + MOZ_ASSERT(aPublicKey->keyType == mPrivateKey->keyType); + + nsNSSShutDownPreventionLock locker; + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // Generate a random 160-bit object ID. + ScopedSECItem objID(::SECITEM_AllocItem(nullptr, nullptr, 20)); + SECStatus rv = PK11_GenerateRandomOnSlot(slot, objID->data, objID->len); + if (rv != SECSuccess) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // Read EC params. + ScopedSECItem params(::SECITEM_AllocItem(nullptr, nullptr, 0)); + rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey, CKA_EC_PARAMS, + params); + if (rv != SECSuccess) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + // Read private value. + ScopedSECItem value(::SECITEM_AllocItem(nullptr, nullptr, 0)); + rv = PK11_ReadRawAttribute(PK11_TypePrivKey, mPrivateKey, CKA_VALUE, value); + if (rv != SECSuccess) { + return NS_ERROR_DOM_OPERATION_ERR; + } + + SECItem* point = &aPublicKey->u.ec.publicValue; + CK_OBJECT_CLASS privateKeyValue = CKO_PRIVATE_KEY; + CK_BBOOL falseValue = CK_FALSE; + CK_KEY_TYPE ecValue = CKK_EC; + + CK_ATTRIBUTE keyTemplate[9] = { + { CKA_CLASS, &privateKeyValue, sizeof(privateKeyValue) }, + { CKA_KEY_TYPE, &ecValue, sizeof(ecValue) }, + { CKA_TOKEN, &falseValue, sizeof(falseValue) }, + { CKA_SENSITIVE, &falseValue, sizeof(falseValue) }, + { CKA_PRIVATE, &falseValue, sizeof(falseValue) }, + { CKA_ID, objID->data, objID->len }, + { CKA_EC_PARAMS, params->data, params->len }, + { CKA_EC_POINT, point->data, point->len }, + { CKA_VALUE, value->data, value->len }, + }; + + mPrivateKey = PrivateKeyFromPrivateKeyTemplate(objID, keyTemplate, + PR_ARRAY_SIZE(keyTemplate)); + NS_ENSURE_TRUE(mPrivateKey, NS_ERROR_DOM_OPERATION_ERR); + + return NS_OK; +} + void CryptoKey::ClearUsages() { @@ -367,6 +457,9 @@ CryptoKey::PrivateKeyFromPkcs8(CryptoBuffer& aKeyData, { SECKEYPrivateKey* privKey; ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + return nullptr; + } ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); if (!arena) { @@ -457,7 +550,9 @@ CryptoKey::PrivateKeyToPkcs8(SECKEYPrivateKey* aPrivKey, if (!pkcs8Item.get()) { return NS_ERROR_DOM_INVALID_ACCESS_ERR; } - aRetVal.Assign(pkcs8Item.get()); + if (!aRetVal.Assign(pkcs8Item.get())) { + return NS_ERROR_DOM_OPERATION_ERR; + } return NS_OK; } @@ -554,7 +649,9 @@ CryptoKey::PublicKeyToSpki(SECKEYPublicKey* aPubKey, const SEC_ASN1Template* tpl = SEC_ASN1_GET(CERT_SubjectPublicKeyInfoTemplate); ScopedSECItem spkiItem(SEC_ASN1EncodeItem(nullptr, nullptr, spki, tpl)); - aRetVal.Assign(spkiItem.get()); + if (!aRetVal.Assign(spkiItem.get())) { + return NS_ERROR_DOM_OPERATION_ERR; + } return NS_OK; } @@ -582,36 +679,6 @@ CreateECPointForCoordinates(const CryptoBuffer& aX, return point; } -SECKEYPrivateKey* -PrivateKeyFromPrivateKeyTemplate(SECItem* aObjID, - CK_ATTRIBUTE* aTemplate, - CK_ULONG aTemplateSize) -{ - // Create a generic object with the contents of the key - ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); - if (!slot.get()) { - return nullptr; - } - - ScopedPK11GenericObject obj(PK11_CreateGenericObject(slot.get(), - aTemplate, - aTemplateSize, - PR_FALSE)); - if (!obj.get()) { - return nullptr; - } - - // Have NSS translate the object to a private key by inspection - // and make a copy we can own - ScopedSECKEYPrivateKey privKey(PK11_FindKeyByKeyID(slot.get(), aObjID, - nullptr)); - if (!privKey.get()) { - return nullptr; - } - - return SECKEY_CopyPrivateKey(privKey.get()); -} - SECKEYPrivateKey* CryptoKey::PrivateKeyFromJwk(const JsonWebKey& aJwk, const nsNSSShutDownPreventionLock& /*proofOfLock*/) @@ -888,6 +955,41 @@ CryptoKey::PrivateKeyToJwk(SECKEYPrivateKey* aPrivKey, } } +SECKEYPublicKey* +CreateECPublicKey(const SECItem* aKeyData, const nsString& aNamedCurve) +{ + ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!arena) { + return nullptr; + } + + SECKEYPublicKey* key = PORT_ArenaZNew(arena, SECKEYPublicKey); + if (!key) { + return nullptr; + } + + key->keyType = ecKey; + key->pkcs11Slot = nullptr; + key->pkcs11ID = CK_INVALID_HANDLE; + + // Create curve parameters. + SECItem* params = CreateECParamsForCurve(aNamedCurve, arena); + if (!params) { + return nullptr; + } + key->u.ec.DEREncodedParams = *params; + + // Set public point. + key->u.ec.publicValue = *aKeyData; + + // Ensure the given point is on the curve. + if (!CryptoKey::PublicKeyValid(key)) { + return nullptr; + } + + return SECKEY_CopyPublicKey(key); +} + SECKEYPublicKey* CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk, const nsNSSShutDownPreventionLock& /*proofOfLock*/) @@ -939,39 +1041,18 @@ CryptoKey::PublicKeyFromJwk(const JsonWebKey& aJwk, return nullptr; } - SECKEYPublicKey* key = PORT_ArenaZNew(arena, SECKEYPublicKey); - if (!key) { + // Create point. + SECItem* point = CreateECPointForCoordinates(x, y, arena.get()); + if (!point) { return nullptr; } - key->keyType = ecKey; - key->pkcs11Slot = nullptr; - key->pkcs11ID = CK_INVALID_HANDLE; - nsString namedCurve; if (!NormalizeToken(aJwk.mCrv.Value(), namedCurve)) { return nullptr; } - // Create parameters. - SECItem* params = CreateECParamsForCurve(namedCurve, arena.get()); - if (!params) { - return nullptr; - } - key->u.ec.DEREncodedParams = *params; - - // Create point. - SECItem* point = CreateECPointForCoordinates(x, y, arena.get()); - if (!point) { - return nullptr; - } - key->u.ec.publicValue = *point; - - if (!PublicKeyValid(key)) { - return nullptr; - } - - return SECKEY_CopyPublicKey(key); + return CreateECPublicKey(point, namedCurve); } return nullptr; @@ -1048,7 +1129,60 @@ CryptoKey::PublicDhKeyToRaw(SECKEYPublicKey* aPubKey, CryptoBuffer& aRetVal, const nsNSSShutDownPreventionLock& /*proofOfLock*/) { - aRetVal.Assign(&aPubKey->u.dh.publicValue); + if (!aRetVal.Assign(&aPubKey->u.dh.publicValue)) { + return NS_ERROR_DOM_OPERATION_ERR; + } + return NS_OK; +} + +SECKEYPublicKey* +CryptoKey::PublicECKeyFromRaw(CryptoBuffer& aKeyData, + const nsString& aNamedCurve, + const nsNSSShutDownPreventionLock& /*proofOfLock*/) +{ + ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); + if (!arena) { + return nullptr; + } + + SECItem rawItem = { siBuffer, nullptr, 0 }; + if (!aKeyData.ToSECItem(arena, &rawItem)) { + return nullptr; + } + + uint32_t flen; + if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P256)) { + flen = 32; // bytes + } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P384)) { + flen = 48; // bytes + } else if (aNamedCurve.EqualsLiteral(WEBCRYPTO_NAMED_CURVE_P521)) { + flen = 66; // bytes + } else { + return nullptr; + } + + // Check length of uncompressed point coordinates. There are 2 field elements + // and a leading point form octet (which must EC_POINT_FORM_UNCOMPRESSED). + if (rawItem.len != (2 * flen + 1)) { + return nullptr; + } + + // No support for compressed points. + if (rawItem.data[0] != EC_POINT_FORM_UNCOMPRESSED) { + return nullptr; + } + + return CreateECPublicKey(&rawItem, aNamedCurve); +} + +nsresult +CryptoKey::PublicECKeyToRaw(SECKEYPublicKey* aPubKey, + CryptoBuffer& aRetVal, + const nsNSSShutDownPreventionLock& /*proofOfLock*/) +{ + if (!aRetVal.Assign(&aPubKey->u.ec.publicValue)) { + return NS_ERROR_DOM_OPERATION_ERR; + } return NS_OK; } @@ -1089,11 +1223,15 @@ CryptoKey::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const CryptoBuffer priv, pub; if (mPrivateKey) { - CryptoKey::PrivateKeyToPkcs8(mPrivateKey, priv, locker); + if (NS_FAILED(CryptoKey::PrivateKeyToPkcs8(mPrivateKey, priv, locker))) { + return false; + } } if (mPublicKey) { - CryptoKey::PublicKeyToSpki(mPublicKey, pub, locker); + if (NS_FAILED(CryptoKey::PublicKeyToSpki(mPublicKey, pub, locker))) { + return false; + } } return JS_WriteUint32Pair(aWriter, mAttributes, CRYPTOKEY_SC_VERSION) && diff --git a/dom/crypto/CryptoKey.h b/dom/crypto/CryptoKey.h index 59c59a0b8c..54f507a918 100644 --- a/dom/crypto/CryptoKey.h +++ b/dom/crypto/CryptoKey.h @@ -116,6 +116,7 @@ public: nsresult SetType(const nsString& aType); void SetType(KeyType aType); void SetExtractable(bool aExtractable); + nsresult AddPublicKeyData(SECKEYPublicKey* point); void ClearUsages(); nsresult AddUsage(const nsString& aUsage); nsresult AddUsageIntersecting(const nsString& aUsage, uint32_t aUsageMask); @@ -179,6 +180,13 @@ public: CryptoBuffer& aRetVal, const nsNSSShutDownPreventionLock& /*proofOfLock*/); + static SECKEYPublicKey* PublicECKeyFromRaw(CryptoBuffer& aKeyData, + const nsString& aNamedCurve, + const nsNSSShutDownPreventionLock& /*proofOfLock*/); + static nsresult PublicECKeyToRaw(SECKEYPublicKey* aPubKey, + CryptoBuffer& aRetVal, + const nsNSSShutDownPreventionLock& /*proofOfLock*/); + static bool PublicKeyValid(SECKEYPublicKey* aPubKey); // Structured clone methods use these to clone keys diff --git a/dom/crypto/KeyAlgorithmProxy.h b/dom/crypto/KeyAlgorithmProxy.h index 396edd893a..7f64dd3e64 100644 --- a/dom/crypto/KeyAlgorithmProxy.h +++ b/dom/crypto/KeyAlgorithmProxy.h @@ -109,7 +109,7 @@ struct KeyAlgorithmProxy mHmac.mHash.mName = aHashName; } - void + bool MakeRsa(const nsString& aName, uint32_t aModulusLength, const CryptoBuffer& aPublicExponent, const nsString& aHashName) { @@ -118,7 +118,10 @@ struct KeyAlgorithmProxy mRsa.mName = aName; mRsa.mModulusLength = aModulusLength; mRsa.mHash.mName = aHashName; - mRsa.mPublicExponent.Assign(aPublicExponent); + if (!mRsa.mPublicExponent.Assign(aPublicExponent)) { + return false; + } + return true; } void @@ -130,15 +133,20 @@ struct KeyAlgorithmProxy mEc.mNamedCurve = aNamedCurve; } - void + bool MakeDh(const nsString& aName, const CryptoBuffer& aPrime, const CryptoBuffer& aGenerator) { mType = DH; mName = aName; mDh.mName = aName; - mDh.mPrime.Assign(aPrime); - mDh.mGenerator.Assign(aGenerator); + if (!mDh.mPrime.Assign(aPrime)) { + return false; + } + if (!mDh.mGenerator.Assign(aGenerator)) { + return false; + } + return true; } }; diff --git a/dom/crypto/WebCryptoCommon.h b/dom/crypto/WebCryptoCommon.h index 0af6a3002e..681690b300 100644 --- a/dom/crypto/WebCryptoCommon.h +++ b/dom/crypto/WebCryptoCommon.h @@ -152,7 +152,7 @@ ReadBuffer(JSStructuredCloneReader* aReader, CryptoBuffer& aBuffer) } if (length > 0) { - if (!aBuffer.SetLength(length)) { + if (!aBuffer.SetLength(length, fallible)) { return false; } ret = JS_ReadBytes(aReader, aBuffer.Elements(), aBuffer.Length()); @@ -161,15 +161,21 @@ ReadBuffer(JSStructuredCloneReader* aReader, CryptoBuffer& aBuffer) } inline bool -WriteBuffer(JSStructuredCloneWriter* aWriter, const CryptoBuffer& aBuffer) +WriteBuffer(JSStructuredCloneWriter* aWriter, const uint8_t* aBuffer, size_t aLength) { - bool ret = JS_WriteUint32Pair(aWriter, aBuffer.Length(), 0); - if (ret && aBuffer.Length() > 0) { - ret = JS_WriteBytes(aWriter, aBuffer.Elements(), aBuffer.Length()); + bool ret = JS_WriteUint32Pair(aWriter, aLength, 0); + if (ret && aLength > 0) { + ret = JS_WriteBytes(aWriter, aBuffer, aLength); } return ret; } +inline bool +WriteBuffer(JSStructuredCloneWriter* aWriter, const CryptoBuffer& aBuffer) +{ + return WriteBuffer(aWriter, aBuffer.Elements(), aBuffer.Length()); +} + inline CK_MECHANISM_TYPE MapAlgorithmNameToMechanism(const nsString& aName) { diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp index 1dd0d6a301..1930f39dd3 100644 --- a/dom/crypto/WebCryptoTask.cpp +++ b/dom/crypto/WebCryptoTask.cpp @@ -304,10 +304,6 @@ WebCryptoTask::CalculateResult() { MOZ_ASSERT(!NS_IsMainThread()); - if (NS_FAILED(mEarlyRv)) { - return mEarlyRv; - } - if (isAlreadyShutDown()) { return NS_ERROR_DOM_UNKNOWN_ERR; } @@ -564,7 +560,7 @@ private: // Initialize the output buffer (enough space for padding / a full tag) uint32_t dataLen = mData.Length(); uint32_t maxLen = dataLen + 16; - if (!mResult.SetLength(maxLen)) { + if (!mResult.SetLength(maxLen, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR; } uint32_t outLen = 0; @@ -572,16 +568,18 @@ private: // Perform the encryption/decryption if (mEncrypt) { rv = MapSECStatus(PK11_Encrypt(symKey.get(), mMechanism, ¶m, - mResult.Elements(), &outLen, maxLen, - mData.Elements(), mData.Length())); + mResult.Elements(), &outLen, + mResult.Length(), mData.Elements(), + mData.Length())); } else { rv = MapSECStatus(PK11_Decrypt(symKey.get(), mMechanism, ¶m, - mResult.Elements(), &outLen, maxLen, - mData.Elements(), mData.Length())); + mResult.Elements(), &outLen, + mResult.Length(), mData.Elements(), + mData.Length())); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); - mResult.SetLength(outLen); + mResult.TruncateLength(outLen); return rv; } }; @@ -688,7 +686,7 @@ private: // Encrypt and return the wrapped key // AES-KW encryption results in a wrapped key 64 bits longer - if (!mResult.SetLength(mData.Length() + 8)) { + if (!mResult.SetLength(mData.Length() + 8, fallible)) { return NS_ERROR_DOM_OPERATION_ERR; } SECItem resultItem = {siBuffer, mResult.Elements(), @@ -820,7 +818,7 @@ private: // Ciphertext is an integer mod the modulus, so it will be // no longer than mStrength octets - if (!mResult.SetLength(mStrength)) { + if (!mResult.SetLength(mStrength, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR; } @@ -857,7 +855,7 @@ private: } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); - mResult.SetLength(outLen); + mResult.TruncateLength(outLen); return NS_OK; } }; @@ -910,7 +908,7 @@ private: virtual nsresult DoCrypto() override { // Initialize the output buffer - if (!mResult.SetLength(HASH_LENGTH_MAX)) { + if (!mResult.SetLength(HASH_LENGTH_MAX, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR; } @@ -943,10 +941,10 @@ private: rv = MapSECStatus(PK11_DigestOp(ctx.get(), mData.Elements(), mData.Length())); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); rv = MapSECStatus(PK11_DigestFinal(ctx.get(), mResult.Elements(), - &outLen, HASH_LENGTH_MAX)); + &outLen, mResult.Length())); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); - mResult.SetLength(outLen); + mResult.TruncateLength(outLen); return rv; } @@ -1192,7 +1190,7 @@ private: { // Resize the result buffer uint32_t hashLen = HASH_ResultLenByOidTag(mOidTag); - if (!mResult.SetLength(hashLen)) { + if (!mResult.SetLength(hashLen, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR; } @@ -1610,7 +1608,9 @@ private: // Extract relevant information from the public key mModulusLength = 8 * pubKey->u.rsa.modulus.len; - mPublicExponent.Assign(&pubKey->u.rsa.publicExponent); + if (!mPublicExponent.Assign(&pubKey->u.rsa.publicExponent)) { + return NS_ERROR_DOM_OPERATION_ERR; + } return NS_OK; } @@ -1635,8 +1635,10 @@ private: } // Set an appropriate KeyAlgorithm - mKey->Algorithm().MakeRsa(mAlgName, mModulusLength, - mPublicExponent, mHashName); + if (!mKey->Algorithm().MakeRsa(mAlgName, mModulusLength, + mPublicExponent, mHashName)) { + return NS_ERROR_DOM_OPERATION_ERR; + } if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) { return NS_ERROR_DOM_DATA_ERR; @@ -1653,7 +1655,7 @@ public: const ObjectOrString& aAlgorithm, bool aExtractable, const Sequence& aKeyUsages) { - ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages); + Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages); } ImportEcKeyTask(JSContext* aCx, const nsAString& aFormat, @@ -1661,7 +1663,7 @@ public: const ObjectOrString& aAlgorithm, bool aExtractable, const Sequence& aKeyUsages) { - ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages); + Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages); if (NS_FAILED(mEarlyRv)) { return; } @@ -1670,6 +1672,30 @@ public: NS_ENSURE_SUCCESS_VOID(mEarlyRv); } + void Init(JSContext* aCx, const nsAString& aFormat, + const ObjectOrString& aAlgorithm, bool aExtractable, + const Sequence& aKeyUsages) + { + ImportKeyTask::Init(aCx, aFormat, aAlgorithm, aExtractable, aKeyUsages); + if (NS_FAILED(mEarlyRv)) { + return; + } + + if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) { + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv) || !params.mNamedCurve.WasPassed()) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + + if (!NormalizeToken(params.mNamedCurve.Value(), mNamedCurve)) { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; + } + } + } + private: nsString mNamedCurve; @@ -1689,14 +1715,19 @@ private: mKey->SetPrivateKey(privKey.get()); mKey->SetType(CryptoKey::PRIVATE); - } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) || + } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW) || + mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI) || (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) && !mJwk.mD.WasPassed())) { // Public key import - if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) { + if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) { + pubKey = CryptoKey::PublicECKeyFromRaw(mKeyData, mNamedCurve, locker); + } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_SPKI)) { pubKey = CryptoKey::PublicKeyFromSpki(mKeyData, locker); - } else { + } else if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) { pubKey = CryptoKey::PublicKeyFromJwk(mJwk, locker); + } else { + MOZ_ASSERT(false); } if (!pubKey) { @@ -1857,7 +1888,9 @@ private: return NS_ERROR_DOM_DATA_ERR; } - mKey->Algorithm().MakeDh(mAlgName, mPrime, mGenerator); + if (!mKey->Algorithm().MakeDh(mAlgName, mPrime, mGenerator)) { + return NS_ERROR_DOM_OPERATION_ERR; + } return NS_OK; } }; @@ -1910,6 +1943,14 @@ private: return NS_OK; } + if (mPublicKey && mPublicKey->keyType == ecKey) { + nsresult rv = CryptoKey::PublicECKeyToRaw(mPublicKey, mResult, locker); + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_OPERATION_ERR; + } + return NS_OK; + } + mResult = mSymKey; if (mResult.Length() == 0) { return NS_ERROR_DOM_NOT_SUPPORTED_ERR; @@ -1922,9 +1963,13 @@ private: } switch (mPrivateKey->keyType) { - case rsaKey: - CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), mResult, locker); + case rsaKey: { + nsresult rv = CryptoKey::PrivateKeyToPkcs8(mPrivateKey.get(), mResult, locker); + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_OPERATION_ERR; + } return NS_OK; + } default: return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } @@ -1971,7 +2016,9 @@ private: if (!mKeyUsages.IsEmpty()) { mJwk.mKey_ops.Construct(); - mJwk.mKey_ops.Value().AppendElements(mKeyUsages); + if (!mJwk.mKey_ops.Value().AppendElements(mKeyUsages, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } } return NS_OK; @@ -2121,227 +2168,239 @@ private: } }; -class GenerateAsymmetricKeyTask : public WebCryptoTask +GenerateAsymmetricKeyTask::GenerateAsymmetricKeyTask( + JSContext* aCx, const ObjectOrString& aAlgorithm, bool aExtractable, + const Sequence& aKeyUsages) { -public: - GenerateAsymmetricKeyTask(JSContext* aCx, - const ObjectOrString& aAlgorithm, bool aExtractable, - const Sequence& aKeyUsages) - { - nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); - if (!global) { - mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; - return; - } + nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); + if (!global) { + mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; + return; + } - mArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - if (!mArena) { - mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; - return; - } + mArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!mArena) { + mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; + return; + } - // Create an empty key and set easy attributes - mKeyPair.mPrivateKey = new CryptoKey(global); - mKeyPair.mPublicKey = new CryptoKey(global); + // Create an empty key and set easy attributes + mKeyPair.mPrivateKey = new CryptoKey(global); + mKeyPair.mPublicKey = new CryptoKey(global); - // Extract algorithm name - nsString algName; - mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, algName); + // Extract algorithm name + mEarlyRv = GetAlgorithmName(aCx, aAlgorithm, mAlgName); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } + + // Construct an appropriate KeyAlorithm + uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0; + if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); if (NS_FAILED(mEarlyRv)) { mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return; } - // Construct an appropriate KeyAlorithm - uint32_t privateAllowedUsages = 0, publicAllowedUsages = 0; - if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || - algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { - RootedDictionary params(aCx); - mEarlyRv = Coerce(aCx, params, aAlgorithm); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } + // Pull relevant info + uint32_t modulusLength = params.mModulusLength; + CryptoBuffer publicExponent; + ATTEMPT_BUFFER_INIT(publicExponent, params.mPublicExponent); + nsString hashName; + mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } - // Pull relevant info - uint32_t modulusLength = params.mModulusLength; - CryptoBuffer publicExponent; - ATTEMPT_BUFFER_INIT(publicExponent, params.mPublicExponent); - nsString hashName; - mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashName); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } + // Create algorithm + if (!mKeyPair.mPublicKey.get()->Algorithm().MakeRsa(mAlgName, + modulusLength, + publicExponent, + hashName)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + if (!mKeyPair.mPrivateKey.get()->Algorithm().MakeRsa(mAlgName, + modulusLength, + publicExponent, + hashName)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + mMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; - // Create algorithm - mKeyPair.mPublicKey.get()->Algorithm().MakeRsa(algName, - modulusLength, - publicExponent, - hashName); - mKeyPair.mPrivateKey.get()->Algorithm().MakeRsa(algName, - modulusLength, - publicExponent, - hashName); - mMechanism = CKM_RSA_PKCS_KEY_PAIR_GEN; + // Set up params struct + mRsaParams.keySizeInBits = modulusLength; + bool converted = publicExponent.GetBigIntValue(mRsaParams.pe); + if (!converted) { + mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; + return; + } + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; + } - // Set up params struct - mRsaParams.keySizeInBits = modulusLength; - bool converted = publicExponent.GetBigIntValue(mRsaParams.pe); - if (!converted) { - mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; - return; - } - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || - algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { - RootedDictionary params(aCx); - mEarlyRv = Coerce(aCx, params, aAlgorithm); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } - - if (!NormalizeToken(params.mNamedCurve, mNamedCurve)) { - mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; - return; - } - - // Create algorithm. - mKeyPair.mPublicKey.get()->Algorithm().MakeEc(algName, mNamedCurve); - mKeyPair.mPrivateKey.get()->Algorithm().MakeEc(algName, mNamedCurve); - mMechanism = CKM_EC_KEY_PAIR_GEN; - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { - RootedDictionary params(aCx); - mEarlyRv = Coerce(aCx, params, aAlgorithm); - if (NS_FAILED(mEarlyRv)) { - mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; - return; - } - - CryptoBuffer prime; - ATTEMPT_BUFFER_INIT(prime, params.mPrime); - - CryptoBuffer generator; - ATTEMPT_BUFFER_INIT(generator, params.mGenerator); - - // Set up params. - if (!prime.ToSECItem(mArena, &mDhParams.prime) || - !generator.ToSECItem(mArena, &mDhParams.base)) { - mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; - return; - } - - // Create algorithm. - mKeyPair.mPublicKey.get()->Algorithm().MakeDh(algName, prime, generator); - mKeyPair.mPrivateKey.get()->Algorithm().MakeDh(algName, prime, generator); - mMechanism = CKM_DH_PKCS_KEY_PAIR_GEN; - } else { + if (!NormalizeToken(params.mNamedCurve, mNamedCurve)) { mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; return; } - // Set key usages. - if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || - algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { - privateAllowedUsages = CryptoKey::SIGN; - publicAllowedUsages = CryptoKey::VERIFY; - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { - privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY; - publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY; - } else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || - algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { - privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS; - publicAllowedUsages = 0; + // Create algorithm. + mKeyPair.mPublicKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve); + mKeyPair.mPrivateKey.get()->Algorithm().MakeEc(mAlgName, mNamedCurve); + mMechanism = CKM_EC_KEY_PAIR_GEN; + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { + RootedDictionary params(aCx); + mEarlyRv = Coerce(aCx, params, aAlgorithm); + if (NS_FAILED(mEarlyRv)) { + mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; + return; } - mKeyPair.mPrivateKey.get()->SetExtractable(aExtractable); - mKeyPair.mPrivateKey.get()->SetType(CryptoKey::PRIVATE); + CryptoBuffer prime; + ATTEMPT_BUFFER_INIT(prime, params.mPrime); - mKeyPair.mPublicKey.get()->SetExtractable(true); - mKeyPair.mPublicKey.get()->SetType(CryptoKey::PUBLIC); + CryptoBuffer generator; + ATTEMPT_BUFFER_INIT(generator, params.mGenerator); - mKeyPair.mPrivateKey.get()->ClearUsages(); - mKeyPair.mPublicKey.get()->ClearUsages(); - for (uint32_t i=0; i < aKeyUsages.Length(); ++i) { - mEarlyRv = mKeyPair.mPrivateKey.get()->AddUsageIntersecting(aKeyUsages[i], - privateAllowedUsages); - if (NS_FAILED(mEarlyRv)) { - return; - } - - mEarlyRv = mKeyPair.mPublicKey.get()->AddUsageIntersecting(aKeyUsages[i], - publicAllowedUsages); - if (NS_FAILED(mEarlyRv)) { - return; - } + // Set up params. + if (!prime.ToSECItem(mArena, &mDhParams.prime) || + !generator.ToSECItem(mArena, &mDhParams.base)) { + mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; + return; } - // If no usages ended up being allowed, DataError - if (!mKeyPair.mPublicKey.get()->HasAnyUsage() && - !mKeyPair.mPrivateKey.get()->HasAnyUsage()) { - mEarlyRv = NS_ERROR_DOM_DATA_ERR; + // Create algorithm. + if (!mKeyPair.mPublicKey.get()->Algorithm().MakeDh(mAlgName, + prime, + generator)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + if (!mKeyPair.mPrivateKey.get()->Algorithm().MakeDh(mAlgName, + prime, + generator)) { + mEarlyRv = NS_ERROR_DOM_OPERATION_ERR; + return; + } + mMechanism = CKM_DH_PKCS_KEY_PAIR_GEN; + } else { + mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; + return; + } + + // Set key usages. + if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) { + privateAllowedUsages = CryptoKey::SIGN; + publicAllowedUsages = CryptoKey::VERIFY; + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) { + privateAllowedUsages = CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY; + publicAllowedUsages = CryptoKey::ENCRYPT | CryptoKey::WRAPKEY; + } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) || + mAlgName.EqualsLiteral(WEBCRYPTO_ALG_DH)) { + privateAllowedUsages = CryptoKey::DERIVEKEY | CryptoKey::DERIVEBITS; + publicAllowedUsages = 0; + } + + mKeyPair.mPrivateKey.get()->SetExtractable(aExtractable); + mKeyPair.mPrivateKey.get()->SetType(CryptoKey::PRIVATE); + + mKeyPair.mPublicKey.get()->SetExtractable(true); + mKeyPair.mPublicKey.get()->SetType(CryptoKey::PUBLIC); + + mKeyPair.mPrivateKey.get()->ClearUsages(); + mKeyPair.mPublicKey.get()->ClearUsages(); + for (uint32_t i=0; i < aKeyUsages.Length(); ++i) { + mEarlyRv = mKeyPair.mPrivateKey.get()->AddUsageIntersecting(aKeyUsages[i], + privateAllowedUsages); + if (NS_FAILED(mEarlyRv)) { + return; + } + + mEarlyRv = mKeyPair.mPublicKey.get()->AddUsageIntersecting(aKeyUsages[i], + publicAllowedUsages); + if (NS_FAILED(mEarlyRv)) { return; } } -private: - ScopedPLArenaPool mArena; - CryptoKeyPair mKeyPair; - CK_MECHANISM_TYPE mMechanism; - PK11RSAGenParams mRsaParams; - SECKEYDHParams mDhParams; - ScopedSECKEYPublicKey mPublicKey; - ScopedSECKEYPrivateKey mPrivateKey; - nsString mNamedCurve; - - virtual void ReleaseNSSResources() override - { - mPublicKey.dispose(); - mPrivateKey.dispose(); + // If no usages ended up being allowed, DataError + if (!mKeyPair.mPublicKey.get()->HasAnyUsage() && + !mKeyPair.mPrivateKey.get()->HasAnyUsage()) { + mEarlyRv = NS_ERROR_DOM_DATA_ERR; + return; } +} - virtual nsresult DoCrypto() override - { - ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); - MOZ_ASSERT(slot.get()); +void +GenerateAsymmetricKeyTask::ReleaseNSSResources() +{ + mPublicKey.dispose(); + mPrivateKey.dispose(); +} - void* param; - switch (mMechanism) { - case CKM_RSA_PKCS_KEY_PAIR_GEN: - param = &mRsaParams; - break; - case CKM_DH_PKCS_KEY_PAIR_GEN: - param = &mDhParams; - break; - case CKM_EC_KEY_PAIR_GEN: { - param = CreateECParamsForCurve(mNamedCurve, mArena); - if (!param) { - return NS_ERROR_DOM_UNKNOWN_ERR; - } - break; +nsresult +GenerateAsymmetricKeyTask::DoCrypto() +{ + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + MOZ_ASSERT(slot.get()); + + void* param; + switch (mMechanism) { + case CKM_RSA_PKCS_KEY_PAIR_GEN: + param = &mRsaParams; + break; + case CKM_DH_PKCS_KEY_PAIR_GEN: + param = &mDhParams; + break; + case CKM_EC_KEY_PAIR_GEN: { + param = CreateECParamsForCurve(mNamedCurve, mArena); + if (!param) { + return NS_ERROR_DOM_UNKNOWN_ERR; } - default: - return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + break; } - - SECKEYPublicKey* pubKey = nullptr; - mPrivateKey = PK11_GenerateKeyPair(slot.get(), mMechanism, param, &pubKey, - PR_FALSE, PR_FALSE, nullptr); - mPublicKey = pubKey; - if (!mPrivateKey.get() || !mPublicKey.get()) { - return NS_ERROR_DOM_UNKNOWN_ERR; - } - - mKeyPair.mPrivateKey.get()->SetPrivateKey(mPrivateKey); - mKeyPair.mPublicKey.get()->SetPublicKey(mPublicKey); - return NS_OK; + default: + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; } - virtual void Resolve() override - { - mResultPromise->MaybeResolve(mKeyPair); + SECKEYPublicKey* pubKey = nullptr; + mPrivateKey = PK11_GenerateKeyPair(slot.get(), mMechanism, param, &pubKey, + PR_FALSE, PR_FALSE, nullptr); + mPublicKey = pubKey; + if (!mPrivateKey.get() || !mPublicKey.get()) { + return NS_ERROR_DOM_UNKNOWN_ERR; } -}; + + mKeyPair.mPrivateKey.get()->SetPrivateKey(mPrivateKey); + mKeyPair.mPublicKey.get()->SetPublicKey(mPublicKey); + + // PK11_GenerateKeyPair() does not set a CKA_EC_POINT attribute on the + // private key, we need this later when exporting to PKCS8 and JWK though. + if (mMechanism == CKM_EC_KEY_PAIR_GEN) { + nsresult rv = mKeyPair.mPrivateKey->AddPublicKeyData(mPublicKey); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR); + } + + return NS_OK; +} + +void +GenerateAsymmetricKeyTask::Resolve() +{ + mResultPromise->MaybeResolve(mKeyPair); +} class DerivePbkdfBitsTask : public ReturnArrayBufferViewTask { @@ -2606,7 +2665,7 @@ private: return NS_ERROR_DOM_DATA_ERR; } - if (!mResult.SetLength(mLength)) { + if (!mResult.SetLength(mLength, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR; } @@ -2705,7 +2764,7 @@ private: return NS_ERROR_DOM_DATA_ERR; } - if (!mResult.SetLength(mLength)) { + if (!mResult.SetLength(mLength, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR; } @@ -2745,7 +2804,9 @@ private: } NS_ConvertUTF16toUTF8 utf8(json); - mResult.Assign((const uint8_t*) utf8.BeginReading(), utf8.Length()); + if (!mResult.Assign((const uint8_t*) utf8.BeginReading(), utf8.Length())) { + return NS_ERROR_DOM_OPERATION_ERR; + } } return NS_OK; diff --git a/dom/crypto/WebCryptoTask.h b/dom/crypto/WebCryptoTask.h index 6b8d47dbb5..67521ee8d1 100644 --- a/dom/crypto/WebCryptoTask.h +++ b/dom/crypto/WebCryptoTask.h @@ -205,6 +205,31 @@ protected: virtual void CallCallback(nsresult rv) override final; }; +// XXX This class is declared here (unlike others) to enable reuse by WebRTC. +class GenerateAsymmetricKeyTask : public WebCryptoTask +{ +public: + GenerateAsymmetricKeyTask(JSContext* aCx, + const ObjectOrString& aAlgorithm, bool aExtractable, + const Sequence& aKeyUsages); +protected: + ScopedPLArenaPool mArena; + CryptoKeyPair mKeyPair; + nsString mAlgName; + CK_MECHANISM_TYPE mMechanism; + PK11RSAGenParams mRsaParams; + SECKEYDHParams mDhParams; + nsString mNamedCurve; + + virtual void ReleaseNSSResources() override; + virtual nsresult DoCrypto() override; + virtual void Resolve() override; + +private: + ScopedSECKEYPublicKey mPublicKey; + ScopedSECKEYPrivateKey mPrivateKey; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/crypto/test/test-vectors.js b/dom/crypto/test/test-vectors.js index ef192c946c..d6f822b737 100644 --- a/dom/crypto/test/test-vectors.js +++ b/dom/crypto/test/test-vectors.js @@ -518,6 +518,11 @@ tv = { "405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf" ), + raw: util.hex2abv( + "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" + + "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf" + ), + secret: util.hex2abv( "35669cd5c244ba6c1ea89b8802c3d1db815cd769979072e6556eb98548c65f7d" ) @@ -606,7 +611,31 @@ tv = { kty: "EC", crv: "P-256", x: "XOe4bjsyZgQD5jcS7wmY3q4QJ_rsPBvp92-TTf61jpg", - } + }, + + // Public point with Y not on the curve. + raw_bad: util.hex2abv( + "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" + + "98f4cf075b39405dd1f1adeb090106edcfb2b4963739d87679e3056cb0557d0adf" + ), + + // Public point with Y a little too short. + raw_short: util.hex2abv( + "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" + + "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0a" + ), + + // Public point with Y a little too long. + raw_long: util.hex2abv( + "045ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" + + "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adfff" + ), + + // Public point with EC_POINT_FORM_COMPRESSED_Y0. + raw_compressed: util.hex2abv( + "025ce7b86e3b32660403e63712ef0998deae1027faec3c1be9f76f934dfeb58e" + + "98f4cf075b39405dd1f1adeb090107edcfb2b4963739d87679e3056cb0557d0adf" + ) }, // NIST ECDSA test vectors @@ -626,6 +655,13 @@ tv = { "9Y33NGQ1_wQ0GZWDyXxmWpfxL3BvI1faS0Aoje-Ijlnm", }, + raw: util.hex2abv( + "040061387fd6b95914e885f912edfbb5fb274655027f216c4091ca83e19336740fd" + + "81aedfe047f51b42bdf68161121013e0d55b117a14e4303f926c8debb77a7fdaad1" + + "00e7d0c75c38626e895ca21526b9f9fdf84dcecb93f2b233390550d2b1463b7ee3f" + + "58df7346435ff0434199583c97c665a97f12f706f2357da4b40288def888e59e6" + ), + "data": util.hex2abv( "9ecd500c60e701404922e58ab20cc002651fdee7cbc9336adda33e4c1088fab1" + "964ecb7904dc6856865d6c8e15041ccf2d5ac302e99d346ff2f686531d255216" + diff --git a/dom/crypto/test/test_WebCrypto_ECDH.html b/dom/crypto/test/test_WebCrypto_ECDH.html index 04dc0c00e1..04a5a19e12 100644 --- a/dom/crypto/test/test_WebCrypto_ECDH.html +++ b/dom/crypto/test/test_WebCrypto_ECDH.html @@ -407,6 +407,114 @@ TestArray.addTest( .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that)); } ); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Raw import/export of a public ECDH key (P-256)", + function () { + var that = this; + var alg = { name: "ECDH", namedCurve: "P-256" }; + + function doExport(x) { + return crypto.subtle.exportKey("raw", x); + } + + crypto.subtle.importKey("raw", tv.ecdh_p256.raw, alg, true, ["deriveBits"]) + .then(doExport) + .then(memcmp_complete(that, tv.ecdh_p256.raw), error(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Test that importing bad raw ECDH keys fails", + function () { + var that = this; + var alg = { name: "ECDH", namedCurve: "P-256" }; + var tvs = tv.ecdh_p256_negative.raw_bad; + + crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"]) + .then(error(that), complete(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Test that importing ECDH keys with an unknown format fails", + function () { + var that = this; + var alg = { name: "ECDH", namedCurve: "P-256" }; + var tvs = tv.ecdh_p256.raw; + + crypto.subtle.importKey("unknown", tv, alg, false, ["deriveBits"]) + .then(error(that), complete(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Test that importing too short raw ECDH keys fails", + function () { + var that = this; + var alg = { name: "ECDH", namedCurve: "P-256" }; + var tvs = tv.ecdh_p256_negative.raw_short; + + crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"]) + .then(error(that), complete(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Test that importing too long raw ECDH keys fails", + function () { + var that = this; + var alg = { name: "ECDH", namedCurve: "P-256" }; + var tvs = tv.ecdh_p256_negative.raw_long; + + crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"]) + .then(error(that), complete(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Test that importing compressed raw ECDH keys fails", + function () { + var that = this; + var alg = { name: "ECDH", namedCurve: "P-256" }; + var tvs = tv.ecdh_p256_negative.raw_compressed; + + crypto.subtle.importKey("raw", tv, alg, false, ["deriveBits"]) + .then(error(that), complete(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "RAW/JWK import ECDH keys (P-256) and derive a known secret", + function () { + var that = this; + var alg = { name: "ECDH", namedCurve: "P-256" }; + + var pubKey, privKey; + function setPub(x) { pubKey = x; } + function setPriv(x) { privKey = x; } + + function doDerive() { + var alg = { name: "ECDH", public: pubKey }; + return crypto.subtle.deriveBits(alg, privKey, tv.ecdh_p256.secret.byteLength * 8); + } + + Promise.all([ + crypto.subtle.importKey("raw", tv.ecdh_p256.raw, alg, false, ["deriveBits"]) + .then(setPub), + crypto.subtle.importKey("jwk", tv.ecdh_p256.jwk_priv, alg, false, ["deriveBits"]) + .then(setPriv) + ]).then(doDerive) + .then(memcmp_complete(that, tv.ecdh_p256.secret), error(that)); + } +); /*]]>*/ diff --git a/dom/crypto/test/test_WebCrypto_ECDSA.html b/dom/crypto/test/test_WebCrypto_ECDSA.html index 409928fcc7..3ada61b2d5 100644 --- a/dom/crypto/test/test_WebCrypto_ECDSA.html +++ b/dom/crypto/test/test_WebCrypto_ECDSA.html @@ -144,6 +144,40 @@ TestArray.addTest( } ); +// ----------------------------------------------------------------------------- +TestArray.addTest( + "Raw import/export of a public ECDSA key (P-521)", + function () { + var that = this; + var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" }; + + function doExport(x) { + return crypto.subtle.exportKey("raw", x); + } + + crypto.subtle.importKey("raw", tv.ecdsa_verify.raw, alg, true, ["verify"]) + .then(doExport) + .then(memcmp_complete(that, tv.ecdsa_verify.raw), error(that)); + } +); + +// ----------------------------------------------------------------------------- +TestArray.addTest( + "ECDSA raw import and verify a known-good signature", + function() { + var that = this; + var alg = { name: "ECDSA", namedCurve: "P-521", hash: "SHA-512" }; + + function doVerify(x) { + return crypto.subtle.verify(alg, x, tv.ecdsa_verify.sig, tv.ecdsa_verify.data); + } + + crypto.subtle.importKey("raw", tv.ecdsa_verify.raw, alg, true, ["verify"]) + .then(doVerify) + .then(complete(that), error(that)) + } +); + /*]]>*/ diff --git a/dom/datastore/DataStoreDB.cpp b/dom/datastore/DataStoreDB.cpp index fff2e177b3..19d34f37ec 100644 --- a/dom/datastore/DataStoreDB.cpp +++ b/dom/datastore/DataStoreDB.cpp @@ -316,7 +316,10 @@ DataStoreDB::DatabaseOpened() } StringOrStringSequence objectStores; - objectStores.RawSetAsStringSequence().AppendElements(mObjectStores); + if (!objectStores.RawSetAsStringSequence().AppendElements(mObjectStores, + fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } nsRefPtr txn; error = mDatabase->Transaction(objectStores, diff --git a/dom/datastore/DataStoreService.cpp b/dom/datastore/DataStoreService.cpp index 716e820003..b7a4344d71 100644 --- a/dom/datastore/DataStoreService.cpp +++ b/dom/datastore/DataStoreService.cpp @@ -1358,7 +1358,9 @@ DataStoreService::CreateFirstRevisionId(uint32_t aAppId, new FirstRevisionIdCallback(aAppId, aName, aManifestURL); Sequence dbs; - dbs.AppendElement(NS_LITERAL_STRING(DATASTOREDB_REVISION)); + if (!dbs.AppendElement(NS_LITERAL_STRING(DATASTOREDB_REVISION))) { + return NS_ERROR_OUT_OF_MEMORY; + } return db->Open(IDBTransactionMode::Readwrite, dbs, callback); } diff --git a/dom/devicestorage/nsDeviceStorage.cpp b/dom/devicestorage/nsDeviceStorage.cpp index 4905c49d84..0417d02da2 100644 --- a/dom/devicestorage/nsDeviceStorage.cpp +++ b/dom/devicestorage/nsDeviceStorage.cpp @@ -4374,7 +4374,7 @@ nsDOMDeviceStorage::EnumerateInternal(const nsAString& aPath, PRTime since = 0; if (aOptions.mSince.WasPassed() && !aOptions.mSince.Value().IsUndefined()) { - since = PRTime(aOptions.mSince.Value().TimeStamp()); + since = PRTime(aOptions.mSince.Value().TimeStamp().toDouble()); } nsRefPtr dsf = new DeviceStorageFile(mStorageType, diff --git a/dom/filehandle/MetadataHelper.cpp b/dom/filehandle/MetadataHelper.cpp index 6ce41f3d5e..9518581c47 100644 --- a/dom/filehandle/MetadataHelper.cpp +++ b/dom/filehandle/MetadataHelper.cpp @@ -10,6 +10,7 @@ #include "js/Value.h" #include "js/RootingAPI.h" #include "jsapi.h" +#include "js/Date.h" #include "mozilla/dom/FileModeBinding.h" #include "nsDebug.h" #include "nsIFileStreams.h" @@ -49,7 +50,7 @@ MetadataHelper::GetSuccessResult(JSContext* aCx, if (mParams->LastModifiedRequested()) { double msec = mParams->LastModified(); - JSObject *date = JS_NewDateObjectMsec(aCx, msec); + JSObject *date = JS::NewDateObject(aCx, JS::TimeClip(msec)); NS_ENSURE_TRUE(date, NS_ERROR_OUT_OF_MEMORY); JS::Rooted dateRoot(aCx, JS::ObjectValue(*date)); diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 1f0495fb51..a3470a163e 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -1824,12 +1824,12 @@ HTMLInputElement::ConvertStringToNumber(nsAString& aValue, return false; } - double date = JS::MakeDate(year, month - 1, day); - if (IsNaN(date)) { + JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day)); + if (!time.isValid()) { return false; } - aResultValue = Decimal::fromDouble(date); + aResultValue = Decimal::fromDouble(time.toDouble()); return true; } case NS_FORM_INPUT_TIME: @@ -1872,7 +1872,11 @@ HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv) return; } Sequence list; - list.AppendElement(aValue); + if (!list.AppendElement(aValue)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + MozSetFileNameArray(list, aRv); return; } @@ -2064,7 +2068,8 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv) return Nullable(); } - return Nullable(Date(JS::MakeDate(year, month - 1, day))); + JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day)); + return Nullable(Date(time)); } case NS_FORM_INPUT_TIME: { @@ -2075,7 +2080,11 @@ HTMLInputElement::GetValueAsDate(ErrorResult& aRv) return Nullable(); } - return Nullable(Date(millisecond)); + JS::ClippedTime time = JS::TimeClip(millisecond); + MOZ_ASSERT(time.toDouble() == millisecond, + "HTML times are restricted to the day after the epoch and " + "never clip"); + return Nullable(Date(time)); } } @@ -2097,7 +2106,7 @@ HTMLInputElement::SetValueAsDate(Nullable aDate, ErrorResult& aRv) return; } - SetValue(Decimal::fromDouble(aDate.Value().TimeStamp())); + SetValue(Decimal::fromDouble(aDate.Value().TimeStamp().toDouble())); } NS_IMETHODIMP @@ -2453,7 +2462,9 @@ HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, uint32_t aLen Sequence list; for (uint32_t i = 0; i < aLength; ++i) { - list.AppendElement(nsDependentString(aFileNames[i])); + if (!list.AppendElement(nsDependentString(aFileNames[i]))) { + return NS_ERROR_OUT_OF_MEMORY; + } } ErrorResult rv; @@ -2504,7 +2515,10 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) if (mType == NS_FORM_INPUT_FILE) { Sequence list; - list.AppendElement(aValue); + if (!list.AppendElement(aValue)) { + return NS_ERROR_OUT_OF_MEMORY; + } + ErrorResult rv; MozSetFileNameArray(list, rv); return rv.StealNSResult(); diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index 7a0f0a162c..f0aa1e3033 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -15440,7 +15440,7 @@ DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob( } AutoFallibleTArray uncompressed; - if (NS_WARN_IF(!uncompressed.SetLength(uncompressedLength))) { + if (NS_WARN_IF(!uncompressed.SetLength(uncompressedLength, fallible))) { return NS_ERROR_OUT_OF_MEMORY; } @@ -18666,7 +18666,8 @@ VersionChangeOp::RunOnOwningThread() MOZ_ASSERT(!info->mLiveDatabases.IsEmpty()); FallibleTArray liveDatabases; - if (NS_WARN_IF(!liveDatabases.AppendElements(info->mLiveDatabases))) { + if (NS_WARN_IF(!liveDatabases.AppendElements(info->mLiveDatabases, + fallible))) { deleteOp->SetFailureCode(NS_ERROR_OUT_OF_MEMORY); } else { #ifdef DEBUG @@ -21411,7 +21412,8 @@ ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse) if (!mResponse.IsEmpty()) { FallibleTArray fallibleCloneInfos; - if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length()))) { + if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length(), + fallible))) { aResponse = NS_ERROR_OUT_OF_MEMORY; return; } @@ -21959,7 +21961,8 @@ IndexGetRequestOp::GetResponse(RequestResponse& aResponse) if (!mResponse.IsEmpty()) { FallibleTArray fallibleCloneInfos; - if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length()))) { + if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length(), + fallible))) { aResponse = NS_ERROR_OUT_OF_MEMORY; return; } diff --git a/dom/indexedDB/IDBFactory.cpp b/dom/indexedDB/IDBFactory.cpp index 9cea6b3da5..a22d933116 100644 --- a/dom/indexedDB/IDBFactory.cpp +++ b/dom/indexedDB/IDBFactory.cpp @@ -182,18 +182,26 @@ IDBFactory::CreateForWindow(nsPIDOMWindow* aWindow, // static nsresult -IDBFactory::CreateForChromeJS(JSContext* aCx, - JS::Handle aOwningObject, - IDBFactory** aFactory) +IDBFactory::CreateForMainThreadJS(JSContext* aCx, + JS::Handle aOwningObject, + IDBFactory** aFactory) { MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(nsContentUtils::IsCallerChrome()); - nsAutoPtr principalInfo( - new PrincipalInfo(SystemPrincipalInfo())); + nsAutoPtr principalInfo(new PrincipalInfo()); + nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aOwningObject); + MOZ_ASSERT(principal); + bool isSystem; + if (!AllowedForPrincipal(principal, &isSystem)) { + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } - nsresult rv = - CreateForMainThreadJSInternal(aCx, aOwningObject, principalInfo, aFactory); + nsresult rv = PrincipalToPrincipalInfo(principal, principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = CreateForMainThreadJSInternal(aCx, aOwningObject, principalInfo, aFactory); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -368,15 +376,14 @@ IDBFactory::AllowedForWindowInternal(nsPIDOMWindow* aWindow, return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - if (nsContentUtils::IsSystemPrincipal(principal)) { - principal.forget(aPrincipal); - return NS_OK; + bool isSystemPrincipal; + if (!AllowedForPrincipal(principal, &isSystemPrincipal)) { + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - bool isNullPrincipal; - if (NS_WARN_IF(NS_FAILED(principal->GetIsNullPrincipal(&isNullPrincipal))) || - isNullPrincipal) { - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + if (isSystemPrincipal) { + principal.forget(aPrincipal); + return NS_OK; } // Whitelist about:home, since it doesn't have a base domain it would not @@ -425,6 +432,36 @@ IDBFactory::AllowedForWindowInternal(nsPIDOMWindow* aWindow, return NS_OK; } +// static +bool +IDBFactory::AllowedForPrincipal(nsIPrincipal* aPrincipal, + bool* aIsSystemPrincipal) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPrincipal); + + if (NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate())) { + return false; + } + + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { + if (aIsSystemPrincipal) { + *aIsSystemPrincipal = true; + } + return true; + } else if (aIsSystemPrincipal) { + *aIsSystemPrincipal = false; + } + + bool isNullPrincipal; + if (NS_WARN_IF(NS_FAILED(aPrincipal->GetIsNullPrincipal(&isNullPrincipal))) || + isNullPrincipal) { + return false; + } + + return true; +} + #ifdef DEBUG void diff --git a/dom/indexedDB/IDBFactory.h b/dom/indexedDB/IDBFactory.h index deab1a976f..821c720b11 100644 --- a/dom/indexedDB/IDBFactory.h +++ b/dom/indexedDB/IDBFactory.h @@ -86,9 +86,9 @@ public: IDBFactory** aFactory); static nsresult - CreateForChromeJS(JSContext* aCx, - JS::Handle aOwningObject, - IDBFactory** aFactory); + CreateForMainThreadJS(JSContext* aCx, + JS::Handle aOwningObject, + IDBFactory** aFactory); static nsresult CreateForDatastore(JSContext* aCx, @@ -105,6 +105,10 @@ public: static bool AllowedForWindow(nsPIDOMWindow* aWindow); + static bool + AllowedForPrincipal(nsIPrincipal* aPrincipal, + bool* aIsSystemPrincipal = nullptr); + void AssertIsOnOwningThread() const #ifdef DEBUG diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 6ff21597c7..e61b22f23b 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -20,6 +20,7 @@ #include "IndexedDatabaseInlines.h" #include "IndexedDatabaseManager.h" #include "js/Class.h" +#include "js/Date.h" #include "js/StructuredClone.h" #include "KeyPath.h" #include "mozilla/Endian.h" @@ -747,8 +748,8 @@ public: return false; } - JS::Rooted date(aCx, - JS_NewDateObjectMsec(aCx, aData.lastModifiedDate)); + JS::ClippedTime time = JS::TimeClip(aData.lastModifiedDate); + JS::Rooted date(aCx, JS::NewDateObject(aCx, time)); if (NS_WARN_IF(!date)) { return false; } @@ -1191,7 +1192,8 @@ IDBObjectStore::AddOrPut(JSContext* aCx, } FallibleTArray cloneData; - if (NS_WARN_IF(!cloneData.SetLength(cloneWriteInfo.mCloneBuffer.nbytes()))) { + if (NS_WARN_IF(!cloneData.SetLength(cloneWriteInfo.mCloneBuffer.nbytes(), + fallible))) { aRv = NS_ERROR_OUT_OF_MEMORY; return nullptr; } diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index d6d08dba5d..f9cde64759 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -527,7 +527,6 @@ IndexedDatabaseManager::DefineIndexedDB(JSContext* aCx, JS::Handle aGlobal) { MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(nsContentUtils::IsCallerChrome(), "Only for chrome!"); MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL, "Passed object is not a global object!"); @@ -554,9 +553,9 @@ IndexedDatabaseManager::DefineIndexedDB(JSContext* aCx, } nsRefPtr factory; - if (NS_FAILED(IDBFactory::CreateForChromeJS(aCx, - aGlobal, - getter_AddRefs(factory)))) { + if (NS_FAILED(IDBFactory::CreateForMainThreadJS(aCx, + aGlobal, + getter_AddRefs(factory)))) { return false; } @@ -848,8 +847,19 @@ IndexedDatabaseManager::LoggingModePrefChangedCallback( return; } - bool useProfiler = + bool useProfiler = +#if defined(DEBUG) || defined(MOZ_ENABLE_PROFILER_SPS) + Preferences::GetBool(kPrefLoggingProfiler); +#if !defined(MOZ_ENABLE_PROFILER_SPS) + if (useProfiler) { + NS_WARNING("IndexedDB cannot create profiler marks because this build does " + "not have profiler extensions enabled!"); + useProfiler = false; + } +#endif +#else false; +#endif const bool logDetails = Preferences::GetBool(kPrefLoggingDetails); diff --git a/dom/indexedDB/Key.cpp b/dom/indexedDB/Key.cpp index b3fc55d930..f60ccb6baf 100644 --- a/dom/indexedDB/Key.cpp +++ b/dom/indexedDB/Key.cpp @@ -8,6 +8,7 @@ #include "Key.h" #include +#include "js/Date.h" #include "js/Value.h" #include "jsfriendapi.h" #include "mozilla/Endian.h" @@ -237,7 +238,11 @@ Key::DecodeJSValInternal(const unsigned char*& aPos, const unsigned char* aEnd, } else if (*aPos - aTypeOffset == eDate) { double msec = static_cast(DecodeNumber(aPos, aEnd)); - JSObject* date = JS_NewDateObjectMsec(aCx, msec); + JS::ClippedTime time = JS::TimeClip(msec); + MOZ_ASSERT(msec == time.toDouble(), + "encoding from a Date object not containing an invalid date " + "means we should always have clipped values"); + JSObject* date = JS::NewDateObject(aCx, time); if (!date) { IDB_WARNING("Failed to make date!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; diff --git a/dom/indexedDB/test/mochitest.ini b/dom/indexedDB/test/mochitest.ini index 33d01cd65e..87db7f087e 100644 --- a/dom/indexedDB/test/mochitest.ini +++ b/dom/indexedDB/test/mochitest.ini @@ -11,6 +11,8 @@ support-files = file_app_isolation.js helpers.js leaving_page_iframe.html + service_worker.js + service_worker_client.html third_party_iframe1.html third_party_iframe2.html unit/test_add_put.js @@ -327,6 +329,8 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_request_readyState.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 +[test_sandbox.html] +skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_setVersion.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Bug 931116 [test_setVersion_abort.html] @@ -373,3 +377,5 @@ skip-if = buildapp == 'b2g' || buildapp == 'mulet' || e10s || toolkit == 'androi # The clearBrowserData tests are only supposed to run in the main process. # They currently time out on android. skip-if = buildapp == 'b2g' || buildapp == 'mulet' || e10s || toolkit == 'android' +[test_serviceworker.html] +skip-if = buildapp == 'b2g' diff --git a/dom/indexedDB/test/service_worker.js b/dom/indexedDB/test/service_worker.js new file mode 100644 index 0000000000..eb8fd7f663 --- /dev/null +++ b/dom/indexedDB/test/service_worker.js @@ -0,0 +1,10 @@ +onmessage = function(e) { + self.clients.matchAll().then(function(res) { + if (!res.length) { + dump("Error: no clients are currently controlled.\n"); + return; + } + res[0].postMessage(indexedDB ? { available: true } : + { available: false }); + }); +}; diff --git a/dom/indexedDB/test/service_worker_client.html b/dom/indexedDB/test/service_worker_client.html new file mode 100644 index 0000000000..c1c98eaabb --- /dev/null +++ b/dom/indexedDB/test/service_worker_client.html @@ -0,0 +1,28 @@ + + + + +controlled page + + + + + + diff --git a/dom/indexedDB/test/test_sandbox.html b/dom/indexedDB/test/test_sandbox.html new file mode 100644 index 0000000000..a6c627fb16 --- /dev/null +++ b/dom/indexedDB/test/test_sandbox.html @@ -0,0 +1,101 @@ + + + + indexedDB in JS Sandbox + + + + + + + diff --git a/dom/indexedDB/test/test_serviceworker.html b/dom/indexedDB/test/test_serviceworker.html new file mode 100644 index 0000000000..505743802a --- /dev/null +++ b/dom/indexedDB/test/test_serviceworker.html @@ -0,0 +1,71 @@ + + + + + Bug 1137245 - Allow IndexedDB usage in ServiceWorkers + + + + +

+ +

+
+
+
+
diff --git a/dom/indexedDB/test/unit/test_sandbox.js b/dom/indexedDB/test/unit/test_sandbox.js
new file mode 100644
index 0000000000..bcd4cad51c
--- /dev/null
+++ b/dom/indexedDB/test/unit/test_sandbox.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function exerciseInterface() {
+  function DB(name, store) {
+    this.name = name;
+    this.store = store;
+    this._db = this._create();
+  }
+
+  DB.prototype = {
+    _create: function() {
+      var op = indexedDB.open(this.name);
+      op.onupgradeneeded = e => {
+        var db = e.target.result;
+        db.createObjectStore(this.store);
+      };
+      return new Promise(resolve => {
+        op.onsuccess = e => resolve(e.target.result);
+      });
+    },
+
+    _result: function(tx, op) {
+      return new Promise((resolve, reject) => {
+        op.onsuccess = e => resolve(e.target.result);
+        op.onerror = () => reject(op.error);
+        tx.onabort = () => reject(tx.error);
+      });
+    },
+
+    get: function(k) {
+      return this._db.then(db => {
+        var tx = db.transaction(this.store, 'readonly');
+        var store = tx.objectStore(this.store);
+        return this._result(tx, store.get(k));
+      });
+    },
+
+    add: function(k, v) {
+      return this._db.then(db => {
+        var tx = db.transaction(this.store, 'readwrite');
+        var store = tx.objectStore(this.store);
+        return this._result(tx, store.add(v, k));
+      });
+    }
+  };
+
+  var db = new DB('data', 'base');
+  return db.add('x', [ 10, {} ])
+    .then(_ => db.get('x'))
+    .then(x => {
+      equal(x.length, 2);
+      equal(x[0], 10);
+      equal(typeof x[1], 'object');
+      equal(Object.keys(x[1]).length, 0);
+    });
+}
+
+function run_test() {
+  do_get_profile();
+
+  let Cu = Components.utils;
+  let sb = new Cu.Sandbox('https://www.example.com',
+                          { wantGlobalProperties: ['indexedDB'] });
+
+  sb.equal = equal;
+  var innerPromise = new Promise((resolve, reject) => {
+    sb.test_done = resolve;
+    sb.test_error = reject;
+  });
+  Cu.evalInSandbox('(' + exerciseInterface.toSource() + ')()' +
+                   '.then(test_done, test_error);', sb);
+
+  Cu.importGlobalProperties(['indexedDB']);
+  do_test_pending();
+  Promise.all([innerPromise, exerciseInterface()])
+    .then(do_test_finished);
+}
diff --git a/dom/indexedDB/test/unit/xpcshell-shared.ini b/dom/indexedDB/test/unit/xpcshell-shared.ini
index 22e40fb252..0aaaf3c90d 100644
--- a/dom/indexedDB/test/unit/xpcshell-shared.ini
+++ b/dom/indexedDB/test/unit/xpcshell-shared.ini
@@ -59,6 +59,7 @@ skip-if = toolkit == 'android' # bug 1079278
 [test_remove_index.js]
 [test_remove_objectStore.js]
 [test_request_readyState.js]
+[test_sandbox.js]
 [test_setVersion.js]
 [test_setVersion_abort.js]
 [test_setVersion_events.js]
diff --git a/dom/interfaces/base/nsITabParent.idl b/dom/interfaces/base/nsITabParent.idl
index 01c4fa1842..e7b2e6c0bf 100644
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -5,7 +5,7 @@
 
 #include "domstubs.idl"
 
-[scriptable, uuid(b19038ba-0d75-40d2-be35-742e26d33bf9)]
+[scriptable, uuid(4d4576eb-ecfe-47b9-93b8-4121518621ad)]
 interface nsITabParent : nsISupports
 {
   void injectTouchEvent(in AString aType,
@@ -26,4 +26,6 @@ interface nsITabParent : nsISupports
   void setIsDocShellActive(in bool aIsActive);
 
   readonly attribute uint64_t tabId;
+
+  readonly attribute boolean hasContentOpener;
 };
diff --git a/dom/ipc/Blob.cpp b/dom/ipc/Blob.cpp
index a713dc321d..6fcd74ec7c 100644
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -968,7 +968,7 @@ CreateBlobImpl(const nsTArray& aBlobDatas,
   }
 
   FallibleTArray> fallibleBlobImpls;
-  if (NS_WARN_IF(!fallibleBlobImpls.SetLength(aBlobDatas.Length()))) {
+  if (NS_WARN_IF(!fallibleBlobImpls.SetLength(aBlobDatas.Length(), fallible))) {
     return nullptr;
   }
 
diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp
index 5b94e70baa..3a4b800463 100644
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2645,14 +2645,17 @@ ContentChild::RecvStopProfiler()
 }
 
 bool
-ContentChild::RecvGetProfile(nsCString* aProfile)
+ContentChild::RecvGatherProfile()
 {
+    nsCString profileCString;
     UniquePtr profile = profiler_get_profile();
     if (profile) {
-        *aProfile = nsCString(profile.get(), strlen(profile.get()));
+        profileCString = nsCString(profile.get(), strlen(profile.get()));
     } else {
-        *aProfile = EmptyCString();
+        profileCString = EmptyCString();
     }
+
+    unused << SendProfile(profileCString);
     return true;
 }
 
diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h
index 6bed82b22e..441461eed4 100644
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -393,7 +393,7 @@ public:
                                    nsTArray&& aFeatures,
                                    nsTArray&& aThreadNameFilters) override;
     virtual bool RecvStopProfiler() override;
-    virtual bool RecvGetProfile(nsCString* aProfile) override;
+    virtual bool RecvGatherProfile() override;
     virtual bool RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType,
                                       const OptionalURIParams& aDomain) override;
     virtual bool RecvShutdown() override;
diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp
index 1b8b87b39f..3e9d5dee24 100755
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -82,6 +82,9 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "mozilla/ProfileGatherer.h"
+#endif
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
@@ -226,6 +229,11 @@ using namespace mozilla::system;
 #include "nsIBrowserSearchService.h"
 #endif
 
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "nsIProfiler.h"
+#include "nsIProfileSaveEvent.h"
+#endif
+
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadMonitoring.h"
 #endif
@@ -234,6 +242,9 @@ static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
 
 using base::ChildPrivileges;
 using base::KillProcess;
+#ifdef MOZ_ENABLE_PROFILER_SPS
+using mozilla::ProfileGatherer;
+#endif
 
 using namespace mozilla::dom::bluetooth;
 using namespace mozilla::dom::cellbroadcast;
@@ -652,6 +663,12 @@ static const char* sObserverTopics[] = {
     "a11y-init-or-shutdown",
 #endif
     "app-theme-changed",
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    "profiler-started",
+    "profiler-stopped",
+    "profiler-subprocess-gather",
+    "profiler-subprocess",
+#endif
 };
 
 /* static */ already_AddRefed
@@ -1058,17 +1075,21 @@ ContentParent::RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
 }
 
 bool
-ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv)
+ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID)
 {
     *aRv = NS_OK;
-    return mozilla::plugins::SetupBridge(aPluginId, this, false, aRv);
+    return mozilla::plugins::SetupBridge(aPluginId, this, false, aRv, aRunID);
 }
 
 bool
 ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv)
 {
     *aRv = NS_OK;
-    return mozilla::plugins::SetupBridge(aPluginId, this, true, aRv);
+    // We don't need to get the run ID for the plugin, since we already got it
+    // in the first call to SetupBridge in RecvLoadPlugin, so we pass in a dummy
+    // pointer and just throw it away.
+    uint32_t dummy = 0;
+    return mozilla::plugins::SetupBridge(aPluginId, this, true, aRv, &dummy);
 }
 
 bool
@@ -3143,6 +3164,30 @@ ContentParent::Observe(nsISupports* aSubject,
     else if (!strcmp(aTopic, "app-theme-changed")) {
         unused << SendOnAppThemeChanged();
     }
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    else if (!strcmp(aTopic, "profiler-started")) {
+        nsCOMPtr params(do_QueryInterface(aSubject));
+        uint32_t entries;
+        double interval;
+        params->GetEntries(&entries);
+        params->GetInterval(&interval);
+        const nsTArray& features = params->GetFeatures();
+        const nsTArray& threadFilterNames = params->GetThreadFilterNames();
+        unused << SendStartProfiler(entries, interval, features, threadFilterNames);
+    }
+    else if (!strcmp(aTopic, "profiler-stopped")) {
+        unused << SendStopProfiler();
+    }
+    else if (!strcmp(aTopic, "profiler-subprocess")) {
+        nsCOMPtr pse = do_QueryInterface(aSubject);
+        if (pse) {
+            if (!mProfile.IsEmpty()) {
+                pse->AddSubProfile(mProfile.get());
+                mProfile.Truncate();
+            }
+        }
+    }
+#endif
     return NS_OK;
 }
 
@@ -5071,6 +5116,20 @@ ContentParent::RecvGamepadListenerRemoved()
     return true;
 }
 
+bool
+ContentParent::RecvProfile(const nsCString& aProfile)
+{
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    if (NS_WARN_IF(!mGatherer)) {
+        return true;
+    }
+    mProfile = aProfile;
+    mGatherer->GatheredOOPProfile();
+    mGatherer = nullptr;
+#endif
+    return true;
+}
+
 } // namespace dom
 } // namespace mozilla
 
diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h
index 8c40cae33f..c6e6d629bb 100644
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -37,6 +37,9 @@ class nsIWidget;
 
 namespace mozilla {
 class PRemoteSpellcheckEngineParent;
+#ifdef MOZ_ENABLE_PROFILER_SPS
+class ProfileGatherer;
+#endif
 
 namespace ipc {
 class OptionalURIParams;
@@ -172,7 +175,7 @@ public:
                                                bool* aHasPlugin,
                                                nsCString* aVersion) override;
 
-    virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv) override;
+    virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override;
     virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) override;
     virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch,
                                  nsTArray* aPlugins,
@@ -853,6 +856,7 @@ private:
 
     virtual bool RecvGamepadListenerAdded() override;
     virtual bool RecvGamepadListenerRemoved() override;
+    virtual bool RecvProfile(const nsCString& aProfile) override;
 
     // If you add strong pointers to cycle collected objects here, be sure to
     // release these objects in ShutDownProcess.  See the comment there for more
@@ -926,6 +930,10 @@ private:
 #endif
 
     PProcessHangMonitorParent* mHangMonitorActor;
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    nsRefPtr mGatherer;
+#endif
+    nsCString mProfile;
 };
 
 } // namespace dom
diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl
index 14f5bd346c..d04a72c04f 100644
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -623,8 +623,8 @@ child:
     async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures,
                         nsCString[] aThreadNameFilters);
     async StopProfiler();
-    prio(high) sync GetProfile()
-      returns (nsCString aProfile);
+
+    async GatherProfile();
 
     InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action);
 
@@ -697,7 +697,7 @@ parent:
      * via bridging. The corresponding PluginModuleChild will live in the plugin
      * process.
      */
-    sync LoadPlugin(uint32_t pluginId) returns (nsresult rv);
+    sync LoadPlugin(uint32_t aPluginId) returns (nsresult aResult, uint32_t aRunID);
 
     /**
      * This call is used by asynchronous plugin instantiation to notify the
@@ -1058,6 +1058,8 @@ parent:
      */
     GamepadListenerRemoved();
 
+    async Profile(nsCString aProfile);
+
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData,
                   CpowEntry[] aCpows, Principal aPrincipal);
diff --git a/dom/ipc/ProcessHangMonitor.cpp b/dom/ipc/ProcessHangMonitor.cpp
index ecfeadb196..74c2e450e8 100644
--- a/dom/ipc/ProcessHangMonitor.cpp
+++ b/dom/ipc/ProcessHangMonitor.cpp
@@ -22,6 +22,9 @@
 #include "nsITabParent.h"
 #include "nsPluginHost.h"
 #include "nsThreadUtils.h"
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#endif
 
 #include "base/task.h"
 #include "base/thread.h"
@@ -149,12 +152,20 @@ public:
   NS_IMETHOD EndStartingDebugger() override;
   NS_IMETHOD TerminatePlugin() override;
   NS_IMETHOD TerminateProcess() override;
+  NS_IMETHOD UserCanceled() override;
 
   NS_IMETHOD IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aResult) override;
 
-  void Clear() { mContentParent = nullptr; mActor = nullptr; }
+  // Called when a content process shuts down.
+  void Clear() {
+    mContentParent = nullptr;
+    mActor = nullptr;
+  }
 
   void SetHangData(const HangData& aHangData) { mHangData = aHangData; }
+  void SetBrowserDumpId(nsAutoString& aId) {
+    mBrowserDumpId = aId;
+  }
 
 private:
   ~HangMonitoredProcess() {}
@@ -163,6 +174,7 @@ private:
   HangMonitorParent* mActor;
   ContentParent* mContentParent;
   HangData mHangData;
+  nsAutoString mBrowserDumpId;
 };
 
 class HangMonitorParent
@@ -185,6 +197,7 @@ public:
   void TerminateScript();
   void BeginStartingDebugger();
   void EndStartingDebugger();
+  void CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles);
 
   MessageLoop* MonitorLoop() { return mHangMonitor->MonitorLoop(); }
 
@@ -204,6 +217,9 @@ public:
   // Must be accessed with mMonitor held.
   nsRefPtr mProcess;
   bool mShutdownDone;
+  // Map from plugin ID to crash dump ID. Protected by mBrowserCrashDumpHashLock.
+  nsDataHashtable mBrowserCrashDumpIds;
+  Mutex mBrowserCrashDumpHashLock;
 };
 
 } // namespace
@@ -389,10 +405,12 @@ HangMonitorChild::IsDebuggerStartupComplete()
 void
 HangMonitorChild::NotifyPluginHang(uint32_t aPluginId)
 {
+  // main thread in the child
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
   mSentReport = true;
 
+  // bounce to background thread
   MonitorLoop()->PostTask(
     FROM_HERE,
     NewRunnableMethod(this,
@@ -405,6 +423,7 @@ HangMonitorChild::NotifyPluginHangAsync(uint32_t aPluginId)
 {
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
+  // bounce back to parent on background thread
   if (mIPCOpen) {
     unused << SendHangEvidence(PluginHangData(aPluginId));
   }
@@ -430,17 +449,32 @@ HangMonitorParent::HangMonitorParent(ProcessHangMonitor* aMonitor)
  : mHangMonitor(aMonitor),
    mIPCOpen(true),
    mMonitor("HangMonitorParent lock"),
-   mShutdownDone(false)
+   mShutdownDone(false),
+   mBrowserCrashDumpHashLock("mBrowserCrashDumpIds lock")
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   mReportHangs = mozilla::Preferences::GetBool("dom.ipc.reportProcessHangs", false);
 }
 
+static PLDHashOperator
+DeleteMinidump(const uint32_t& aPluginId, nsString aCrashId, void* aUserData)
+{
+#ifdef MOZ_CRASHREPORTER
+  if (!aCrashId.IsEmpty()) {
+    CrashReporter::DeleteMinidumpFilesForID(aCrashId);
+  }
+#endif
+  return PL_DHASH_NEXT;
+}
+
 HangMonitorParent::~HangMonitorParent()
 {
   // For some reason IPDL doesn't autmatically delete the channel for a
   // bridged protocol (bug 1090570). So we have to do it ourselves.
   XRE_GetIOMessageLoop()->PostTask(FROM_HERE, new DeleteTask(GetTransport()));
+
+  MutexAutoLock lock(mBrowserCrashDumpHashLock);
+  mBrowserCrashDumpIds.EnumerateRead(DeleteMinidump, nullptr);
 }
 
 void
@@ -501,16 +535,21 @@ HangMonitorParent::Open(Transport* aTransport, ProcessId aPid,
 class HangObserverNotifier final : public nsRunnable
 {
 public:
-  HangObserverNotifier(HangMonitoredProcess* aProcess, const HangData& aHangData)
+  HangObserverNotifier(HangMonitoredProcess* aProcess,
+                       const HangData& aHangData,
+                       const nsString& aBrowserDumpId)
     : mProcess(aProcess),
-      mHangData(aHangData)
+      mHangData(aHangData),
+      mBrowserDumpId(aBrowserDumpId)
   {}
 
   NS_IMETHOD
   Run()
   {
+    // chrome process, main thread
     MOZ_RELEASE_ASSERT(NS_IsMainThread());
     mProcess->SetHangData(mHangData);
+    mProcess->SetBrowserDumpId(mBrowserDumpId);
 
     nsCOMPtr observerService =
       mozilla::services::GetObserverService();
@@ -521,11 +560,13 @@ public:
 private:
   nsRefPtr mProcess;
   HangData mHangData;
+  nsAutoString mBrowserDumpId;
 };
 
 bool
 HangMonitorParent::RecvHangEvidence(const HangData& aHangData)
 {
+  // chrome process, background thread
   MOZ_RELEASE_ASSERT(MessageLoop::current() == MonitorLoop());
 
   if (!mReportHangs) {
@@ -540,11 +581,33 @@ HangMonitorParent::RecvHangEvidence(const HangData& aHangData)
   }
 #endif
 
+  // Before we wake up the browser main thread we want to take a
+  // browser minidump.
+  nsAutoString crashId;
+#ifdef MOZ_CRASHREPORTER
+  if (aHangData.type() == HangData::TPluginHangData) {
+    MutexAutoLock lock(mBrowserCrashDumpHashLock);
+    const PluginHangData& phd = aHangData.get_PluginHangData();
+    if (!mBrowserCrashDumpIds.Get(phd.pluginId(), &crashId)) {
+      nsCOMPtr browserDump;
+      if (CrashReporter::TakeMinidump(getter_AddRefs(browserDump), true)) {
+        if (!CrashReporter::GetIDFromMinidump(browserDump, crashId) || crashId.IsEmpty()) {
+          browserDump->Remove(false);
+          NS_WARNING("Failed to generate timely browser stack, this is bad for plugin hang analysis!");
+        } else {
+          mBrowserCrashDumpIds.Put(phd.pluginId(), crashId);
+        }
+      }
+    }
+  }
+#endif
+
   mHangMonitor->InitiateCPOWTimeout();
 
   MonitorAutoLock lock(mMonitor);
 
-  nsCOMPtr notifier = new HangObserverNotifier(mProcess, aHangData);
+  nsCOMPtr notifier =
+    new HangObserverNotifier(mProcess, aHangData, crashId);
   NS_DispatchToMainThread(notifier);
 
   return true;
@@ -580,6 +643,22 @@ HangMonitorParent::EndStartingDebugger()
   }
 }
 
+void
+HangMonitorParent::CleanupPluginHang(uint32_t aPluginId, bool aRemoveFiles)
+{
+  MutexAutoLock lock(mBrowserCrashDumpHashLock);
+  nsAutoString crashId;
+  if (!mBrowserCrashDumpIds.Get(aPluginId, &crashId)) {
+    return;
+  }
+  mBrowserCrashDumpIds.Remove(aPluginId);
+#ifdef MOZ_CRASHREPORTER
+  if (aRemoveFiles && !crashId.IsEmpty()) {
+    CrashReporter::DeleteMinidumpFilesForID(crashId);
+  }
+#endif
+}
+
 /* HangMonitoredProcess implementation */
 
 NS_IMPL_ISUPPORTS(HangMonitoredProcess, nsIHangReport)
@@ -738,7 +817,11 @@ HangMonitoredProcess::TerminatePlugin()
   }
 
   uint32_t id = mHangData.get_PluginHangData().pluginId();
-  plugins::TerminatePlugin(id);
+  plugins::TerminatePlugin(id, mBrowserDumpId);
+
+  if (mActor) {
+    mActor->CleanupPluginHang(id, false);
+  }
   return NS_OK;
 }
 
@@ -751,6 +834,11 @@ HangMonitoredProcess::TerminateProcess()
     return NS_ERROR_UNEXPECTED;
   }
 
+  if (mActor && mHangData.type() == HangData::TPluginHangData) {
+    uint32_t id = mHangData.get_PluginHangData().pluginId();
+    mActor->CleanupPluginHang(id, true);
+  }
+
   mContentParent->KillHard("HangMonitor");
   return NS_OK;
 }
@@ -775,6 +863,21 @@ HangMonitoredProcess::IsReportForBrowser(nsIFrameLoader* aFrameLoader, bool* aRe
   return NS_OK;
 }
 
+NS_IMETHODIMP
+HangMonitoredProcess::UserCanceled()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  if (mHangData.type() != HangData::TPluginHangData) {
+    return NS_OK;
+  }
+
+  if (mActor) {
+    uint32_t id = mHangData.get_PluginHangData().pluginId();
+    mActor->CleanupPluginHang(id, true);
+  }
+  return NS_OK;
+}
+
 ProcessHangMonitor* ProcessHangMonitor::sInstance;
 
 ProcessHangMonitor::ProcessHangMonitor()
diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp
index ce0132de9f..0c17e82b63 100644
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -284,6 +284,7 @@ TabParent::TabParent(nsIContentParent* aManager,
   , mNeedLayerTreeReadyNotification(false)
   , mCursor(nsCursor(-1))
   , mTabSetsCursor(false)
+  , mHasContentOpener(false)
 {
   MOZ_ASSERT(aManager);
 }
@@ -609,6 +610,11 @@ TabParent::RecvCreateWindow(PBrowserParent* aNewTab,
   NS_ENSURE_SUCCESS(rv, false);
 
   TabParent* newTab = TabParent::GetFrom(aNewTab);
+  MOZ_ASSERT(newTab);
+
+  // Content has requested that we open this new content window, so
+  // we must have an opener.
+  newTab->SetHasContentOpener(true);
 
   nsCOMPtr frame(do_QueryInterface(mFrameElement));
 
@@ -2909,6 +2915,19 @@ TabParent::GetTabId(uint64_t* aId)
   return NS_OK;
 }
 
+NS_IMETHODIMP
+TabParent::GetHasContentOpener(bool* aResult)
+{
+  *aResult = mHasContentOpener;
+  return NS_OK;
+}
+
+void
+TabParent::SetHasContentOpener(bool aHasContentOpener)
+{
+  mHasContentOpener = aHasContentOpener;
+}
+
 class LayerTreeUpdateRunnable final
   : public nsRunnable
 {
diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h
index c0870eede4..de58650344 100644
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -461,6 +461,8 @@ protected:
     bool InitBrowserConfiguration(const nsCString& aURI,
                                   BrowserConfiguration& aConfiguration);
 
+    void SetHasContentOpener(bool aHasContentOpener);
+
     // IME
     static TabParent *mIMETabParent;
     nsString mIMECacheText;
@@ -608,6 +610,7 @@ private:
 
     nsRefPtr mPresShellWithRefreshListener;
 
+    bool mHasContentOpener;
 private:
     // This is used when APZ needs to find the TabParent associated with a layer
     // to dispatch events.
diff --git a/dom/ipc/nsIHangReport.idl b/dom/ipc/nsIHangReport.idl
index d8ac125052..565ad8a6e3 100644
--- a/dom/ipc/nsIHangReport.idl
+++ b/dom/ipc/nsIHangReport.idl
@@ -18,7 +18,7 @@ interface nsIFrameLoader;
  * process will continue to run uninhibitedly during this time.
  */
 
-[scriptable, uuid(3b88d100-8d5b-11e4-b4a9-0800200c9a66)]
+[scriptable, uuid(90cea731-dd3e-459e-b017-f9a14697b56e)]
 interface nsIHangReport : nsISupports
 {
   const unsigned long SLOW_SCRIPT = 1;
@@ -38,6 +38,10 @@ interface nsIHangReport : nsISupports
   // Only valid for PLUGIN_HANG reports.
   readonly attribute ACString pluginName;
 
+  // Called by front end code when user ignores or cancels
+  // the notification.
+  void userCanceled();
+
   // Terminate the slow script if it is still running.
   // Only valid for SLOW_SCRIPT reports.
   void terminateScript();
diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp
index 63d8ba336c..bd011d61f9 100644
--- a/dom/media/MediaData.cpp
+++ b/dom/media/MediaData.cpp
@@ -3,6 +3,7 @@
 /* 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 "MediaData.h"
 #include "MediaInfo.h"
 #ifdef MOZ_OMX_DECODER
diff --git a/dom/media/PeerConnection.js b/dom/media/PeerConnection.js
index 0f0ddf1643..b7a0c9e820 100644
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -47,9 +47,14 @@ function GlobalPCList() {
   Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
   Services.obs.addObserver(this, "network:offline-status-changed", true);
   Services.obs.addObserver(this, "gmp-plugin-crash", true);
+  if (Cc["@mozilla.org/childprocessmessagemanager;1"]) {
+    let mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+    mm.addMessageListener("gmp-plugin-crash", this);
+  }
 }
 GlobalPCList.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsIMessageListener,
                                          Ci.nsISupportsWeakReference,
                                          Ci.IPeerConnectionManager]),
   classID: PC_MANAGER_CID,
@@ -95,6 +100,31 @@ GlobalPCList.prototype = {
     return this._list[winID] ? true : false;
   },
 
+  handleGMPCrash: function(data) {
+    let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
+      if (list.hasOwnProperty(winID)) {
+        list[winID].forEach(function(pcref) {
+          let pc = pcref.get();
+          if (pc) {
+            pc._pc.pluginCrash(pluginID, pluginName);
+          }
+        });
+      }
+    };
+
+    // a plugin crashed; if it's associated with any of our PCs, fire an
+    // event to the DOM window
+    for (let winId in this._list) {
+      broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
+    }
+  },
+
+  receiveMessage: function(message) {
+    if (message.name == "gmp-plugin-crash") {
+      this.handleGMPCrash(message.data);
+    }
+  },
+
   observe: function(subject, topic, data) {
     let cleanupPcRef = function(pcref) {
       let pc = pcref.get();
@@ -112,17 +142,6 @@ GlobalPCList.prototype = {
       }
     };
 
-    let broadcastPluginCrash = function(list, winID, pluginID, name, crashReportID) {
-      if (list.hasOwnProperty(winID)) {
-        list[winID].forEach(function(pcref) {
-          let pc = pcref.get();
-          if (pc) {
-            pc._pc.pluginCrash(pluginID, name, crashReportID);
-          }
-        });
-      }
-    };
-
     if (topic == "inner-window-destroyed") {
       let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
       cleanupWinId(this._list, winID);
@@ -160,17 +179,11 @@ GlobalPCList.prototype = {
       }
       this._networkdown = !this._win.navigator.onLine;
     } else if (topic == "gmp-plugin-crash") {
-      // a plugin crashed; if it's associated with any of our PCs, fire an
-      // event to the DOM window
-      let sep = data.indexOf(' ');
-      let pluginId = data.slice(0, sep);
-      let rest = data.slice(sep+1);
-      // This presumes no spaces in the name!
-      sep = rest.indexOf(' ');
-      let name = rest.slice(0, sep);
-      let crashId = rest.slice(sep+1);
-      for (let winId in this._list) {
-        broadcastPluginCrash(this._list, winId, pluginId, name, crashId);
+      if (subject instanceof Ci.nsIWritablePropertyBag2) {
+        let pluginID = subject.getPropertyAsUint32("pluginID");
+        let pluginName = subject.getPropertyAsAString("pluginName");
+        let data = { pluginID, pluginName };
+        this.handleGMPCrash(data);
       }
     }
   },
diff --git a/dom/media/gmp/GMPContentParent.h b/dom/media/gmp/GMPContentParent.h
index c137b675fb..b85d2c7e39 100644
--- a/dom/media/gmp/GMPContentParent.h
+++ b/dom/media/gmp/GMPContentParent.h
@@ -51,11 +51,11 @@ public:
   {
     return mDisplayName;
   }
-  void SetPluginId(const nsCString& aPluginId)
+  void SetPluginId(const uint32_t aPluginId)
   {
     mPluginId = aPluginId;
   }
-  const nsCString& GetPluginId()
+  const uint32_t GetPluginId()
   {
     return mPluginId;
   }
@@ -92,7 +92,7 @@ private:
   nsCOMPtr mGMPThread;
   nsRefPtr mParent;
   nsCString mDisplayName;
-  nsCString mPluginId;
+  uint32_t mPluginId;
 };
 
 } // namespace gmp
diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp
index 250cac74af..4a1d73ae13 100644
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -28,7 +28,7 @@ GMPDecryptorParent::~GMPDecryptorParent()
 {
 }
 
-const nsACString&
+const uint32_t
 GMPDecryptorParent::GetPluginId() const
 {
   return mPluginId;
diff --git a/dom/media/gmp/GMPDecryptorParent.h b/dom/media/gmp/GMPDecryptorParent.h
index cbb975e1d3..fa8f9071aa 100644
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -28,7 +28,7 @@ public:
   explicit GMPDecryptorParent(GMPContentParent *aPlugin);
 
   // GMPDecryptorProxy
-  virtual const nsACString& GetPluginId() const override;
+  virtual const uint32_t GetPluginId() const override;
 
   virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) override;
 
@@ -111,7 +111,7 @@ private:
   bool mIsOpen;
   bool mShuttingDown;
   nsRefPtr mPlugin;
-  nsCString mPluginId;
+  uint32_t mPluginId;
   GMPDecryptorProxyCallback* mCallback;
 #ifdef DEBUG
   nsIThread* const mGMPThread;
diff --git a/dom/media/gmp/GMPDecryptorProxy.h b/dom/media/gmp/GMPDecryptorProxy.h
index 7fdb4cc133..56b89629c9 100644
--- a/dom/media/gmp/GMPDecryptorProxy.h
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -59,7 +59,7 @@ class GMPDecryptorProxy {
 public:
   ~GMPDecryptorProxy() {}
 
-  virtual const nsACString& GetPluginId() const = 0;
+  virtual const uint32_t GetPluginId() const = 0;
 
   virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) = 0;
 
diff --git a/dom/media/gmp/GMPParent.cpp b/dom/media/gmp/GMPParent.cpp
index 147b1e6722..cabe4580e6 100644
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -13,7 +13,9 @@
 #include "nsCharSeparatedTokenizer.h"
 #include "nsThreadUtils.h"
 #include "nsIRunnable.h"
+#include "nsIWritablePropertyBag2.h"
 #include "mozIGeckoMediaPluginService.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/unused.h"
 #include "nsIObserverService.h"
@@ -23,6 +25,8 @@
 #include "mozilla/SandboxInfo.h"
 #endif
 
+using mozilla::ipc::GeckoChildProcessHost;
+
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
@@ -49,9 +53,7 @@ GMPParent::GMPParent()
   , mChildPid(0)
 {
   LOGD("GMPParent ctor");
-  // Use the parent address to identify it.
-  // We could use any unique-to-the-parent value.
-  mPluginId.AppendInt(reinterpret_cast(this));
+  mPluginId = GeckoChildProcessHost::GetUniqueID();
 }
 
 GMPParent::~GMPParent()
@@ -722,7 +724,7 @@ GMPParent::GetVersion() const
   return mVersion;
 }
 
-const nsCString&
+const uint32_t
 GMPParent::GetPluginId() const
 {
   return mPluginId;
diff --git a/dom/media/gmp/GMPParent.h b/dom/media/gmp/GMPParent.h
index fb9822dd1b..db7ccb608e 100644
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -111,7 +111,7 @@ public:
 
   const nsCString& GetDisplayName() const;
   const nsCString& GetVersion() const;
-  const nsCString& GetPluginId() const;
+  const uint32_t GetPluginId() const;
 
   // Returns true if a plugin can be or is being used across multiple NodeIds.
   bool CanBeSharedCrossNodeIds() const;
@@ -173,7 +173,7 @@ private:
   nsCString mDisplayName; // name of plugin displayed to users
   nsCString mDescription; // description of plugin for display to users
   nsCString mVersion;
-  nsCString mPluginId;
+  uint32_t mPluginId;
   nsTArray> mCapabilities;
   GMPProcessParent* mProcess;
   bool mDeleteProcessOnlyOnUnload;
diff --git a/dom/media/gmp/GMPService.cpp b/dom/media/gmp/GMPService.cpp
index 6d2ee655b2..65982e951e 100644
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -160,9 +160,8 @@ GeckoMediaPluginService::RemoveObsoletePluginCrashCallbacks()
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
     nsRefPtr& callback = mPluginCrashCallbacks[i - 1];
     if (!callback->IsStillValid()) {
-      LOGD(("%s::%s - Removing obsolete callback for pluginId %s",
-            __CLASS__, __FUNCTION__,
-            PromiseFlatCString(callback->PluginId()).get()));
+      LOGD(("%s::%s - Removing obsolete callback for pluginId %i",
+            __CLASS__, __FUNCTION__, callback->PluginId()));
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     }
   }
@@ -177,7 +176,7 @@ GeckoMediaPluginService::AddPluginCrashCallback(
 }
 
 void
-GeckoMediaPluginService::RemovePluginCrashCallbacks(const nsACString& aPluginId)
+GeckoMediaPluginService::RemovePluginCrashCallbacks(const uint32_t aPluginId)
 {
   RemoveObsoletePluginCrashCallbacks();
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
@@ -189,24 +188,22 @@ GeckoMediaPluginService::RemovePluginCrashCallbacks(const nsACString& aPluginId)
 }
 
 void
-GeckoMediaPluginService::RunPluginCrashCallbacks(const nsACString& aPluginId,
-                                                 const nsACString& aPluginName,
-                                                 const nsAString& aPluginDumpId)
+GeckoMediaPluginService::RunPluginCrashCallbacks(const uint32_t aPluginId,
+                                                 const nsACString& aPluginName)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  LOGD(("%s::%s(%s)", __CLASS__, __FUNCTION__, aPluginId.Data()));
+  LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
     nsRefPtr& callback = mPluginCrashCallbacks[i - 1];
-    const nsACString& callbackPluginId = callback->PluginId();
+    const uint32_t callbackPluginId = callback->PluginId();
     if (!callback->IsStillValid()) {
-      LOGD(("%s::%s(%s) - Removing obsolete callback for pluginId %s",
-            __CLASS__, __FUNCTION__, aPluginId.Data(),
-            PromiseFlatCString(callback->PluginId()).get()));
+      LOGD(("%s::%s(%i) - Removing obsolete callback for pluginId %i",
+            __CLASS__, __FUNCTION__, aPluginId, callback->PluginId()));
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     } else if (callbackPluginId == aPluginId) {
-      LOGD(("%s::%s(%s) - Running #%u",
-          __CLASS__, __FUNCTION__, aPluginId.Data(), i - 1));
-      callback->Run(aPluginName, aPluginDumpId);
+      LOGD(("%s::%s(%i) - Running #%u",
+          __CLASS__, __FUNCTION__, aPluginId, i - 1));
+      callback->Run(aPluginName);
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     }
   }
diff --git a/dom/media/gmp/GMPService.h b/dom/media/gmp/GMPService.h
index 349f33af00..d1fbfd6a69 100644
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -66,13 +66,13 @@ public:
   public:
     NS_INLINE_DECL_REFCOUNTING(PluginCrashCallback)
 
-    PluginCrashCallback(const nsACString& aPluginId)
+    PluginCrashCallback(const uint32_t aPluginId)
       : mPluginId(aPluginId)
     {
       MOZ_ASSERT(NS_IsMainThread());
     }
-    const nsACString& PluginId() const { return mPluginId; }
-    virtual void Run(const nsACString& aPluginName, const nsAString& aPluginDumpId) = 0;
+    const uint32_t PluginId() const { return mPluginId; }
+    virtual void Run(const nsACString& aPluginName) = 0;
     virtual bool IsStillValid() = 0; // False if callback has become useless.
   protected:
     virtual ~PluginCrashCallback()
@@ -80,14 +80,13 @@ public:
       MOZ_ASSERT(NS_IsMainThread());
     }
   private:
-    const nsCString mPluginId;
+    const uint32_t mPluginId;
   };
   void RemoveObsoletePluginCrashCallbacks(); // Called from add/remove/run.
   void AddPluginCrashCallback(nsRefPtr aPluginCrashCallback);
-  void RemovePluginCrashCallbacks(const nsACString& aPluginId);
-  void RunPluginCrashCallbacks(const nsACString& aPluginId,
-                               const nsACString& aPluginName,
-                               const nsAString& aPluginDumpId);
+  void RemovePluginCrashCallbacks(const uint32_t aPluginId);
+  void RunPluginCrashCallbacks(const uint32_t aPluginId,
+                               const nsACString& aPluginName);
 
 protected:
   GeckoMediaPluginService();
diff --git a/dom/media/gmp/GMPServiceChild.cpp b/dom/media/gmp/GMPServiceChild.cpp
index 125e664063..15f004d395 100644
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -77,7 +77,7 @@ public:
 
     base::ProcessId otherProcess;
     nsCString displayName;
-    nsCString pluginId;
+    uint32_t pluginId;
     bool ok = aGMPServiceChild->SendLoadGMP(mNodeId, mAPI, mTags,
                                             alreadyBridgedTo, &otherProcess,
                                             &displayName, &pluginId);
diff --git a/dom/media/gmp/GMPServiceParent.cpp b/dom/media/gmp/GMPServiceParent.cpp
index c9491acb12..e78560b8ca 100644
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -1336,7 +1336,7 @@ GMPServiceParent::RecvLoadGMP(const nsCString& aNodeId,
                               nsTArray&& aAlreadyBridgedTo,
                               ProcessId* aId,
                               nsCString* aDisplayName,
-                              nsCString* aPluginId)
+                              uint32_t* aPluginId)
 {
   nsRefPtr gmp = mService->SelectPluginForAPI(aNodeId, aAPI, aTags);
 
diff --git a/dom/media/gmp/GMPServiceParent.h b/dom/media/gmp/GMPServiceParent.h
index 152683d11b..b776777807 100644
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -190,7 +190,7 @@ public:
                            nsTArray&& aAlreadyBridgedTo,
                            base::ProcessId* aID,
                            nsCString* aDisplayName,
-                           nsCString* aPluginId) override;
+                           uint32_t* aPluginId) override;
   virtual bool RecvGetGMPNodeId(const nsString& aOrigin,
                                 const nsString& aTopLevelOrigin,
                                 const bool& aInPrivateBrowsing,
diff --git a/dom/media/gmp/GMPVideoEncoderParent.cpp b/dom/media/gmp/GMPVideoEncoderParent.cpp
index 416bedefbe..a423160d4c 100644
--- a/dom/media/gmp/GMPVideoEncoderParent.cpp
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -210,6 +210,12 @@ GMPVideoEncoderParent::SetPeriodicKeyFrames(bool aEnable)
   return GMPNoErr;
 }
 
+const uint32_t
+GMPVideoEncoderParent::ParentID()
+{
+  return mPlugin ? mPlugin->GetPluginId() : 0;
+}
+
 // Note: Consider keeping ActorDestroy sync'd up when making changes here.
 void
 GMPVideoEncoderParent::Shutdown()
diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h
index 0485cb43ad..610215b236 100644
--- a/dom/media/gmp/GMPVideoEncoderParent.h
+++ b/dom/media/gmp/GMPVideoEncoderParent.h
@@ -45,7 +45,7 @@ public:
   virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override;
   virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override;
   virtual GMPErr SetPeriodicKeyFrames(bool aEnable) override;
-  virtual const uint64_t ParentID() override { return reinterpret_cast(mPlugin.get()); }
+  virtual const uint32_t ParentID() override;
 
   // GMPSharedMemManager
   virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
diff --git a/dom/media/gmp/GMPVideoEncoderProxy.h b/dom/media/gmp/GMPVideoEncoderProxy.h
index 3e76e5358f..6fdd3dd099 100644
--- a/dom/media/gmp/GMPVideoEncoderProxy.h
+++ b/dom/media/gmp/GMPVideoEncoderProxy.h
@@ -46,7 +46,7 @@ public:
   virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
   virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
   virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0;
-  virtual const uint64_t ParentID() = 0;
+  virtual const uint32_t ParentID() = 0;
 
   // Call to tell GMP/plugin the consumer will no longer use this
   // interface/codec.
diff --git a/dom/media/gmp/PGMPService.ipdl b/dom/media/gmp/PGMPService.ipdl
index 80d24f7a6a..bdfc40be09 100644
--- a/dom/media/gmp/PGMPService.ipdl
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -17,7 +17,7 @@ sync protocol PGMPService
 parent:
   sync LoadGMP(nsCString nodeId, nsCString api, nsCString[] tags,
                ProcessId[] alreadyBridgedTo)
-    returns (ProcessId id, nsCString displayName, nsCString pluginId);
+    returns (ProcessId id, nsCString displayName, uint32_t pluginId);
   sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
                     bool inPrivateBrowsing)
     returns (nsCString id);
diff --git a/dom/media/mediasource/ContainerParser.cpp b/dom/media/mediasource/ContainerParser.cpp
index 7f7ba2704f..810285a8ef 100644
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -8,6 +8,7 @@
 
 #include "WebMBufferedParser.h"
 #include "mozilla/Endian.h"
+#include "mozilla/ErrorResult.h"
 #include "mp4_demuxer/MoofParser.h"
 #include "mozilla/Logging.h"
 #include "MediaData.h"
@@ -216,7 +217,7 @@ public:
     if (initSegment || !HasCompleteInitData()) {
       if (mParser.mInitEndOffset > 0) {
         MOZ_ASSERT(mParser.mInitEndOffset <= mResource->GetLength());
-        if (!mInitData->SetLength(mParser.mInitEndOffset)) {
+        if (!mInitData->SetLength(mParser.mInitEndOffset, fallible)) {
           // Super unlikely OOM
           return false;
         }
@@ -390,7 +391,7 @@ public:
       MediaByteRange& range = mParser->mInitRange;
       if (range.Length()) {
         mCompleteInitSegmentRange = range;
-        if (!mInitData->SetLength(range.Length())) {
+        if (!mInitData->SetLength(range.Length(), fallible)) {
           // Super unlikely OOM
           return false;
         }
@@ -409,8 +410,13 @@ public:
 
     mCompleteMediaHeaderRange = mParser->FirstCompleteMediaHeader();
     mCompleteMediaSegmentRange = mParser->FirstCompleteMediaSegment();
+    ErrorResult rv;
     if (HasCompleteInitData()) {
-      mResource->EvictData(mParser->mOffset, mParser->mOffset);
+      mResource->EvictData(mParser->mOffset, mParser->mOffset, rv);
+    }
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      return false;
     }
 
     if (compositionRange.IsNull()) {
diff --git a/dom/media/mediasource/ResourceQueue.cpp b/dom/media/mediasource/ResourceQueue.cpp
index a245ba7a85..ee4c69adbc 100644
--- a/dom/media/mediasource/ResourceQueue.cpp
+++ b/dom/media/mediasource/ResourceQueue.cpp
@@ -89,14 +89,15 @@ ResourceQueue::AppendItem(MediaLargeByteBuffer* aData)
 }
 
 uint32_t
-ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict)
+ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict,
+                     ErrorResult& aRv)
 {
   SBR_DEBUG("Evict(aOffset=%llu, aSizeToEvict=%u)",
             aOffset, aSizeToEvict);
-  return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict));
+  return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict), aRv);
 }
 
-uint32_t ResourceQueue::EvictBefore(uint64_t aOffset)
+uint32_t ResourceQueue::EvictBefore(uint64_t aOffset, ErrorResult& aRv)
 {
   SBR_DEBUG("EvictBefore(%llu)", aOffset);
   uint32_t evicted = 0;
@@ -111,8 +112,13 @@ uint32_t ResourceQueue::EvictBefore(uint64_t aOffset)
       mOffset += offset;
       evicted += offset;
       nsRefPtr data = new MediaLargeByteBuffer;
-      data->AppendElements(item->mData->Elements() + offset,
-                           item->mData->Length() - offset);
+      if (!data->AppendElements(item->mData->Elements() + offset,
+                                item->mData->Length() - offset,
+                                fallible)) {
+        aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+        return 0;
+      }
+
       item->mData = data;
       break;
     }
diff --git a/dom/media/mediasource/ResourceQueue.h b/dom/media/mediasource/ResourceQueue.h
index 2690a91c8a..78d60169a8 100644
--- a/dom/media/mediasource/ResourceQueue.h
+++ b/dom/media/mediasource/ResourceQueue.h
@@ -12,6 +12,8 @@
 
 namespace mozilla {
 
+class ErrorResult;
+
 // A SourceBufferResource has a queue containing the data that is appended
 // to it. The queue holds instances of ResourceItem which is an array of the
 // bytes. Appending data to the SourceBufferResource pushes this onto the
@@ -47,9 +49,10 @@ public:
 
   // Tries to evict at least aSizeToEvict from the queue up until
   // aOffset. Returns amount evicted.
-  uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict);
+  uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict,
+                 ErrorResult& aRv);
 
-  uint32_t EvictBefore(uint64_t aOffset);
+  uint32_t EvictBefore(uint64_t aOffset, ErrorResult& aRv);
 
   uint32_t EvictAll();
 
diff --git a/dom/media/mediasource/SourceBuffer.cpp b/dom/media/mediasource/SourceBuffer.cpp
index ccd760f688..0535676cb7 100644
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -552,7 +552,7 @@ SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult&
   }
 
   nsRefPtr data = new MediaLargeByteBuffer();
-  if (!data->AppendElements(aData, aLength)) {
+  if (!data->AppendElements(aData, aLength, fallible)) {
     aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
     return nullptr;
   }
diff --git a/dom/media/mediasource/SourceBufferResource.cpp b/dom/media/mediasource/SourceBufferResource.cpp
index 87e67ecfe1..04f76c2f03 100644
--- a/dom/media/mediasource/SourceBufferResource.cpp
+++ b/dom/media/mediasource/SourceBufferResource.cpp
@@ -174,12 +174,13 @@ SourceBufferResource::ReadFromCache(char* aBuffer, int64_t aOffset, uint32_t aCo
 }
 
 uint32_t
-SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold)
+SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold,
+                                ErrorResult& aRv)
 {
   SBR_DEBUG("EvictData(aPlaybackOffset=%llu,"
             "aThreshold=%u)", aPlaybackOffset, aThreshold);
   ReentrantMonitorAutoEnter mon(mMonitor);
-  uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold);
+  uint32_t result = mInputBuffer.Evict(aPlaybackOffset, aThreshold, aRv);
   if (result > 0) {
     // Wake up any waiting threads in case a ReadInternal call
     // is now invalid.
@@ -189,13 +190,13 @@ SourceBufferResource::EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold)
 }
 
 void
-SourceBufferResource::EvictBefore(uint64_t aOffset)
+SourceBufferResource::EvictBefore(uint64_t aOffset, ErrorResult& aRv)
 {
   SBR_DEBUG("EvictBefore(aOffset=%llu)", aOffset);
   ReentrantMonitorAutoEnter mon(mMonitor);
   // If aOffset is past the current playback offset we don't evict.
   if (aOffset < mOffset) {
-    mInputBuffer.EvictBefore(aOffset);
+    mInputBuffer.EvictBefore(aOffset, aRv);
   }
   // Wake up any waiting threads in case a ReadInternal call
   // is now invalid.
diff --git a/dom/media/mediasource/SourceBufferResource.h b/dom/media/mediasource/SourceBufferResource.h
index 0fe4575575..46c7e1df11 100644
--- a/dom/media/mediasource/SourceBufferResource.h
+++ b/dom/media/mediasource/SourceBufferResource.h
@@ -112,10 +112,11 @@ public:
   }
   // Remove data from resource if it holds more than the threshold
   // number of bytes. Returns amount evicted.
-  uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold);
+  uint32_t EvictData(uint64_t aPlaybackOffset, uint32_t aThreshold,
+                     ErrorResult& aRv);
 
   // Remove data from resource before the given offset.
-  void EvictBefore(uint64_t aOffset);
+  void EvictBefore(uint64_t aOffset, ErrorResult& aRv);
 
   // Remove all data from the resource
   uint32_t EvictAll();
diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp
index 67a26459b0..3cb5ac59d1 100644
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -366,8 +366,14 @@ TrackBuffer::EvictData(TimeUnit aPlaybackTime,
                   buffered.GetEnd().ToSeconds(), aPlaybackTime.ToSeconds(),
                   time, playbackOffset, decoders[i]->GetResource()->GetSize());
         if (playbackOffset > 0) {
+          ErrorResult rv;
           toEvict -= decoders[i]->GetResource()->EvictData(playbackOffset,
-                                                           playbackOffset);
+                                                           playbackOffset,
+                                                           rv);
+          if (NS_WARN_IF(rv.Failed())) {
+            rv.SuppressException();
+            return EvictDataResult::CANT_EVICT;
+          }
         }
       }
       decoders[i]->GetReader()->NotifyDataRemoved();
@@ -522,7 +528,12 @@ TrackBuffer::EvictBefore(TimeUnit aTime)
     if (endOffset > 0) {
       MSE_DEBUG("decoder=%u offset=%lld",
                 i, endOffset);
-      mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset);
+      ErrorResult rv;
+      mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset, rv);
+      if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
+        return;
+      }
       mInitializedDecoders[i]->GetReader()->NotifyDataRemoved();
     }
   }
@@ -1107,7 +1118,12 @@ TrackBuffer::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
                   buffered.GetEnd().ToSeconds(), offset,
                   decoders[i]->GetResource()->GetSize());
         if (offset > 0) {
-          decoders[i]->GetResource()->EvictData(offset, offset);
+          ErrorResult rv;
+          decoders[i]->GetResource()->EvictData(offset, offset, rv);
+          if (NS_WARN_IF(rv.Failed())) {
+            rv.SuppressException();
+            return RangeRemovalPromise::CreateAndResolve(false, __func__);
+          }
         }
       }
       decoders[i]->GetReader()->NotifyDataRemoved();
diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp
index 5c2360abcc..b4a771102a 100644
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -62,7 +62,7 @@ public:
       // These allocations might fail if content provides a huge number of
       // channels or size, but it's OK since we'll deal with the failure
       // gracefully.
-      if (mInputChannels.SetLength(mNumberOfChannels)) {
+      if (mInputChannels.SetLength(mNumberOfChannels, fallible)) {
         for (uint32_t i = 0; i < mNumberOfChannels; ++i) {
           mInputChannels[i] = new (fallible) float[mLength];
           if (!mInputChannels[i]) {
diff --git a/dom/media/webaudio/DelayBuffer.cpp b/dom/media/webaudio/DelayBuffer.cpp
index 2da0db73f3..1bf0a480ae 100644
--- a/dom/media/webaudio/DelayBuffer.cpp
+++ b/dom/media/webaudio/DelayBuffer.cpp
@@ -196,7 +196,7 @@ DelayBuffer::EnsureBuffer()
     // block size, so that no block of writes will need to wrap.
     const int chunkCount = (mMaxDelayTicks + 2 * WEBAUDIO_BLOCK_SIZE - 1) >>
                                          WEBAUDIO_BLOCK_SIZE_BITS;
-    if (!mChunks.SetLength(chunkCount)) {
+    if (!mChunks.SetLength(chunkCount, fallible)) {
       return false;
     }
 
diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp
index 39e68e9257..537b29199d 100644
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -350,7 +350,7 @@ MediaDecodeTask::FinishDecode()
   // write fewer bytes than mResampledFrames to the output buffer, in which
   // case mWriteIndex will tell us how many valid samples we have.
   bool memoryAllocationSuccess = true;
-  if (!mDecodeJob.mChannelBuffers.SetLength(channelCount)) {
+  if (!mDecodeJob.mChannelBuffers.SetLength(channelCount, fallible)) {
     memoryAllocationSuccess = false;
   } else {
     for (uint32_t i = 0; i < channelCount; ++i) {
diff --git a/dom/media/webrtc/RTCCertificate.cpp b/dom/media/webrtc/RTCCertificate.cpp
new file mode 100644
index 0000000000..93686d340f
--- /dev/null
+++ b/dom/media/webrtc/RTCCertificate.cpp
@@ -0,0 +1,443 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "mozilla/dom/RTCCertificate.h"
+
+#include 
+#include "cert.h"
+#include "jsapi.h"
+#include "mozilla/dom/CryptoKey.h"
+#include "mozilla/dom/RTCCertificateBinding.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/dom/WebCryptoTask.h"
+
+#include 
+
+namespace mozilla {
+namespace dom {
+
+#define RTCCERTIFICATE_SC_VERSION 0x00000001
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCCertificate, mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCCertificate)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCCertificate)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCCertificate)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// Note: explicit casts necessary to avoid
+//       warning C4307: '*' : integral constant overflow
+#define ONE_DAY PRTime(PR_USEC_PER_SEC) * PRTime(60) /*sec*/ \
+  * PRTime(60) /*min*/ * PRTime(24) /*hours*/
+#define EXPIRATION_DEFAULT ONE_DAY * PRTime(30)
+#define EXPIRATION_SLACK ONE_DAY
+#define EXPIRATION_MAX ONE_DAY * PRTime(365) /*year*/
+
+const size_t RTCCertificateCommonNameLength = 16;
+const size_t RTCCertificateMinRsaSize = 1024;
+
+class GenerateRTCCertificateTask : public GenerateAsymmetricKeyTask
+{
+public:
+  GenerateRTCCertificateTask(JSContext* aCx, const ObjectOrString& aAlgorithm,
+                     const Sequence& aKeyUsages)
+      : GenerateAsymmetricKeyTask(aCx, aAlgorithm, true, aKeyUsages),
+        mExpires(0),
+        mAuthType(ssl_kea_null),
+        mCertificate(nullptr),
+        mSignatureAlg(SEC_OID_UNKNOWN)
+  {
+    // Expiry is 30 days after by default.
+    // This is a sort of arbitrary range designed to be valid
+    // now with some slack in case the other side expects
+    // some before expiry.
+    //
+
+    mExpires = EXPIRATION_DEFAULT;
+    if (!aAlgorithm.IsObject()) {
+      return;
+    }
+
+    // Load the "expires" attribute from the algorithm dictionary.  This is
+    // (currently) non-standard; it exists to support testing of certificate
+    // expiration, since one month is too long to wait for a test to run.
+    JS::Rooted exp(aCx, JS::UndefinedValue());
+    JS::Rooted jsval(aCx, aAlgorithm.GetAsObject());
+    bool ok = JS_GetProperty(aCx, jsval, "expires", &exp);
+    int64_t expval;
+    if (ok) {
+      ok = JS::ToInt64(aCx, exp, &expval);
+    }
+    if (ok && expval > 0) {
+      mExpires = std::min(expval, EXPIRATION_MAX);
+    }
+  }
+
+private:
+  PRTime mExpires;
+  SSLKEAType mAuthType;
+  ScopedCERTCertificate mCertificate;
+  SECOidTag mSignatureAlg;
+
+  static CERTName* GenerateRandomName(PK11SlotInfo* aSlot)
+  {
+    uint8_t randomName[RTCCertificateCommonNameLength];
+    SECStatus rv = PK11_GenerateRandomOnSlot(aSlot, randomName,
+                                             sizeof(randomName));
+    if (rv != SECSuccess) {
+      return nullptr;
+    }
+
+    char buf[sizeof(randomName) * 2 + 4];
+    PL_strncpy(buf, "CN=", 3);
+    for (size_t i = 0; i < sizeof(randomName); ++i) {
+      PR_snprintf(&buf[i * 2 + 3], 2, "%.2x", randomName[i]);
+    }
+    buf[sizeof(buf) - 1] = '\0';
+
+    return CERT_AsciiToName(buf);
+  }
+
+  nsresult GenerateCertificate()
+  {
+    ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+    MOZ_ASSERT(slot.get());
+
+    ScopedCERTName subjectName(GenerateRandomName(slot.get()));
+    if (!subjectName) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    ScopedSECKEYPublicKey publicKey(mKeyPair.mPublicKey.get()->GetPublicKey());
+    ScopedCERTSubjectPublicKeyInfo spki(
+        SECKEY_CreateSubjectPublicKeyInfo(publicKey));
+    if (!spki) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    ScopedCERTCertificateRequest certreq(
+        CERT_CreateCertificateRequest(subjectName, spki, nullptr));
+    if (!certreq) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    PRTime now = PR_Now();
+    PRTime notBefore = now - EXPIRATION_SLACK;
+    mExpires += now;
+
+    ScopedCERTValidity validity(CERT_CreateValidity(notBefore, mExpires));
+    if (!validity) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    unsigned long serial;
+    // Note: This serial in principle could collide, but it's unlikely, and we
+    // don't expect anyone to be validating certificates anyway.
+    SECStatus rv =
+        PK11_GenerateRandomOnSlot(slot,
+                                  reinterpret_cast(&serial),
+                                  sizeof(serial));
+    if (rv != SECSuccess) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    CERTCertificate* cert = CERT_CreateCertificate(serial, subjectName,
+                                                   validity, certreq);
+    if (!cert) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+    mCertificate.reset(cert);
+    return NS_OK;
+  }
+
+  nsresult SignCertificate()
+  {
+    MOZ_ASSERT(mSignatureAlg != SEC_OID_UNKNOWN);
+    PLArenaPool *arena = mCertificate->arena;
+
+    SECStatus rv = SECOID_SetAlgorithmID(arena, &mCertificate->signature,
+                                         mSignatureAlg, nullptr);
+    if (rv != SECSuccess) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    // Set version to X509v3.
+    *(mCertificate->version.data) = SEC_CERTIFICATE_VERSION_3;
+    mCertificate->version.len = 1;
+
+    SECItem innerDER = { siBuffer, nullptr, 0 };
+    if (!SEC_ASN1EncodeItem(arena, &innerDER, mCertificate,
+                            SEC_ASN1_GET(CERT_CertificateTemplate))) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    SECItem *signedCert = PORT_ArenaZNew(arena, SECItem);
+    if (!signedCert) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+
+    ScopedSECKEYPrivateKey privateKey(mKeyPair.mPrivateKey.get()->GetPrivateKey());
+    rv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len,
+                         privateKey, mSignatureAlg);
+    if (rv != SECSuccess) {
+      return NS_ERROR_DOM_UNKNOWN_ERR;
+    }
+    mCertificate->derCert = *signedCert;
+    return NS_OK;
+  }
+
+  nsresult BeforeCrypto() override
+  {
+    if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+      // Double check that size is OK.
+      auto sz = static_cast(mRsaParams.keySizeInBits);
+      if (sz < RTCCertificateMinRsaSize) {
+        return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+      }
+
+      mSignatureAlg = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
+      mAuthType = ssl_kea_rsa;
+
+    } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+      // We only support good curves in WebCrypto.
+      // If that ever changes, check that a good one was chosen.
+
+      mSignatureAlg = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE;
+      mAuthType = ssl_kea_ecdh;
+    } else {
+      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+    }
+    return NS_OK;
+  }
+
+  nsresult DoCrypto() override
+  {
+    nsresult rv = GenerateAsymmetricKeyTask::DoCrypto();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = GenerateCertificate();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = SignCertificate();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+  virtual void Resolve() override
+  {
+    // Make copies of the private key and certificate, otherwise, when this
+    // object is deleted, the structures they reference will be deleted too.
+    SECKEYPrivateKey* key = mKeyPair.mPrivateKey.get()->GetPrivateKey();
+    CERTCertificate* cert = CERT_DupCertificate(mCertificate);
+    nsRefPtr result =
+        new RTCCertificate(mResultPromise->GetParentObject(),
+                           key, cert, mAuthType, mExpires);
+    mResultPromise->MaybeResolve(result);
+  }
+};
+
+already_AddRefed
+RTCCertificate::GenerateCertificate(
+    const GlobalObject& aGlobal, const ObjectOrString& aKeygenAlgorithm,
+    ErrorResult& aRv, JSCompartment* aCompartment)
+{
+  nsIGlobalObject* global = xpc::NativeGlobal(aGlobal.Get());
+  nsRefPtr p = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+  Sequence usages;
+  if (!usages.AppendElement(NS_LITERAL_STRING("sign"), fallible)) {
+    return nullptr;
+  }
+  nsRefPtr task =
+      new GenerateRTCCertificateTask(aGlobal.Context(),
+                                     aKeygenAlgorithm, usages);
+  task->DispatchWithPromise(p);
+  return p.forget();
+}
+
+RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal)
+    : mGlobal(aGlobal),
+      mPrivateKey(nullptr),
+      mCertificate(nullptr),
+      mAuthType(ssl_kea_null),
+      mExpires(0)
+{
+}
+
+RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal,
+                               SECKEYPrivateKey* aPrivateKey,
+                               CERTCertificate* aCertificate,
+                               SSLKEAType aAuthType,
+                               PRTime aExpires)
+    : mGlobal(aGlobal),
+      mPrivateKey(aPrivateKey),
+      mCertificate(aCertificate),
+      mAuthType(aAuthType),
+      mExpires(aExpires)
+{
+}
+
+RTCCertificate::~RTCCertificate()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+  destructorSafeDestroyNSSReference();
+  shutdown(calledFromObject);
+}
+
+// This creates some interesting lifecycle consequences, since the DtlsIdentity
+// holds NSS objects, but does not implement nsNSSShutDownObject.
+
+// Unfortunately, the code that uses DtlsIdentity cannot always use that lock
+// due to external linkage requirements.  Therefore, the lock is held on this
+// object instead.  Consequently, the DtlsIdentity that this method returns must
+// have a lifetime that is strictly shorter than the RTCCertificate.
+//
+// RTCPeerConnection provides this guarantee by holding a strong reference to
+// the RTCCertificate.  It will cleanup any DtlsIdentity instances that it
+// creates before the RTCCertificate reference is released.
+RefPtr
+RTCCertificate::CreateDtlsIdentity() const
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown() || !mPrivateKey || !mCertificate) {
+    return nullptr;
+  }
+  SECKEYPrivateKey* key = SECKEY_CopyPrivateKey(mPrivateKey);
+  CERTCertificate* cert = CERT_DupCertificate(mCertificate);
+  RefPtr id = new DtlsIdentity(key, cert, mAuthType);
+  return id;
+}
+
+JSObject*
+RTCCertificate::WrapObject(JSContext* aCx, JS::Handle aGivenProto)
+{
+  return RTCCertificateBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+RTCCertificate::virtualDestroyNSSReference()
+{
+  destructorSafeDestroyNSSReference();
+}
+
+void
+RTCCertificate::destructorSafeDestroyNSSReference()
+{
+  mPrivateKey.dispose();
+  mCertificate.dispose();
+}
+
+bool
+RTCCertificate::WritePrivateKey(JSStructuredCloneWriter* aWriter,
+                                const nsNSSShutDownPreventionLock& aLockProof) const
+{
+  JsonWebKey jwk;
+  nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey, jwk, aLockProof);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  nsString json;
+  if (!jwk.ToJSON(json)) {
+    return false;
+  }
+  return WriteString(aWriter, json);
+}
+
+bool
+RTCCertificate::WriteCertificate(JSStructuredCloneWriter* aWriter,
+                                 const nsNSSShutDownPreventionLock& /*proof*/) const
+{
+  ScopedCERTCertificateList certs(CERT_CertListFromCert(mCertificate.get()));
+  if (!certs || certs->len <= 0) {
+    return false;
+  }
+  if (!JS_WriteUint32Pair(aWriter, certs->certs[0].len, 0)) {
+    return false;
+  }
+  return JS_WriteBytes(aWriter, certs->certs[0].data, certs->certs[0].len);
+}
+
+bool
+RTCCertificate::WriteStructuredClone(JSStructuredCloneWriter* aWriter) const
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown() || !mPrivateKey || !mCertificate) {
+    return false;
+  }
+
+  return JS_WriteUint32Pair(aWriter, RTCCERTIFICATE_SC_VERSION, mAuthType) &&
+      JS_WriteUint32Pair(aWriter, (mExpires >> 32) & 0xffffffff,
+                         mExpires & 0xffffffff) &&
+      WritePrivateKey(aWriter, locker) &&
+      WriteCertificate(aWriter, locker);
+}
+
+bool
+RTCCertificate::ReadPrivateKey(JSStructuredCloneReader* aReader,
+                               const nsNSSShutDownPreventionLock& aLockProof)
+{
+  nsString json;
+  if (!ReadString(aReader, json)) {
+    return false;
+  }
+  JsonWebKey jwk;
+  if (!jwk.Init(json)) {
+    return false;
+  }
+  mPrivateKey = CryptoKey::PrivateKeyFromJwk(jwk, aLockProof);
+  return !!mPrivateKey;
+}
+
+bool
+RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader,
+                                const nsNSSShutDownPreventionLock& /*proof*/)
+{
+  CryptoBuffer cert;
+  if (!ReadBuffer(aReader, cert) || cert.Length() == 0) {
+    return false;
+  }
+
+  SECItem der = { siBuffer, cert.Elements(),
+                  static_cast(cert.Length()) };
+  mCertificate = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
+                                         &der, nullptr, true, true);
+  return !!mCertificate;
+}
+
+bool
+RTCCertificate::ReadStructuredClone(JSStructuredCloneReader* aReader)
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return false;
+  }
+
+  uint32_t version, authType;
+  if (!JS_ReadUint32Pair(aReader, &version, &authType) ||
+      version != RTCCERTIFICATE_SC_VERSION) {
+    return false;
+  }
+  mAuthType = static_cast(authType);
+
+  uint32_t high, low;
+  if (!JS_ReadUint32Pair(aReader, &high, &low)) {
+    return false;
+  }
+  mExpires = static_cast(high) << 32 | low;
+
+  return ReadPrivateKey(aReader, locker) &&
+      ReadCertificate(aReader, locker);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/webrtc/RTCCertificate.h b/dom/media/webrtc/RTCCertificate.h
new file mode 100644
index 0000000000..558f02beb8
--- /dev/null
+++ b/dom/media/webrtc/RTCCertificate.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_dom_RTCCertificate_h
+#define mozilla_dom_RTCCertificate_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsIGlobalObject.h"
+#include "nsNSSShutDown.h"
+#include "prtime.h"
+#include "sslt.h"
+#include "ScopedNSSTypes.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Date.h"
+#include "mozilla/dom/CryptoKey.h"
+#include "mtransport/dtlsidentity.h"
+#include "js/Date.h"
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace dom {
+
+class ObjectOrString;
+
+class RTCCertificate final
+    : public nsISupports,
+      public nsWrapperCache,
+      public nsNSSShutDownObject
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCCertificate)
+
+  // WebIDL method that implements RTCPeerConnection.generateCertificate.
+  static already_AddRefed GenerateCertificate(
+      const GlobalObject& global, const ObjectOrString& keygenAlgorithm,
+      ErrorResult& aRv, JSCompartment* aCompartment = nullptr);
+
+  explicit RTCCertificate(nsIGlobalObject* aGlobal);
+  RTCCertificate(nsIGlobalObject* aGlobal, SECKEYPrivateKey* aPrivateKey,
+                 CERTCertificate* aCertificate, SSLKEAType aAuthType,
+                 PRTime aExpires);
+
+  nsIGlobalObject* GetParentObject() const { return mGlobal; }
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle aGivenProto) override;
+
+  // WebIDL expires attribute.  Note: JS dates are milliseconds since epoch;
+  // NSPR PRTime is in microseconds since the same epoch.
+  JS::ClippedTime Expires() const
+  {
+    return JS::TimeClip(mExpires / PR_USEC_PER_MSEC);
+  }
+
+  // Accessors for use by PeerConnectionImpl.
+  RefPtr CreateDtlsIdentity() const;
+  CERTCertificate* Certificate() const { return mCertificate; }
+
+  // For nsNSSShutDownObject
+  virtual void virtualDestroyNSSReference() override;
+  void destructorSafeDestroyNSSReference();
+
+  // Structured clone methods
+  bool WriteStructuredClone(JSStructuredCloneWriter* aWriter) const;
+  bool ReadStructuredClone(JSStructuredCloneReader* aReader);
+
+private:
+  ~RTCCertificate();
+  void operator=(const RTCCertificate&) = delete;
+  RTCCertificate(const RTCCertificate&) = delete;
+
+  bool ReadCertificate(JSStructuredCloneReader* aReader,
+                       const nsNSSShutDownPreventionLock& /*lockproof*/);
+  bool ReadPrivateKey(JSStructuredCloneReader* aReader,
+                      const nsNSSShutDownPreventionLock& aLockProof);
+  bool WriteCertificate(JSStructuredCloneWriter* aWriter,
+                        const nsNSSShutDownPreventionLock& /*lockproof*/) const;
+  bool WritePrivateKey(JSStructuredCloneWriter* aWriter,
+                       const nsNSSShutDownPreventionLock& aLockProof) const;
+
+  nsRefPtr mGlobal;
+  ScopedSECKEYPrivateKey mPrivateKey;
+  ScopedCERTCertificate mCertificate;
+  SSLKEAType mAuthType;
+  PRTime mExpires;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_RTCCertificate_h
diff --git a/dom/media/webrtc/moz.build b/dom/media/webrtc/moz.build
index 9af1293db6..6905e84db7 100644
--- a/dom/media/webrtc/moz.build
+++ b/dom/media/webrtc/moz.build
@@ -23,6 +23,7 @@ if CONFIG['MOZ_WEBRTC']:
         'MediaEngineWebRTCAudio.cpp',
         'MediaEngineWebRTCVideo.cpp',
         'MediaTrackConstraints.cpp',
+	'RTCCertificate.cpp',
         'RTCIdentityProviderRegistrar.cpp',
     ]
     # MediaEngineWebRTC.cpp needs to be built separately.
diff --git a/dom/mobilemessage/MobileMessageManager.cpp b/dom/mobilemessage/MobileMessageManager.cpp
index 779b084db7..c8b97eb9f0 100644
--- a/dom/mobilemessage/MobileMessageManager.cpp
+++ b/dom/mobilemessage/MobileMessageManager.cpp
@@ -355,7 +355,7 @@ MobileMessageManager::Delete(const Sequence idArray;
-  if (!idArray.SetLength(size)) {
+  if (!idArray.SetLength(size, fallible)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
@@ -598,13 +598,19 @@ MobileMessageManager::DispatchTrustedDeletedEventToSelf(nsISupports* aDeletedInf
     uint32_t msgIdLength = info->GetData().deletedMessageIds().Length();
     if (msgIdLength) {
       Sequence& deletedMsgIds = init.mDeletedMessageIds.SetValue();
-      deletedMsgIds.AppendElements(info->GetData().deletedMessageIds());
+      if (!deletedMsgIds.AppendElements(info->GetData().deletedMessageIds(),
+                                        fallible)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
     }
 
     uint32_t threadIdLength = info->GetData().deletedThreadIds().Length();
     if (threadIdLength) {
       Sequence& deletedThreadIds = init.mDeletedThreadIds.SetValue();
-      deletedThreadIds.AppendElements(info->GetData().deletedThreadIds());
+      if (!deletedThreadIds.AppendElements(info->GetData().deletedThreadIds(),
+                                           fallible)) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
     }
 
     nsRefPtr event =
diff --git a/dom/plugins/base/PluginPRLibrary.h b/dom/plugins/base/PluginPRLibrary.h
index a8722bffab..c92ef83106 100644
--- a/dom/plugins/base/PluginPRLibrary.h
+++ b/dom/plugins/base/PluginPRLibrary.h
@@ -122,6 +122,8 @@ public:
     virtual nsresult EndUpdateBackground(NPP instance,
                                          gfxContext* aCtx, const nsIntRect&) override;
     virtual void GetLibraryPath(nsACString& aPath) { aPath.Assign(mFilePath); }
+    virtual nsresult GetRunID(uint32_t* aRunID) override { return NS_ERROR_NOT_IMPLEMENTED; }
+    virtual void SetHasLocalInstance() override { }
 
 private:
     NP_InitializeFunc mNP_Initialize;
diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp
index e3e4242a1f..4b8c1fd86c 100644
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -1778,3 +1778,22 @@ nsNPAPIPluginInstance::GetContentsScaleFactor()
   }
   return scaleFactor;
 }
+
+nsresult
+nsNPAPIPluginInstance::GetRunID(uint32_t* aRunID)
+{
+  if (NS_WARN_IF(!aRunID)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  if (NS_WARN_IF(!mPlugin)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PluginLibrary* library = mPlugin->GetLibrary();
+  if (!library) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return library->GetRunID(aRunID);
+}
diff --git a/dom/plugins/base/nsNPAPIPluginInstance.h b/dom/plugins/base/nsNPAPIPluginInstance.h
index 636039eb5a..3e3e9f824b 100644
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -287,6 +287,8 @@ public:
   // Returns the contents scale factor of the screen the plugin is drawn on.
   double GetContentsScaleFactor();
 
+  nsresult GetRunID(uint32_t *aRunID);
+
   static bool InPluginCallUnsafeForReentry() { return gInUnsafePluginCalls > 0; }
   static void BeginPluginCall(NSPluginCallReentry aReentryState)
   {
diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp
index 5fea2f74aa..7565278ee6 100644
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -3769,6 +3769,7 @@ nsPluginHost::PluginCrashed(nsNPAPIPlugin* aPlugin,
                             const nsAString& browserDumpID)
 {
   nsPluginTag* crashedPluginTag = TagForPlugin(aPlugin);
+  MOZ_ASSERT(crashedPluginTag);
 
   // Notify the app's observer that a plugin crashed so it can submit
   // a crashreport.
@@ -3778,6 +3779,18 @@ nsPluginHost::PluginCrashed(nsNPAPIPlugin* aPlugin,
   nsCOMPtr propbag =
     do_CreateInstance("@mozilla.org/hash-property-bag;1");
   if (obsService && propbag) {
+    uint32_t runID = 0;
+    PluginLibrary* library = aPlugin->GetLibrary();
+
+    if (!NS_WARN_IF(!library)) {
+      library->GetRunID(&runID);
+    }
+    propbag->SetPropertyAsUint32(NS_LITERAL_STRING("runID"), runID);
+
+    nsCString pluginName;
+    crashedPluginTag->GetName(pluginName);
+    propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginName"),
+                                   NS_ConvertUTF8toUTF16(pluginName));
     propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"),
                                   pluginDumpID);
     propbag->SetPropertyAsAString(NS_LITERAL_STRING("browserDumpID"),
diff --git a/dom/plugins/base/nsPluginTags.cpp b/dom/plugins/base/nsPluginTags.cpp
index 95a5972439..759b5393cd 100644
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -147,6 +147,7 @@ nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
                          bool fromExtension)
   : mId(sNextId++),
     mContentProcessRunningCount(0),
+    mHadLocalInstance(false),
     mName(aPluginInfo->fName),
     mDescription(aPluginInfo->fDescription),
     mLibrary(nullptr),
diff --git a/dom/plugins/base/nsPluginTags.h b/dom/plugins/base/nsPluginTags.h
index 8099f7fa0f..ae082572b8 100644
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -92,6 +92,10 @@ public:
 
   // Number of PluginModuleParents living in all content processes.
   size_t        mContentProcessRunningCount;
+
+  // True if we've ever created an instance of this plugin in the current process.
+  bool          mHadLocalInstance;
+
   nsCString     mName; // UTF-8
   nsCString     mDescription; // UTF-8
   nsTArray mMimeTypes; // UTF-8
diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl
index 94d093d52d..c9973bd30f 100644
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -93,8 +93,7 @@ child:
                       nsCString[] aThreadNameFilters);
   async StopProfiler();
 
-  intr GetProfile()
-    returns (nsCString aProfile);
+  async GatherProfile();
 
   async SettingChanged(PluginSettings settings);
 
@@ -141,6 +140,8 @@ parent:
   // process was destroyed. The chrome process may choose to asynchronously shut
   // down the plugin process in response.
   async NotifyContentModuleDestroyed();
+
+  async Profile(nsCString aProfile);
 };
 
 } // namespace plugins
diff --git a/dom/plugins/ipc/PluginBridge.h b/dom/plugins/ipc/PluginBridge.h
index e81b97f886..df1248c596 100644
--- a/dom/plugins/ipc/PluginBridge.h
+++ b/dom/plugins/ipc/PluginBridge.h
@@ -17,7 +17,7 @@ namespace plugins {
 
 bool
 SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent,
-            bool aForceBridgeNow, nsresult* rv);
+            bool aForceBridgeNow, nsresult* aResult, uint32_t* aRunID);
 
 bool
 FindPluginsForContent(uint32_t aPluginEpoch,
@@ -25,7 +25,7 @@ FindPluginsForContent(uint32_t aPluginEpoch,
                       uint32_t* aNewPluginEpoch);
 
 void
-TerminatePlugin(uint32_t aPluginId);
+TerminatePlugin(uint32_t aPluginId, const nsString& aBrowserDumpId);
 
 } // namespace plugins
 } // namespace mozilla
diff --git a/dom/plugins/ipc/PluginHangUIParent.cpp b/dom/plugins/ipc/PluginHangUIParent.cpp
index 811997eb9f..4ab5f96d5a 100644
--- a/dom/plugins/ipc/PluginHangUIParent.cpp
+++ b/dom/plugins/ipc/PluginHangUIParent.cpp
@@ -353,7 +353,7 @@ PluginHangUIParent::RecvUserResponse(const unsigned int& aResponse)
   int responseCode;
   if (aResponse & HANGUI_USER_RESPONSE_STOP) {
     // User clicked Stop
-    mModule->TerminateChildProcess(mMainThreadMessageLoop);
+    mModule->TerminateChildProcess(mMainThreadMessageLoop, EmptyString());
     responseCode = 1;
   } else if(aResponse & HANGUI_USER_RESPONSE_CONTINUE) {
     mModule->OnHangUIContinue();
diff --git a/dom/plugins/ipc/PluginLibrary.h b/dom/plugins/ipc/PluginLibrary.h
index d6f40db7b3..b91af7ab8d 100644
--- a/dom/plugins/ipc/PluginLibrary.h
+++ b/dom/plugins/ipc/PluginLibrary.h
@@ -84,6 +84,8 @@ public:
                                          const nsIntRect&, gfxContext**) = 0;
   virtual nsresult EndUpdateBackground(NPP instance,
                                        gfxContext*, const nsIntRect&) = 0;
+  virtual nsresult GetRunID(uint32_t* aRunID) = 0;
+  virtual void SetHasLocalInstance() = 0;
 };
 
 
diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp
index c782cacf44..12f5272eaf 100644
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -37,6 +37,7 @@
 #include "mozilla/plugins/StreamNotifyChild.h"
 #include "mozilla/plugins/BrowserStreamChild.h"
 #include "mozilla/plugins/PluginStreamChild.h"
+#include "mozilla/unused.h"
 
 #include "nsNPAPIPlugin.h"
 
@@ -2508,13 +2509,16 @@ PluginModuleChild::RecvStopProfiler()
 }
 
 bool
-PluginModuleChild::AnswerGetProfile(nsCString* aProfile)
+PluginModuleChild::RecvGatherProfile()
 {
+    nsCString profileCString;
     UniquePtr profile = profiler_get_profile();
     if (profile != nullptr) {
-        *aProfile = nsCString(profile.get(), strlen(profile.get()));
+        profileCString = nsCString(profile.get(), strlen(profile.get()));
     } else {
-        *aProfile = nsCString("", 0);
+        profileCString = nsCString("", 0);
     }
+
+    unused << SendProfile(profileCString);
     return true;
 }
diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h
index dd11923434..06cf7205f7 100644
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -135,7 +135,7 @@ class PluginModuleChild : public PPluginModuleChild
                                    nsTArray&& aFeatures,
                                    nsTArray&& aThreadNameFilters) override;
     virtual bool RecvStopProfiler() override;
-    virtual bool AnswerGetProfile(nsCString* aProfile) override;
+    virtual bool RecvGatherProfile() override;
 
 public:
     explicit PluginModuleChild(bool aIsChrome);
diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp
index 6b746c0871..5f86f66d2b 100755
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -14,12 +14,16 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/plugins/BrowserStreamParent.h"
 #include "mozilla/plugins/PluginAsyncSurrogate.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/Preferences.h"
+#ifdef MOZ_ENABLE_PROFILER_SPS
+#include "mozilla/ProfileGatherer.h"
+#endif
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
@@ -51,7 +55,11 @@
 using base::KillProcess;
 
 using mozilla::PluginLibrary;
+#ifdef MOZ_ENABLE_PROFILER_SPS
+using mozilla::ProfileGatherer;
+#endif
 using mozilla::ipc::MessageChannel;
+using mozilla::ipc::GeckoChildProcessHost;
 
 using namespace mozilla;
 using namespace mozilla::plugins;
@@ -82,8 +90,13 @@ bool
 mozilla::plugins::SetupBridge(uint32_t aPluginId,
                               dom::ContentParent* aContentParent,
                               bool aForceBridgeNow,
-                              nsresult* rv)
+                              nsresult* rv,
+                              uint32_t* runID)
 {
+    if (NS_WARN_IF(!rv) || NS_WARN_IF(!runID)) {
+        return false;
+    }
+
     PluginModuleChromeParent::ClearInstantiationFlag();
     nsRefPtr host = nsPluginHost::GetInst();
     nsRefPtr plugin;
@@ -92,6 +105,10 @@ mozilla::plugins::SetupBridge(uint32_t aPluginId,
         return true;
     }
     PluginModuleChromeParent* chromeParent = static_cast(plugin->GetLibrary());
+    *rv = chromeParent->GetRunID(runID);
+    if (NS_FAILED(*rv)) {
+        return true;
+    }
     chromeParent->SetContentParent(aContentParent);
     if (!aForceBridgeNow && chromeParent->IsStartingAsync() &&
         PluginModuleChromeParent::DidInstantiate()) {
@@ -319,7 +336,7 @@ bool PluginModuleMapping::sIsLoadModuleOnStack = false;
 } // anonymous namespace
 
 void
-mozilla::plugins::TerminatePlugin(uint32_t aPluginId)
+mozilla::plugins::TerminatePlugin(uint32_t aPluginId, const nsString& aBrowserDumpId)
 {
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
@@ -328,10 +345,9 @@ mozilla::plugins::TerminatePlugin(uint32_t aPluginId)
     if (!pluginTag || !pluginTag->mPlugin) {
         return;
     }
-
     nsRefPtr plugin = pluginTag->mPlugin;
     PluginModuleChromeParent* chromeParent = static_cast(plugin->GetLibrary());
-    chromeParent->TerminateChildProcess(MessageLoop::current());
+    chromeParent->TerminateChildProcess(MessageLoop::current(), aBrowserDumpId);
 }
 
 /* static */ PluginLibrary*
@@ -351,7 +367,8 @@ PluginModuleContentParent::LoadModule(uint32_t aPluginId)
      */
     dom::ContentChild* cp = dom::ContentChild::GetSingleton();
     nsresult rv;
-    if (!cp->SendLoadPlugin(aPluginId, &rv) ||
+    uint32_t runID;
+    if (!cp->SendLoadPlugin(aPluginId, &rv, &runID) ||
         NS_FAILED(rv)) {
         return nullptr;
     }
@@ -367,6 +384,7 @@ PluginModuleContentParent::LoadModule(uint32_t aPluginId)
     }
 
     parent->mPluginId = aPluginId;
+    parent->mRunID = runID;
 
     return parent;
 }
@@ -454,7 +472,8 @@ PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId,
     }
 #endif
 
-    nsAutoPtr parent(new PluginModuleChromeParent(aFilePath, aPluginId));
+    nsAutoPtr parent(new PluginModuleChromeParent(aFilePath, aPluginId,
+                                                                            sandboxLevel));
     UniquePtr onLaunchedRunnable(new LaunchedTask(parent));
     parent->mSubprocess->SetCallRunnableImmediately(!parent->mIsStartingAsync);
     TimeStamp launchStart = TimeStamp::Now();
@@ -512,9 +531,11 @@ PluginModuleChromeParent::OnProcessLaunched(const bool aSucceeded)
     Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this);
 #endif
 
-#ifdef XP_WIN
+#if defined(XP_WIN) && defined(_X86_)
+    // Protected mode only applies to Windows and only to x86.
     if (!mIsBlocklisted && mIsFlashPlugin &&
-        Preferences::GetBool("dom.ipc.plugins.flash.disable-protected-mode", false)) {
+        (Preferences::GetBool("dom.ipc.plugins.flash.disable-protected-mode", false) ||
+         mSandboxLevel >= 2)) {
         SendDisableFlashProtectedMode();
     }
 #endif
@@ -561,6 +582,7 @@ PluginModuleChromeParent::WaitForIPCConnection()
 PluginModuleParent::PluginModuleParent(bool aIsChrome)
     : mIsChrome(aIsChrome)
     , mShutdown(false)
+    , mHadLocalInstance(false)
     , mClearSiteDataSupported(false)
     , mGetSitesWithDataSupported(false)
     , mNPNIface(nullptr)
@@ -570,6 +592,7 @@ PluginModuleParent::PluginModuleParent(bool aIsChrome)
     , mIsFlashPlugin(false)
     , mIsStartingAsync(false)
     , mNPInitialized(false)
+    , mIsNPShutdownPending(false)
     , mAsyncNewRv(NS_ERROR_NOT_INITIALIZED)
 {
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK)
@@ -603,7 +626,9 @@ PluginModuleContentParent::~PluginModuleContentParent()
 
 bool PluginModuleChromeParent::sInstantiated = false;
 
-PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId)
+PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath,
+                                                   uint32_t aPluginId,
+                                                   int32_t aSandboxLevel)
     : PluginModuleParent(true)
     , mSubprocess(new PluginProcessParent(aFilePath))
     , mPluginId(aPluginId)
@@ -615,6 +640,7 @@ PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32
     , mHangUIParent(nullptr)
     , mHangUIEnabled(true)
     , mIsTimerReset(true)
+    , mSandboxLevel(aSandboxLevel)
 #ifdef MOZ_CRASHREPORTER
     , mCrashReporterMutex("PluginModuleChromeParent::mCrashReporterMutex")
     , mCrashReporter(nullptr)
@@ -632,6 +658,7 @@ PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32
 {
     NS_ASSERTION(mSubprocess, "Out of memory!");
     sInstantiated = true;
+    mRunID = GeckoChildProcessHost::GetUniqueID();
 
     RegisterSettingsCallbacks();
 
@@ -1016,7 +1043,7 @@ PluginModuleChromeParent::ShouldContinueFromReplyTimeout()
     // original plugin hang behaviour and kill the plugin container.
     FinishHangUI();
 #endif // XP_WIN
-    TerminateChildProcess(MessageLoop::current());
+    TerminateChildProcess(MessageLoop::current(), EmptyString());
     GetIPCChannel()->CloseWithTimeout();
     return false;
 }
@@ -1039,7 +1066,8 @@ PluginModuleContentParent::OnExitedSyncSend()
 }
 
 void
-PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop)
+PluginModuleChromeParent::TerminateChildProcess(MessageLoop* aMsgLoop,
+                                                const nsAString& aBrowserDumpId)
 {
     mozilla::ipc::ScopedProcessHandle geckoChildProcess;
     bool childOpened = base::OpenProcessHandle(OtherPid(),
@@ -1213,6 +1241,16 @@ PluginModuleParent::ActorDestroy(ActorDestroyReason why)
     }
 }
 
+nsresult
+PluginModuleParent::GetRunID(uint32_t* aRunID)
+{
+    if (NS_WARN_IF(!aRunID)) {
+      return NS_ERROR_INVALID_POINTER;
+    }
+    *aRunID = mRunID;
+    return NS_OK;
+}
+
 void
 PluginModuleChromeParent::ActorDestroy(ActorDestroyReason why)
 {
@@ -1957,6 +1995,13 @@ PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError)
 void
 PluginModuleParent::InitAsyncSurrogates()
 {
+    if (MaybeRunDeferredShutdown()) {
+        // We've shut down, so the surrogates are no longer valid. Clear
+        // mSurrogateInstances to ensure that these aren't used.
+        mSurrogateInstances.Clear();
+        return;
+    }
+
     uint32_t len = mSurrogateInstances.Length();
     for (uint32_t i = 0; i < len; ++i) {
         NPError err;
@@ -1976,6 +2021,21 @@ PluginModuleParent::RemovePendingSurrogate(
     return mSurrogateInstances.RemoveElement(aSurrogate);
 }
 
+bool
+PluginModuleParent::MaybeRunDeferredShutdown()
+{
+    if (!mIsStartingAsync || !mIsNPShutdownPending) {
+        return false;
+    }
+    MOZ_ASSERT(!mShutdown);
+    NPError error;
+    if (!DoShutdown(&error)) {
+        return false;
+    }
+    mIsNPShutdownPending = false;
+    return true;
+}
+
 nsresult
 PluginModuleParent::NP_Shutdown(NPError* error)
 {
@@ -1986,6 +2046,24 @@ PluginModuleParent::NP_Shutdown(NPError* error)
         return NS_ERROR_FAILURE;
     }
 
+    /* If we're still running an async NP_Initialize then we need to defer
+       shutdown until we've received the result of the NP_Initialize call. */
+    if (mIsStartingAsync && !mNPInitialized) {
+        mIsNPShutdownPending = true;
+        *error = NPERR_NO_ERROR;
+        return NS_OK;
+    }
+
+    if (!DoShutdown(error)) {
+        return NS_ERROR_FAILURE;
+    }
+
+    return NS_OK;
+}
+
+bool
+PluginModuleParent::DoShutdown(NPError* error)
+{
     bool ok = true;
     if (IsChrome()) {
         ok = CallNP_Shutdown(error);
@@ -1997,7 +2075,11 @@ PluginModuleParent::NP_Shutdown(NPError* error)
     // CallNP_Shutdown() message
     Close();
 
-    return ok ? NS_OK : NS_ERROR_FAILURE;
+    mShutdown = ok;
+    if (!ok) {
+        *error = NPERR_GENERIC_ERROR;
+    }
+    return ok;
 }
 
 nsresult
@@ -2581,13 +2663,13 @@ public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
 
-    explicit PluginProfilerObserver(PluginModuleParent* pmp)
+    explicit PluginProfilerObserver(PluginModuleChromeParent* pmp)
       : mPmp(pmp)
     {}
 
 private:
     ~PluginProfilerObserver() {}
-    PluginModuleParent* mPmp;
+    PluginModuleChromeParent* mPmp;
 };
 
 NS_IMPL_ISUPPORTS(PluginProfilerObserver, nsIObserver, nsISupportsWeakReference)
@@ -2608,15 +2690,12 @@ PluginProfilerObserver::Observe(nsISupports *aSubject,
         unused << mPmp->SendStartProfiler(entries, interval, features, threadFilterNames);
     } else if (!strcmp(aTopic, "profiler-stopped")) {
         unused << mPmp->SendStopProfiler();
+    } else if (!strcmp(aTopic, "profiler-subprocess-gather")) {
+        nsRefPtr gatherer = static_cast(aSubject);
+        mPmp->GatherAsyncProfile(gatherer);
     } else if (!strcmp(aTopic, "profiler-subprocess")) {
         nsCOMPtr pse = do_QueryInterface(aSubject);
-        if (pse) {
-            nsCString result;
-            bool success = mPmp->CallGetProfile(&result);
-            if (success && !result.IsEmpty()) {
-                pse->AddSubProfile(result.get());
-            }
-        }
+        mPmp->GatheredAsyncProfile(pse);
     }
     return NS_OK;
 }
@@ -2629,6 +2708,7 @@ PluginModuleChromeParent::InitPluginProfiling()
         mProfilerObserver = new PluginProfilerObserver(this);
         observerService->AddObserver(mProfilerObserver, "profiler-started", false);
         observerService->AddObserver(mProfilerObserver, "profiler-stopped", false);
+        observerService->AddObserver(mProfilerObserver, "profiler-subprocess-gather", false);
         observerService->AddObserver(mProfilerObserver, "profiler-subprocess", false);
     }
 }
@@ -2640,8 +2720,41 @@ PluginModuleChromeParent::ShutdownPluginProfiling()
     if (observerService) {
         observerService->RemoveObserver(mProfilerObserver, "profiler-started");
         observerService->RemoveObserver(mProfilerObserver, "profiler-stopped");
+        observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess-gather");
         observerService->RemoveObserver(mProfilerObserver, "profiler-subprocess");
     }
 }
-#endif
+
+void
+PluginModuleChromeParent::GatherAsyncProfile(ProfileGatherer* aGatherer)
+{
+    mGatherer = aGatherer;
+    mGatherer->WillGatherOOPProfile();
+    unused << SendGatherProfile();
+}
+
+void
+PluginModuleChromeParent::GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent)
+{
+    if (aSaveEvent && !mProfile.IsEmpty()) {
+        aSaveEvent->AddSubProfile(mProfile.get());
+        mProfile.Truncate();
+    }
+}
+#endif // MOZ_ENABLE_PROFILER_SPS
+
+bool
+PluginModuleChromeParent::RecvProfile(const nsCString& aProfile)
+{
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    if (NS_WARN_IF(!mGatherer)) {
+        return true;
+    }
+
+    mProfile = aProfile;
+    mGatherer->GatheredOOPProfile();
+    mGatherer = nullptr;
+#endif
+    return true;
+}
 
diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h
index 482ad1925d..cd4d632644 100644
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -27,9 +27,13 @@
 #include "nsWindowsHelpers.h"
 #endif
 
+class nsIProfileSaveEvent;
 class nsPluginTag;
 
 namespace mozilla {
+#ifdef MOZ_ENABLE_PROFILER_SPS
+class ProfileGatherer;
+#endif
 namespace plugins {
 //-----------------------------------------------------------------------------
 
@@ -114,6 +118,11 @@ public:
         return mPluginName + mPluginVersion;
     }
 
+    virtual nsresult GetRunID(uint32_t* aRunID) override;
+    virtual void SetHasLocalInstance() override {
+        mHadLocalInstance = true;
+    }
+
 protected:
     virtual mozilla::ipc::RacyInterruptPolicy
     MediateInterruptRace(const Message& parent, const Message& child) override
@@ -169,6 +178,8 @@ protected:
 
     virtual bool RecvNotifyContentModuleDestroyed() override { return true; }
 
+    virtual bool RecvProfile(const nsCString& aProfile) override { return true; }
+
     void SetPluginFuncs(NPPluginFuncs* aFuncs);
 
     nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode,
@@ -248,12 +259,15 @@ protected:
     void NotifyFlashHang();
     void NotifyPluginCrashed();
     void OnInitFailure();
+    bool MaybeRunDeferredShutdown();
+    bool DoShutdown(NPError* error);
 
     bool GetSetting(NPNVariable aVariable);
     void GetSettings(PluginSettings* aSettings);
 
     bool mIsChrome;
     bool mShutdown;
+    bool mHadLocalInstance;
     bool mClearSiteDataSupported;
     bool mGetSitesWithDataSupported;
     NPNetscapeFuncs* mNPNIface;
@@ -282,8 +296,10 @@ protected:
 
     bool              mIsStartingAsync;
     bool              mNPInitialized;
+    bool              mIsNPShutdownPending;
     nsTArray> mSurrogateInstances;
     nsresult          mAsyncNewRv;
+    uint32_t          mRunID;
 };
 
 class PluginModuleContentParent : public PluginModuleParent
@@ -338,7 +354,19 @@ class PluginModuleChromeParent
 
     virtual ~PluginModuleChromeParent();
 
-    void TerminateChildProcess(MessageLoop* aMsgLoop);
+    /*
+     * Terminates the plugin process associated with this plugin module. Also
+     * generates appropriate crash reports. Takes ownership of the file
+     * associated with aBrowserDumpId on success.
+     *
+     * @param aMsgLoop the main message pump associated with the module
+     *   protocol.
+     * @param aBrowserDumpId (optional) previously taken browser dump id. If
+     *   provided TerminateChildProcess will use this browser dump file in
+     *   generating a multi-process crash report. If not provided a browser
+     *   dump will be taken at the time of this call.
+     */
+    void TerminateChildProcess(MessageLoop* aMsgLoop, const nsAString& aBrowserDumpId);
 
 #ifdef XP_WIN
     /**
@@ -370,6 +398,14 @@ class PluginModuleChromeParent
     void OnEnteredSyncSend() override;
     void OnExitedSyncSend() override;
 
+#ifdef  MOZ_ENABLE_PROFILER_SPS
+    void GatherAsyncProfile(mozilla::ProfileGatherer* aGatherer);
+    void GatheredAsyncProfile(nsIProfileSaveEvent* aSaveEvent);
+#endif
+
+    virtual bool
+    RecvProfile(const nsCString& aProfile) override;
+
 private:
     virtual void
     EnteredCxxStack() override;
@@ -401,7 +437,8 @@ private:
     virtual void ActorDestroy(ActorDestroyReason why) override;
 
     // aFilePath is UTF8, not native!
-    explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId);
+    explicit PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId,
+                                      int32_t aSandboxLevel);
 
     void CleanupFromTimeout(const bool aByHangUI);
 
@@ -439,6 +476,7 @@ private:
     PluginHangUIParent *mHangUIParent;
     bool mHangUIEnabled;
     bool mIsTimerReset;
+    int32_t mSandboxLevel;
 
     /**
      * Launches the Plugin Hang UI.
@@ -500,6 +538,10 @@ private:
     NPError             mAsyncInitError;
     dom::ContentParent* mContentParent;
     nsCOMPtr mOfflineObserver;
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    nsRefPtr mGatherer;
+#endif
+    nsCString mProfile;
     bool mIsBlocklisted;
     static bool sInstantiated;
 };
diff --git a/dom/plugins/ipc/PluginProcessParent.cpp b/dom/plugins/ipc/PluginProcessParent.cpp
index 39e09bb12a..ac8399e334 100644
--- a/dom/plugins/ipc/PluginProcessParent.cpp
+++ b/dom/plugins/ipc/PluginProcessParent.cpp
@@ -76,7 +76,7 @@ AddSandboxAllowedFiles(int32_t aSandboxLevel,
                        vector& aAllowedFilesRead,
                        vector& aAllowedFilesReadWrite)
 {
-    if (aSandboxLevel < 3) {
+    if (aSandboxLevel < 2) {
         return;
     }
 
@@ -87,16 +87,26 @@ AddSandboxAllowedFiles(int32_t aSandboxLevel,
         return;
     }
 
-    AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR);
-    AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR,
-                          NS_LITERAL_STRING("\\*"));
+    // Higher than level 2 currently removes the users own rights.
+    if (aSandboxLevel > 2) {
+        AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR);
+        AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_WIN_HOME_DIR,
+                              NS_LITERAL_STRING("\\*"));
+    }
 
+    // Level 2 and above is now using low integrity, so we need to give write
+    // access to the Flash directories.
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR,
                           NS_LITERAL_STRING("\\Macromedia\\Flash Player\\*"));
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_WIN_APPDATA_DIR,
                           NS_LITERAL_STRING("\\Adobe\\Flash Player\\*"));
+
+#if defined(_X86_)
+    // Write access to the Temp directory should only be needed for 32-bit as
+    // it is used to turn off protected mode, which only applies to x86.
     AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc, NS_OS_TEMP_DIR,
                           NS_LITERAL_STRING("\\*"));
+#endif
 }
 #endif
 
diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.cpp b/dom/plugins/ipc/PluginScriptableObjectChild.cpp
index 9f6231345b..ae399dde2c 100644
--- a/dom/plugins/ipc/PluginScriptableObjectChild.cpp
+++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp
@@ -778,7 +778,7 @@ PluginScriptableObjectChild::AnswerInvoke(const PluginIdentifier& aId,
   AutoFallibleTArray convertedArgs;
   uint32_t argCount = aArgs.Length();
 
-  if (!convertedArgs.SetLength(argCount)) {
+  if (!convertedArgs.SetLength(argCount, mozilla::fallible)) {
     *aResult = void_t();
     *aSuccess = false;
     return true;
@@ -848,7 +848,7 @@ PluginScriptableObjectChild::AnswerInvokeDefault(InfallibleTArray&& aAr
   AutoFallibleTArray convertedArgs;
   uint32_t argCount = aArgs.Length();
 
-  if (!convertedArgs.SetLength(argCount)) {
+  if (!convertedArgs.SetLength(argCount, mozilla::fallible)) {
     *aResult = void_t();
     *aSuccess = false;
     return true;
@@ -1099,7 +1099,7 @@ PluginScriptableObjectChild::AnswerConstruct(InfallibleTArray&& aArgs,
   AutoFallibleTArray convertedArgs;
   uint32_t argCount = aArgs.Length();
 
-  if (!convertedArgs.SetLength(argCount)) {
+  if (!convertedArgs.SetLength(argCount, mozilla::fallible)) {
     *aResult = void_t();
     *aSuccess = false;
     return true;
diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.cpp b/dom/plugins/ipc/PluginScriptableObjectParent.cpp
index 464f8d9d9a..fbac6c6bdc 100644
--- a/dom/plugins/ipc/PluginScriptableObjectParent.cpp
+++ b/dom/plugins/ipc/PluginScriptableObjectParent.cpp
@@ -824,7 +824,7 @@ PluginScriptableObjectParent::AnswerInvoke(const PluginIdentifier& aId,
   AutoFallibleTArray convertedArgs;
   uint32_t argCount = aArgs.Length();
 
-  if (!convertedArgs.SetLength(argCount)) {
+  if (!convertedArgs.SetLength(argCount, fallible)) {
     *aResult = void_t();
     *aSuccess = false;
     return true;
@@ -907,7 +907,7 @@ PluginScriptableObjectParent::AnswerInvokeDefault(InfallibleTArray&& aA
   AutoFallibleTArray convertedArgs;
   uint32_t argCount = aArgs.Length();
 
-  if (!convertedArgs.SetLength(argCount)) {
+  if (!convertedArgs.SetLength(argCount, fallible)) {
     *aResult = void_t();
     *aSuccess = false;
     return true;
@@ -1227,7 +1227,7 @@ PluginScriptableObjectParent::AnswerConstruct(InfallibleTArray&& aArgs,
   AutoFallibleTArray convertedArgs;
   uint32_t argCount = aArgs.Length();
 
-  if (!convertedArgs.SetLength(argCount)) {
+  if (!convertedArgs.SetLength(argCount, fallible)) {
     *aResult = void_t();
     *aSuccess = false;
     return true;
diff --git a/dom/svg/DOMSVGLengthList.cpp b/dom/svg/DOMSVGLengthList.cpp
index 92361592b4..0fdcdd78f9 100644
--- a/dom/svg/DOMSVGLengthList.cpp
+++ b/dom/svg/DOMSVGLengthList.cpp
@@ -131,7 +131,7 @@ DOMSVGLengthList::InternalListLengthWillChange(uint32_t aNewLength)
     }
   }
 
-  if (!mItems.SetLength(aNewLength)) {
+  if (!mItems.SetLength(aNewLength, fallible)) {
     // We silently ignore SetLength OOM failure since being out of sync is safe
     // so long as we have *fewer* items than our internal list.
     mItems.Clear();
diff --git a/dom/svg/DOMSVGNumberList.cpp b/dom/svg/DOMSVGNumberList.cpp
index 030ec249e4..0913ab3303 100644
--- a/dom/svg/DOMSVGNumberList.cpp
+++ b/dom/svg/DOMSVGNumberList.cpp
@@ -132,7 +132,7 @@ DOMSVGNumberList::InternalListLengthWillChange(uint32_t aNewLength)
     }
   }
 
-  if (!mItems.SetLength(aNewLength)) {
+  if (!mItems.SetLength(aNewLength, fallible)) {
     // We silently ignore SetLength OOM failure since being out of sync is safe
     // so long as we have *fewer* items than our internal list.
     mItems.Clear();
diff --git a/dom/svg/DOMSVGPathSegList.cpp b/dom/svg/DOMSVGPathSegList.cpp
index 76ef9ea505..438550f606 100644
--- a/dom/svg/DOMSVGPathSegList.cpp
+++ b/dom/svg/DOMSVGPathSegList.cpp
@@ -209,8 +209,7 @@ DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData& aNewValue)
     }
 
     // Only now may we truncate mItems
-    mItems.SetLength(newLength);
-
+    mItems.TruncateLength(newLength);
   } else if (dataIndex < dataLength) {
     // aNewValue has more items than our previous internal counterpart
 
diff --git a/dom/svg/DOMSVGPointList.cpp b/dom/svg/DOMSVGPointList.cpp
index 86d986b4a2..2f90b62144 100644
--- a/dom/svg/DOMSVGPointList.cpp
+++ b/dom/svg/DOMSVGPointList.cpp
@@ -166,7 +166,7 @@ DOMSVGPointList::InternalListWillChangeTo(const SVGPointList& aNewValue)
     }
   }
 
-  if (!mItems.SetLength(newLength)) {
+  if (!mItems.SetLength(newLength, fallible)) {
     // We silently ignore SetLength OOM failure since being out of sync is safe
     // so long as we have *fewer* items than our internal list.
     mItems.Clear();
diff --git a/dom/svg/DOMSVGTransformList.cpp b/dom/svg/DOMSVGTransformList.cpp
index 77aac6583d..ea87c1822d 100644
--- a/dom/svg/DOMSVGTransformList.cpp
+++ b/dom/svg/DOMSVGTransformList.cpp
@@ -132,7 +132,7 @@ DOMSVGTransformList::InternalListLengthWillChange(uint32_t aNewLength)
     }
   }
 
-  if (!mItems.SetLength(aNewLength)) {
+  if (!mItems.SetLength(aNewLength, fallible)) {
     // We silently ignore SetLength OOM failure since being out of sync is safe
     // so long as we have *fewer* items than our internal list.
     mItems.Clear();
diff --git a/dom/svg/SVGLengthList.h b/dom/svg/SVGLengthList.h
index 01c714a640..b5196423d9 100644
--- a/dom/svg/SVGLengthList.h
+++ b/dom/svg/SVGLengthList.h
@@ -90,7 +90,7 @@ protected:
    * increased, in which case the list will be left unmodified.
    */
   bool SetLength(uint32_t aNumberOfItems) {
-    return mLengths.SetLength(aNumberOfItems);
+    return mLengths.SetLength(aNumberOfItems, fallible);
   }
 
 private:
diff --git a/dom/svg/SVGNumberList.h b/dom/svg/SVGNumberList.h
index 593dd57eb4..99ee34997d 100644
--- a/dom/svg/SVGNumberList.h
+++ b/dom/svg/SVGNumberList.h
@@ -91,7 +91,7 @@ protected:
    * increased, in which case the list will be left unmodified.
    */
   bool SetLength(uint32_t aNumberOfItems) {
-    return mNumbers.SetLength(aNumberOfItems);
+    return mNumbers.SetLength(aNumberOfItems, fallible);
   }
 
 private:
diff --git a/dom/svg/SVGPathData.cpp b/dom/svg/SVGPathData.cpp
index a91ef96e4f..599f710453 100644
--- a/dom/svg/SVGPathData.cpp
+++ b/dom/svg/SVGPathData.cpp
@@ -81,7 +81,7 @@ SVGPathData::AppendSeg(uint32_t aType, ...)
 {
   uint32_t oldLength = mData.Length();
   uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType);
-  if (!mData.SetLength(newLength)) {
+  if (!mData.SetLength(newLength, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
diff --git a/dom/svg/SVGPathData.h b/dom/svg/SVGPathData.h
index 44669b5176..5fd504cd61 100644
--- a/dom/svg/SVGPathData.h
+++ b/dom/svg/SVGPathData.h
@@ -202,7 +202,7 @@ protected:
    * increased, in which case the list will be left unmodified.
    */
   bool SetLength(uint32_t aLength) {
-    return mData.SetLength(aLength);
+    return mData.SetLength(aLength, fallible);
   }
 
   nsresult SetValueFromString(const nsAString& aValue);
diff --git a/dom/svg/SVGPointList.h b/dom/svg/SVGPointList.h
index 51d92b0f12..54844ce97d 100644
--- a/dom/svg/SVGPointList.h
+++ b/dom/svg/SVGPointList.h
@@ -99,7 +99,7 @@ protected:
    * increased, in which case the list will be left unmodified.
    */
   bool SetLength(uint32_t aNumberOfItems) {
-    return mItems.SetLength(aNumberOfItems);
+    return mItems.SetLength(aNumberOfItems, fallible);
   }
 
 private:
diff --git a/dom/svg/SVGStringList.h b/dom/svg/SVGStringList.h
index d9961d8b2b..e1b346fdb6 100644
--- a/dom/svg/SVGStringList.h
+++ b/dom/svg/SVGStringList.h
@@ -92,7 +92,7 @@ protected:
    * increased, in which case the list will be left unmodified.
    */
   bool SetLength(uint32_t aStringOfItems) {
-    return mStrings.SetLength(aStringOfItems);
+    return mStrings.SetLength(aStringOfItems, fallible);
   }
 
 private:
diff --git a/dom/svg/SVGTransformList.h b/dom/svg/SVGTransformList.h
index 758b2dbbff..e757c3ea9b 100644
--- a/dom/svg/SVGTransformList.h
+++ b/dom/svg/SVGTransformList.h
@@ -94,7 +94,7 @@ protected:
    * increased, in which case the list will be left unmodified.
    */
   bool SetLength(uint32_t aNumberOfItems) {
-    return mItems.SetLength(aNumberOfItems);
+    return mItems.SetLength(aNumberOfItems, fallible);
   }
 
 private:
diff --git a/dom/tests/mochitest/chrome/MozEnteredDomFullscreen_chrome.xul b/dom/tests/mochitest/chrome/MozDomFullscreen_chrome.xul
similarity index 85%
rename from dom/tests/mochitest/chrome/MozEnteredDomFullscreen_chrome.xul
rename to dom/tests/mochitest/chrome/MozDomFullscreen_chrome.xul
index 63fa30e71e..5673b1b1c5 100644
--- a/dom/tests/mochitest/chrome/MozEnteredDomFullscreen_chrome.xul
+++ b/dom/tests/mochitest/chrome/MozDomFullscreen_chrome.xul
@@ -60,7 +60,18 @@ function thirdEntry(event) {
   is(event.target, gOuterDoc, "Third MozEnteredDomFullscreen should be targeted at outer doc");
   ok(gOuterDoc.mozFullScreenElement != null, "Outer doc return to fullscreen after cancel fullscreen in inner doc");
   window.removeEventListener("MozEnteredDomFullscreen", thirdEntry, false);
+  window.removeEventListener("MozExitedDomFullscreen", earlyExit, false);
+  window.addEventListener("MozExitedDomFullscreen", lastExit, false);
   gOuterDoc.mozCancelFullScreen();
+}
+
+function earlyExit(event) {
+  ok(false, "MozExitedDomFullscreen should only be triggered after cancel all fullscreen");
+}
+
+function lastExit(event) {
+  is(event.target, gOuterDoc, "MozExitedDomFullscreen should be targeted at the last exited doc");
+  ok(gOuterDoc.mozFullScreenElement == null, "Fullscreen should have been fully exited");
   window.opener.wrappedJSObject.done();
 }
 
@@ -71,6 +82,7 @@ function start() {
       gOuterDoc = gBrowser.contentDocument;
       gBrowser.contentWindow.focus();
       window.addEventListener("MozEnteredDomFullscreen", firstEntry, false);
+      window.addEventListener("MozExitedDomFullscreen", earlyExit, false);
       gOuterDoc.body.mozRequestFullScreen();
     });
 }
@@ -78,6 +90,6 @@ function start() {
 ]]>
 
 
-
+
 
 
diff --git a/dom/tests/mochitest/chrome/chrome.ini b/dom/tests/mochitest/chrome/chrome.ini
index 487b059e15..36d2ac27c2 100644
--- a/dom/tests/mochitest/chrome/chrome.ini
+++ b/dom/tests/mochitest/chrome/chrome.ini
@@ -3,7 +3,7 @@ support-files =
   489127.html
   DOMWindowCreated_chrome.xul
   DOMWindowCreated_content.html
-  MozEnteredDomFullscreen_chrome.xul
+  MozDomFullscreen_chrome.xul
   child_focus_frame.html
   file_DOM_element_instanceof.xul
   file_bug799299.xul
@@ -48,7 +48,7 @@ skip-if = os == 'linux'
 [test_indexedSetter.html]
 [test_moving_nodeList.xul]
 [test_moving_xhr.xul]
-[test_MozEnteredDomFullscreen_event.xul]
+[test_MozDomFullscreen_event.xul]
 # disabled on OS X for intermittent failures--bug-798848
 skip-if = toolkit == 'cocoa'
 [test_nodesFromRect.html]
diff --git a/dom/tests/mochitest/chrome/test_MozEnteredDomFullscreen_event.xul b/dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
similarity index 93%
rename from dom/tests/mochitest/chrome/test_MozEnteredDomFullscreen_event.xul
rename to dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
index d9ab22684b..076e9db4e4 100644
--- a/dom/tests/mochitest/chrome/test_MozEnteredDomFullscreen_event.xul
+++ b/dom/tests/mochitest/chrome/test_MozDomFullscreen_event.xul
@@ -36,7 +36,7 @@ SpecialPowers.pushPrefEnv({"set": [['full-screen-api.enabled', true],
                                    ['full-screen-api.allow-trusted-requests-only', false]]}, setup);
 
 function setup() {
-   newwindow = window.open("MozEnteredDomFullscreen_chrome.xul", "_blank","chrome,resizable=yes,width=400,height=400");
+   newwindow = window.open("MozDomFullscreen_chrome.xul", "_blank","chrome,resizable=yes,width=400,height=400");
 }
 
 function done()
diff --git a/dom/tests/mochitest/general/file_MozEnteredDomFullscreen.html b/dom/tests/mochitest/general/file_MozDomFullscreen.html
similarity index 100%
rename from dom/tests/mochitest/general/file_MozEnteredDomFullscreen.html
rename to dom/tests/mochitest/general/file_MozDomFullscreen.html
diff --git a/dom/tests/mochitest/general/mochitest.ini b/dom/tests/mochitest/general/mochitest.ini
index f5c7a328b0..94afdd9f74 100644
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -1,7 +1,7 @@
 [DEFAULT]
 support-files =
   497633.html
-  file_MozEnteredDomFullscreen.html
+  file_MozDomFullscreen.html
   file_bug628069.html
   file_clonewrapper.html
   file_domWindowUtils_scrollbarSize.html
diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html
index 7bbc59faa7..18cd8082a5 100644
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -900,6 +900,8 @@ var interfaceNamesInGlobalScope =
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "RGBColor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "RTCCertificate",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "RTCDataChannelEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
@@ -1408,17 +1410,23 @@ function createInterfaceMap(isXBLScope) {
     for (var entry of interfaces) {
       if (typeof(entry) === "string") {
         interfaceMap[entry] = true;
-      } else if ((entry.nightly === !isNightly) ||
-                 (entry.xbl === !isXBLScope) ||
-                 (entry.desktop === !isDesktop) ||
-                 (entry.b2g === !isB2G) ||
-                 (entry.release === !isRelease) ||
-                 (entry.pref && !prefs.getBoolPref(entry.pref))  ||
-                 (entry.permission && !hasPermission(entry.permission)) ||
-                 entry.disabled) {
-        interfaceMap[entry.name] = false;
       } else {
-        interfaceMap[entry.name] = true;
+        ok(!("pref" in entry), "Bogus pref annotation for " + entry.name);
+        if ((entry.nightly === !isNightly) ||
+            (entry.xbl === !isXBLScope) ||
+            (entry.desktop === !isDesktop) ||
+            (entry.b2g === !isB2G) ||
+            (entry.windows === !isWindows) ||
+            (entry.mac === !isMac) ||
+            (entry.linux === !isLinux) ||
+            (entry.android === !isAndroid) ||
+            (entry.release === !isRelease) ||
+            (entry.permission && !hasPermission(entry.permission)) ||
+            entry.disabled) {
+          interfaceMap[entry.name] = false;
+        } else {
+          interfaceMap[entry.name] = true;
+        }
       }
     }
   }
diff --git a/dom/time/TimeManager.cpp b/dom/time/TimeManager.cpp
index ba10a4928d..0fb7f16232 100644
--- a/dom/time/TimeManager.cpp
+++ b/dom/time/TimeManager.cpp
@@ -34,7 +34,7 @@ TimeManager::WrapObject(JSContext* aCx, JS::Handle aGivenProto)
 void
 TimeManager::Set(Date& aDate)
 {
-  Set(aDate.TimeStamp());
+  Set(aDate.ToDouble());
 }
 
 void
diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl
index f12283d557..b933cd3b9b 100644
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -91,7 +91,7 @@ interface CanvasRenderingContext2D {
   void fill(Path2D path, optional CanvasWindingRule winding = "nonzero");
   void stroke();
   void stroke(Path2D path);
-  [Pref="canvas.focusring.enabled"] void drawFocusIfNeeded(Element element);
+  [Pref="canvas.focusring.enabled", Throws] void drawFocusIfNeeded(Element element);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Path path, HTMLElement element);
   [Pref="canvas.customfocusring.enabled"] boolean drawCustomFocusRing(Element element);
 // NOT IMPLEMENTED  boolean drawCustomFocusRing(Path path, HTMLElement element);
@@ -245,7 +245,7 @@ interface CanvasDrawingStyles {
            attribute double miterLimit; // (default 10)
 
   // dashed lines
-    [LenientFloat] void setLineDash(sequence segments); // default empty
+    [LenientFloat, Throws] void setLineDash(sequence segments); // default empty
     sequence getLineDash();
     [LenientFloat] attribute double lineDashOffset;
 
diff --git a/dom/webidl/HTMLObjectElement.webidl b/dom/webidl/HTMLObjectElement.webidl
index e3836e6d7a..43aaa0dd40 100644
--- a/dom/webidl/HTMLObjectElement.webidl
+++ b/dom/webidl/HTMLObjectElement.webidl
@@ -211,6 +211,9 @@ interface MozObjectLoadingContent {
    */
   [ChromeOnly, Throws]
   void cancelPlayPreview();
+
+  [ChromeOnly, Throws]
+  readonly attribute unsigned long runID;
 };
 
 /**
diff --git a/dom/webidl/MutationObserver.webidl b/dom/webidl/MutationObserver.webidl
index e1bbccd425..f222a8fe99 100644
--- a/dom/webidl/MutationObserver.webidl
+++ b/dom/webidl/MutationObserver.webidl
@@ -43,7 +43,7 @@ interface MutationObserver {
   void disconnect();
   sequence takeRecords();
 
-  [ChromeOnly]
+  [ChromeOnly, Throws]
   sequence getObservingInfo();
   [ChromeOnly]
   readonly attribute MutationCallback mutationCallback;
diff --git a/dom/webidl/PeerConnectionImpl.webidl b/dom/webidl/PeerConnectionImpl.webidl
index 65870c810d..fdce2dcad8 100644
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -23,6 +23,7 @@ interface PeerConnectionImpl  {
   void initialize(PeerConnectionObserver observer, Window window,
                   RTCConfiguration iceServers,
                   nsISupports thread);
+
   /* JSEP calls */
   [Throws]
   void createOffer(optional RTCOfferOptions options);
@@ -63,9 +64,14 @@ interface PeerConnectionImpl  {
   void close();
 
   /* Notify DOM window if this plugin crash is ours. */
-  boolean pluginCrash(unsigned long long pluginId, DOMString name, DOMString pluginDumpID);
+  boolean pluginCrash(unsigned long long pluginId, DOMString name);
 
   /* Attributes */
+  /* This provides the implementation with the certificate it uses to
+   * authenticate itself.  The JS side must set this before calling
+   * createOffer/createAnswer or retrieving the value of fingerprint.  This has
+   * to be delayed because generating the certificate takes some time. */
+  attribute RTCCertificate certificate;
   [Constant]
   readonly attribute DOMString fingerprint;
   readonly attribute DOMString localDescription;
diff --git a/dom/webidl/PluginCrashedEvent.webidl b/dom/webidl/PluginCrashedEvent.webidl
index b2056d7bb9..8eed7244e5 100644
--- a/dom/webidl/PluginCrashedEvent.webidl
+++ b/dom/webidl/PluginCrashedEvent.webidl
@@ -7,6 +7,7 @@
 [Constructor(DOMString type, optional PluginCrashedEventInit eventInitDict), ChromeOnly]
 interface PluginCrashedEvent : Event
 {
+  readonly attribute unsigned long pluginID;
   readonly attribute DOMString pluginDumpID;
   readonly attribute DOMString pluginName;
   readonly attribute DOMString? browserDumpID;
@@ -17,6 +18,7 @@ interface PluginCrashedEvent : Event
 
 dictionary PluginCrashedEventInit : EventInit
 {
+  unsigned long pluginID = 0;
   DOMString pluginDumpID = "";
   DOMString pluginName = "";
   DOMString? browserDumpID = null;
diff --git a/dom/webidl/RTCCertificate.webidl b/dom/webidl/RTCCertificate.webidl
new file mode 100644
index 0000000000..e1ac972384
--- /dev/null
+++ b/dom/webidl/RTCCertificate.webidl
@@ -0,0 +1,12 @@
+/* -*- 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/.
+ *
+ * Specification: http://w3c.github.io/webrtc-pc/#certificate-management
+ */
+
+[Pref="media.peerconnection.enabled"]
+interface RTCCertificate {
+  readonly attribute Date expires;
+};
diff --git a/dom/webidl/RTCConfiguration.webidl b/dom/webidl/RTCConfiguration.webidl
index 02e7c8e465..579fd67bc8 100644
--- a/dom/webidl/RTCConfiguration.webidl
+++ b/dom/webidl/RTCConfiguration.webidl
@@ -17,4 +17,5 @@ dictionary RTCIceServer {
 dictionary RTCConfiguration {
     sequence iceServers;
     DOMString? peerIdentity = null;
+    sequence certificates;
 };
diff --git a/dom/webidl/RTCPeerConnection.webidl b/dom/webidl/RTCPeerConnection.webidl
index 49eb5cd7f7..8920782c73 100644
--- a/dom/webidl/RTCPeerConnection.webidl
+++ b/dom/webidl/RTCPeerConnection.webidl
@@ -78,6 +78,9 @@ interface RTCDataChannel;
               optional object? constraints)]
 // moz-prefixed until sufficiently standardized.
 interface mozRTCPeerConnection : EventTarget  {
+  [Throws, StaticClassOverride="mozilla::dom::RTCCertificate"]
+  static Promise generateCertificate (AlgorithmIdentifier keygenAlgorithm);
+
   [Pref="media.peerconnection.identity.enabled"]
   void setIdentityProvider (DOMString provider,
                             optional DOMString protocol,
diff --git a/dom/webidl/SubtleCrypto.webidl b/dom/webidl/SubtleCrypto.webidl
index 6b8ff75ead..5cd4b35d43 100644
--- a/dom/webidl/SubtleCrypto.webidl
+++ b/dom/webidl/SubtleCrypto.webidl
@@ -100,6 +100,10 @@ dictionary EcdsaParams : Algorithm {
   required AlgorithmIdentifier hash;
 };
 
+dictionary EcKeyImportParams : Algorithm {
+  NamedCurve namedCurve;
+};
+
 /***** JWK *****/
 
 dictionary RsaOtherPrimesInfo {
diff --git a/dom/webidl/WebGL2RenderingContext.webidl b/dom/webidl/WebGL2RenderingContext.webidl
index 93719a627a..fa71c3ec62 100644
--- a/dom/webidl/WebGL2RenderingContext.webidl
+++ b/dom/webidl/WebGL2RenderingContext.webidl
@@ -330,9 +330,14 @@ interface WebGL2RenderingContext : WebGLRenderingContext
                          GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
     void framebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
     any getInternalformatParameter(GLenum target, GLenum internalformat, GLenum pname);
+
+    [Throws]
     void invalidateFramebuffer(GLenum target, sequence attachments);
+
+    [Throws]
     void invalidateSubFramebuffer (GLenum target, sequence attachments,
                                    GLint x, GLint y, GLsizei width, GLsizei height);
+
     void readBuffer(GLenum src);
 
     /* Renderbuffer objects */
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index 1322b03180..12d80319eb 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -584,6 +584,7 @@ if CONFIG['MOZ_WEBRTC']:
         'PeerConnectionImplEnums.webidl',
         'PeerConnectionObserver.webidl',
         'PeerConnectionObserverEnums.webidl',
+	'RTCCertificate.webidl',
         'RTCConfiguration.webidl',
         'RTCIceCandidate.webidl',
         'RTCIdentityAssertion.webidl',
diff --git a/dom/workers/DataStore.cpp b/dom/workers/DataStore.cpp
index 7939b30b3a..1b08bcb392 100644
--- a/dom/workers/DataStore.cpp
+++ b/dom/workers/DataStore.cpp
@@ -188,7 +188,7 @@ public:
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
-    if (!mId.AppendElements(aId)) {
+    if (!mId.AppendElements(aId, fallible)) {
       mRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     }
   }
diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp
index 0d52077cba..d07dc9136a 100644
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -30,6 +30,7 @@
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "mozilla/dom/Headers.h"
+#include "mozilla/dom/indexedDB/IDBFactory.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/Navigator.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
@@ -2860,6 +2861,9 @@ ServiceWorkerManager::CreateServiceWorker(nsIPrincipal* aPrincipal,
 
   info.mPrincipal = aPrincipal;
 
+  info.mIndexedDBAllowed =
+    indexedDB::IDBFactory::AllowedForPrincipal(aPrincipal);
+
   // NOTE: this defaults the SW load context to:
   //  - private browsing = false
   //  - content = true
diff --git a/gfx/thebes/gfxContext.cpp b/gfx/thebes/gfxContext.cpp
index ebda38e93c..d0dd30d56b 100644
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -462,7 +462,7 @@ gfxContext::CurrentDash(FallibleTArray& dashes, gfxFloat* offset) cons
   const AzureState &state = CurrentState();
   int count = state.strokeOptions.mDashLength;
 
-  if (count <= 0 || !dashes.SetLength(count)) {
+  if (count <= 0 || !dashes.SetLength(count, fallible)) {
     return false;
   }
 
diff --git a/gfx/thebes/gfxCoreTextShaper.cpp b/gfx/thebes/gfxCoreTextShaper.cpp
index 7ab77f9d94..510a96e9bc 100644
--- a/gfx/thebes/gfxCoreTextShaper.cpp
+++ b/gfx/thebes/gfxCoreTextShaper.cpp
@@ -284,7 +284,7 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxShapedText *aShapedText,
 
     static const int32_t NO_GLYPH = -1;
     AutoFallibleTArray charToGlyphArray;
-    if (!charToGlyphArray.SetLength(stringRange.length)) {
+    if (!charToGlyphArray.SetLength(stringRange.length, fallible)) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
     int32_t *charToGlyph = charToGlyphArray.Elements();
diff --git a/gfx/thebes/gfxFontUtils.cpp b/gfx/thebes/gfxFontUtils.cpp
index 62dc19ac93..f07aa10364 100644
--- a/gfx/thebes/gfxFontUtils.cpp
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -1010,7 +1010,7 @@ gfxFontUtils::RenameFont(const nsAString& aName, const uint8_t *aFontData,
     uint32_t adjFontDataSize = paddedFontDataSize + nameTableSize;
 
     // create new buffer: old font data plus new name table
-    if (!aNewFont->AppendElements(adjFontDataSize))
+    if (!aNewFont->AppendElements(adjFontDataSize, fallible))
         return NS_ERROR_OUT_OF_MEMORY;
 
     // copy the old font data
diff --git a/gfx/thebes/gfxGraphiteShaper.cpp b/gfx/thebes/gfxGraphiteShaper.cpp
index 5088068a33..ac1a71360d 100644
--- a/gfx/thebes/gfxGraphiteShaper.cpp
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -213,10 +213,10 @@ gfxGraphiteShaper::SetGlyphsFromSegment(gfxContext      *aContext,
     AutoFallibleTArray xLocs;
     AutoFallibleTArray yLocs;
 
-    if (!clusters.SetLength(aLength) ||
-        !gids.SetLength(glyphCount) ||
-        !xLocs.SetLength(glyphCount) ||
-        !yLocs.SetLength(glyphCount))
+    if (!clusters.SetLength(aLength, fallible) ||
+        !gids.SetLength(glyphCount, fallible) ||
+        !xLocs.SetLength(glyphCount, fallible) ||
+        !yLocs.SetLength(glyphCount, fallible))
     {
         return NS_ERROR_OUT_OF_MEMORY;
     }
diff --git a/gfx/thebes/gfxHarfBuzzShaper.cpp b/gfx/thebes/gfxHarfBuzzShaper.cpp
index f835583757..f40888def8 100644
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -1434,7 +1434,7 @@ gfxHarfBuzzShaper::SetGlyphsFromRun(gfxContext     *aContext,
     uint32_t wordLength = aLength;
     static const int32_t NO_GLYPH = -1;
     AutoFallibleTArray charToGlyphArray;
-    if (!charToGlyphArray.SetLength(wordLength)) {
+    if (!charToGlyphArray.SetLength(wordLength, fallible)) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
 
diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp
index 4946b7700b..35a86f6308 100644
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -375,7 +375,7 @@ CopyWOFFMetadata(const uint8_t* aFontData,
     if (metaOffset >= aLength || metaCompLen > aLength - metaOffset) {
         return;
     }
-    if (!aMetadata->SetLength(woff->metaCompLen)) {
+    if (!aMetadata->SetLength(woff->metaCompLen, fallible)) {
         return;
     }
     memcpy(aMetadata->Elements(), aFontData + metaOffset, metaCompLen);
diff --git a/intl/hyphenation/nsHyphenator.cpp b/intl/hyphenation/nsHyphenator.cpp
index b72928f1ad..8a75090e40 100644
--- a/intl/hyphenation/nsHyphenator.cpp
+++ b/intl/hyphenation/nsHyphenator.cpp
@@ -46,7 +46,7 @@ nsresult
 nsHyphenator::Hyphenate(const nsAString& aString,
                         FallibleTArray& aHyphens)
 {
-  if (!aHyphens.SetLength(aString.Length())) {
+  if (!aHyphens.SetLength(aString.Length(), mozilla::fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   memset(aHyphens.Elements(), false, aHyphens.Length() * sizeof(bool));
diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp
index 4f42283b26..8a745753cf 100644
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -255,6 +255,17 @@ uint32_t GeckoChildProcessHost::GetSupportedArchitecturesForProcessType(GeckoPro
   return base::GetCurrentProcessArchitecture();
 }
 
+// We start the unique IDs at 1 so that 0 can be used to mean that
+// a component has no unique ID assigned to it.
+uint32_t GeckoChildProcessHost::sNextUniqueID = 1;
+
+/* static */
+uint32_t
+GeckoChildProcessHost::GetUniqueID()
+{
+  return sNextUniqueID++;
+}
+
 void
 GeckoChildProcessHost::PrepareLaunch()
 {
diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h
index 76728058b5..96eef639af 100644
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -47,6 +47,8 @@ public:
 
   static uint32_t GetSupportedArchitecturesForProcessType(GeckoProcessType type);
 
+  static uint32_t GetUniqueID();
+
   // Block until the IPC channel for our subprocess is initialized,
   // but no longer.  The child process may or may not have been
   // created when this method returns.
@@ -196,6 +198,8 @@ private:
   //
   // FIXME/cjones: this strongly indicates bad design.  Shame on us.
   std::queue mQueue;
+
+  static uint32_t sNextUniqueID;
 };
 
 #ifdef MOZ_NUWA_PROCESS
diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h
index 4d66060f32..7e59e9771a 100644
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -513,7 +513,7 @@ struct ParamTraits >
         return false;
       }
 
-      E* elements = aResult->AppendElements(length);
+      E* elements = aResult->AppendElements(length, mozilla::fallible);
       if (!elements) {
         return false;
       }
diff --git a/ipc/ipdl/ipdl/lower.py b/ipc/ipdl/ipdl/lower.py
index e4013dc13e..8e8f4ac09c 100644
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -343,7 +343,7 @@ def _callCxxArrayLength(arr):
 
 def _callCxxCheckedArraySetLength(arr, lenexpr, sel='.'):
     ifbad = StmtIf(ExprNot(ExprCall(ExprSelect(arr, sel, 'SetLength'),
-                                    args=[ lenexpr ])))
+                                    args=[ lenexpr, ExprVar('mozilla::fallible') ])))
     ifbad.addifstmt(_fatalError('Error setting the array length'))
     ifbad.addifstmt(StmtReturn.FALSE)
     return ifbad
diff --git a/js/ipc/CPOWTimer.cpp b/js/ipc/CPOWTimer.cpp
index 43e7cbbebd..e833cf2c1d 100644
--- a/js/ipc/CPOWTimer.cpp
+++ b/js/ipc/CPOWTimer.cpp
@@ -9,6 +9,8 @@
 #include "nsContentUtils.h"
 #include "CPOWTimer.h"
 
+#include "jsapi.h"
+
 CPOWTimer::CPOWTimer(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
     : cx_(nullptr)
     , startInterval_(0)
@@ -18,7 +20,7 @@ CPOWTimer::CPOWTimer(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
     if (!js::GetStopwatchIsMonitoringCPOW(runtime))
         return;
     cx_ = cx;
-    startInterval_ = PR_IntervalNow();
+    startInterval_ = JS_Now();
 }
 CPOWTimer::~CPOWTimer()
 {
@@ -33,7 +35,12 @@ CPOWTimer::~CPOWTimer()
         return;
     }
 
+    const int64_t endInterval = JS_Now();
+    if (endInterval <= startInterval_) {
+        // Do not assume monotonicity.
+        return;
+    }
+
     js::PerformanceData* performance = js::GetPerformanceData(runtime);
-    uint64_t duration = PR_IntervalToMicroseconds(PR_IntervalNow() - startInterval_);
-    performance->totalCPOWTime += duration;
+    performance->totalCPOWTime += endInterval - startInterval_;
 }
diff --git a/js/ipc/CPOWTimer.h b/js/ipc/CPOWTimer.h
index a5da838311..898806c64a 100644
--- a/js/ipc/CPOWTimer.h
+++ b/js/ipc/CPOWTimer.h
@@ -38,7 +38,7 @@ class MOZ_STACK_CLASS CPOWTimer final {
      * The instant at which the stopwatch was started. Undefined
      * if CPOW monitoring was off when the timer was created.
      */
-    PRIntervalTime startInterval_;
+    int64_t startInterval_;
 };
 
 #endif
diff --git a/js/public/Class.h b/js/public/Class.h
index 25608fd330..3daa193629 100644
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -454,21 +454,27 @@ const size_t JSCLASS_CACHED_PROTO_WIDTH = 6;
 
 struct ClassSpec
 {
-    ClassObjectCreationOp createConstructor;
-    ClassObjectCreationOp createPrototype;
-    const JSFunctionSpec* constructorFunctions;
-    const JSPropertySpec* constructorProperties;
-    const JSFunctionSpec* prototypeFunctions;
-    const JSPropertySpec* prototypeProperties;
-    FinishClassInitOp finishInit;
+    // All properties except flags should be accessed through accessor.
+    ClassObjectCreationOp createConstructor_;
+    ClassObjectCreationOp createPrototype_;
+    const JSFunctionSpec* constructorFunctions_;
+    const JSPropertySpec* constructorProperties_;
+    const JSFunctionSpec* prototypeFunctions_;
+    const JSPropertySpec* prototypeProperties_;
+    FinishClassInitOp finishInit_;
     uintptr_t flags;
 
     static const size_t ParentKeyWidth = JSCLASS_CACHED_PROTO_WIDTH;
 
     static const uintptr_t ParentKeyMask = (1 << ParentKeyWidth) - 1;
     static const uintptr_t DontDefineConstructor = 1 << ParentKeyWidth;
+    static const uintptr_t IsDelegated = 1 << (ParentKeyWidth + 1);
 
-    bool defined() const { return !!createConstructor; }
+    bool defined() const { return !!createConstructor_; }
+
+    bool delegated() const {
+        return (flags & IsDelegated);
+    }
 
     bool dependent() const {
         MOZ_ASSERT(defined());
@@ -484,6 +490,47 @@ struct ClassSpec
         MOZ_ASSERT(defined());
         return !(flags & DontDefineConstructor);
     }
+
+    const ClassSpec* delegatedClassSpec() const {
+        MOZ_ASSERT(delegated());
+        return reinterpret_cast(createConstructor_);
+    }
+
+    ClassObjectCreationOp createConstructorHook() const {
+        if (delegated())
+            return delegatedClassSpec()->createConstructorHook();
+        return createConstructor_;
+    }
+    ClassObjectCreationOp createPrototypeHook() const {
+        if (delegated())
+            return delegatedClassSpec()->createPrototypeHook();
+        return createPrototype_;
+    }
+    const JSFunctionSpec* constructorFunctions() const {
+        if (delegated())
+            return delegatedClassSpec()->constructorFunctions();
+        return constructorFunctions_;
+    }
+    const JSPropertySpec* constructorProperties() const {
+        if (delegated())
+            return delegatedClassSpec()->constructorProperties();
+        return constructorProperties_;
+    }
+    const JSFunctionSpec* prototypeFunctions() const {
+        if (delegated())
+            return delegatedClassSpec()->prototypeFunctions();
+        return prototypeFunctions_;
+    }
+    const JSPropertySpec* prototypeProperties() const {
+        if (delegated())
+            return delegatedClassSpec()->prototypeProperties();
+        return prototypeProperties_;
+    }
+    FinishClassInitOp finishInitHook() const {
+        if (delegated())
+            return delegatedClassSpec()->finishInitHook();
+        return finishInit_;
+    }
 };
 
 struct ClassExtension
@@ -524,6 +571,10 @@ struct ClassExtension
     JSObjectMovedOp objectMovedOp;
 };
 
+inline ClassObjectCreationOp DELEGATED_CLASSSPEC(const ClassSpec* spec) {
+    return reinterpret_cast(const_cast(spec));
+}
+
 #define JS_NULL_CLASS_SPEC  {nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr}
 #define JS_NULL_CLASS_EXT   {nullptr,nullptr,false,nullptr,nullptr}
 
@@ -616,7 +667,7 @@ struct JSClass {
 // the beginning of every global object's slots for use by the
 // application.
 #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5
-#define JSCLASS_GLOBAL_SLOT_COUNT      (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 34)
+#define JSCLASS_GLOBAL_SLOT_COUNT      (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 33)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
diff --git a/js/public/Date.h b/js/public/Date.h
index 6199f9eca5..2324ad7104 100644
--- a/js/public/Date.h
+++ b/js/public/Date.h
@@ -3,15 +3,118 @@
  * 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/. */
 
+/* JavaScript date/time computation and creation functions. */
+
 #ifndef js_Date_h
 #define js_Date_h
 
-#include "jstypes.h"
+/*
+ * Dates in JavaScript are defined by IEEE-754 double precision numbers from
+ * the set:
+ *
+ *   { t ∈ ℕ : -8.64e15 ≤ t ≤ +8.64e15 } ∪ { NaN }
+ *
+ * The single NaN value represents any invalid-date value.  All other values
+ * represent idealized durations in milliseconds since the UTC epoch.  (Leap
+ * seconds are ignored; leap days are not.)  +0 is the only zero in this set.
+ * The limit represented by 8.64e15 milliseconds is 100 million days either
+ * side of 00:00 January 1, 1970 UTC.
+ *
+ * Dates in the above set are represented by the |ClippedTime| class.  The
+ * double type is a superset of the above set, so it *may* (but need not)
+ * represent a date.  Use ECMAScript's |TimeClip| method to produce a date from
+ * a double.
+ *
+ * Date *objects* are simply wrappers around |TimeClip|'d numbers, with a bunch
+ * of accessor methods to the various aspects of the represented date.
+ */
+
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "js/Conversions.h"
+#include "js/Value.h"
+
+struct JSContext;
 
 namespace JS {
 
-// Year is a year, month is 0-11, day is 1-based.  The return value is
-// a number of milliseconds since the epoch.  Can return NaN.
+class ClippedTime;
+inline ClippedTime TimeClip(double time);
+
+/*
+ * |ClippedTime| represents the limited subset of dates/times described above.
+ *
+ * An invalid date/time may be created through the |ClippedTime::invalid|
+ * method.  Otherwise, a |ClippedTime| may be created using the |TimeClip|
+ * method.
+ *
+ * In typical use, the user might wish to manipulate a timestamp.  The user
+ * performs a series of operations on it, but the final value might not be a
+ * date as defined above -- it could have overflowed, acquired a fractional
+ * component, &c.  So as a *final* step, the user passes that value through
+ * |TimeClip| to produce a number restricted to JavaScript's date range.
+ *
+ * APIs that accept a JavaScript date value thus accept a |ClippedTime|, not a
+ * double.  This ensures that date/time APIs will only ever receive acceptable
+ * JavaScript dates.  This also forces users to perform any desired clipping,
+ * as only the user knows what behavior is desired when clipping occurs.
+ */
+class ClippedTime
+{
+    double t;
+
+    explicit ClippedTime(double time) : t(time) {}
+    friend ClippedTime TimeClip(double time);
+
+  public:
+    // Create an invalid date.
+    ClippedTime() : t(mozilla::UnspecifiedNaN()) {}
+
+    // Create an invalid date/time, more explicitly; prefer this to the default
+    // constructor.
+    static ClippedTime invalid() { return ClippedTime(); }
+
+    double toDouble() const { return t; }
+
+    bool isValid() const { return !mozilla::IsNaN(t); }
+};
+
+// ES6 20.3.1.15.
+//
+// Clip a double to JavaScript's date range (or to an invalid date) using the
+// ECMAScript TimeClip algorithm.
+inline ClippedTime
+TimeClip(double time)
+{
+    // Steps 1-2.
+    const double MaxTimeMagnitude = 8.64e15;
+    if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude)
+        return ClippedTime(mozilla::UnspecifiedNaN());
+
+    // Step 3.
+    return ClippedTime(ToInteger(time) + (+0.0));
+}
+
+// Produce a double Value from the given time.  Because times may be NaN,
+// prefer using this to manual canonicalization.
+inline Value
+TimeValue(ClippedTime time)
+{
+    return DoubleValue(JS::CanonicalizeNaN(time.toDouble()));
+}
+
+// Create a new Date object whose [[DateValue]] internal slot contains the
+// clipped |time|.  (Users who must represent times outside that range must use
+// another representation.)
+extern JS_PUBLIC_API(JSObject*)
+NewDateObject(JSContext* cx, ClippedTime time);
+
+// Year is a year, month is 0-11, day is 1-based.  The return value is a number
+// of milliseconds since the epoch.
+//
+// Consistent with the MakeDate algorithm defined in ECMAScript, this value is
+// *not* clipped!  Use JS::TimeClip if you need a clipped date.
 JS_PUBLIC_API(double)
 MakeDate(double year, unsigned month, unsigned day);
 
diff --git a/js/src/jit-test/tests/auto-regress/bug771946.js b/js/src/jit-test/tests/auto-regress/bug771946.js
index 75af67064a..3cfb590e14 100644
--- a/js/src/jit-test/tests/auto-regress/bug771946.js
+++ b/js/src/jit-test/tests/auto-regress/bug771946.js
@@ -1,4 +1,4 @@
 // Binary: cache/js-dbg-64-221f1a184f67-linux
 // Flags:
 //
-Date.prototype.setFullYear(Math.cos(1))
+new Date().setFullYear(Math.cos(1))
diff --git a/js/src/jit-test/tests/basic/bug1177907.js b/js/src/jit-test/tests/basic/bug1177907.js
new file mode 100644
index 0000000000..0967b8aceb
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug1177907.js
@@ -0,0 +1,4 @@
+// |jit-test| error: InternalError
+
+var Date_toString = newGlobal().Date.prototype.toString;
+(function f(){ f(Date_toString.call({})); })();
diff --git a/js/src/jit-test/tests/basic/eval-scopes.js b/js/src/jit-test/tests/basic/eval-scopes.js
index 35c519b32a..12f8396e91 100644
--- a/js/src/jit-test/tests/basic/eval-scopes.js
+++ b/js/src/jit-test/tests/basic/eval-scopes.js
@@ -11,8 +11,8 @@ function hasGname(f, v, hasIt = true) {
     try {
 	var b = bytecode(f);
 	if (b != "unavailable") {
-	    assertEq(b.contains(`getgname "${v}"`), hasIt);
-	    assertEq(b.contains(`getname "${v}"`), !hasIt);
+	    assertEq(b.includes(`getgname "${v}"`), hasIt);
+	    assertEq(b.includes(`getname "${v}"`), !hasIt);
 	}
     } catch (e) {
 	print(e.stack);
diff --git a/js/src/jit-test/tests/basic/function-gname.js b/js/src/jit-test/tests/basic/function-gname.js
index 34a0d84871..9c4e5f80d7 100644
--- a/js/src/jit-test/tests/basic/function-gname.js
+++ b/js/src/jit-test/tests/basic/function-gname.js
@@ -11,8 +11,8 @@ function hasGname(f, v) {
     try {
 	var b = bytecode(f);
 	if (b != "unavailable") {
-	    assertEq(b.contains(`getgname "${v}"`), true);
-	    assertEq(b.contains(`getname "${v}"`), false);
+	    assertEq(b.includes(`getgname "${v}"`), true);
+	    assertEq(b.includes(`getname "${v}"`), false);
 	}
     } catch (e) {
 	print(e.stack);
diff --git a/js/src/jit-test/tests/basic/testCrossCompartmentTransparency.js b/js/src/jit-test/tests/basic/testCrossCompartmentTransparency.js
index 148fd0527a..e1f389e6c3 100644
--- a/js/src/jit-test/tests/basic/testCrossCompartmentTransparency.js
+++ b/js/src/jit-test/tests/basic/testCrossCompartmentTransparency.js
@@ -15,7 +15,7 @@ var proxyStr = "Proxy.create(                              "+
 var proxy1 = g1.eval(proxyStr);
 var proxy2 = g2.eval(proxyStr);
 
-function test(str, f) {
+function test(str, f, isGeneric = false) {
     "use strict";
 
     var x = f(eval(str));
@@ -29,7 +29,7 @@ function test(str, f) {
         assertEq(Object.prototype.toString.call(e), "[object Error]");
         threw = true;
     }
-    assertEq(threw, true);
+    assertEq(threw, !isGeneric);
     threw = false;
     try {
         f(g2.eval("new Object()"));
@@ -37,7 +37,7 @@ function test(str, f) {
         assertEq(Object.prototype.toString.call(e), "[object Error]");
         threw = true;
     }
-    assertEq(threw, true);
+    assertEq(threw, !isGeneric);
     threw = false;
     try {
         f(proxy1);
@@ -45,7 +45,7 @@ function test(str, f) {
         assertEq(Object.prototype.toString.call(e), "[object Error]");
         threw = true;
     }
-    assertEq(threw, true);
+    assertEq(threw, !isGeneric);
     threw = false;
     try {
         f(proxy2);
@@ -53,7 +53,7 @@ function test(str, f) {
         assertEq(Object.prototype.toString.call(e), "[object Error]");
         threw = true;
     }
-    assertEq(threw, true);
+    assertEq(threw, !isGeneric);
 }
 
 test("new Boolean(true)", b => Boolean.prototype.toSource.call(b));
@@ -154,7 +154,7 @@ test("new Date()", d => justDontThrow(Date.prototype.toLocaleFormat.call(d)));
 test("new Date()", d => justDontThrow(Date.prototype.toTimeString.call(d)));
 test("new Date()", d => justDontThrow(Date.prototype.toDateString.call(d)));
 test("new Date()", d => justDontThrow(Date.prototype.toSource.call(d)));
-test("new Date()", d => justDontThrow(Date.prototype.toString.call(d)));
+test("new Date()", d => justDontThrow(Date.prototype.toString.call(d)), true);
 test("new Date()", d => justDontThrow(Date.prototype.valueOf.call(d)));
 
 throw "done";
diff --git a/js/src/js.msg b/js/src/js.msg
index fd1a7fbb6f..499558d76f 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -124,6 +124,7 @@ MSG_DEF(JSMSG_INVALID_NORMALIZE_FORM,  0, JSEXN_RANGEERR, "form must be one of '
 MSG_DEF(JSMSG_NEGATIVE_REPETITION_COUNT, 0, JSEXN_RANGEERR, "repeat count must be non-negative")
 MSG_DEF(JSMSG_NOT_A_CODEPOINT,         1, JSEXN_RANGEERR, "{0} is not a valid code point")
 MSG_DEF(JSMSG_RESULTING_STRING_TOO_LARGE, 0, JSEXN_RANGEERR, "repeat count must be less than infinity and not overflow maximum string size")
+MSG_DEF(JSMSG_DEPRECATED_STRING_CONTAINS, 0, JSEXN_NONE, "String.prototype.contains() is deprecated and will be removed in a future release; use String.prototype.includes() instead")
 
 // Number
 MSG_DEF(JSMSG_BAD_RADIX,               0, JSEXN_RANGEERR, "radix must be an integer at least 2 and no greater than 36")
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index dbd199be6f..09127d94d5 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -61,6 +61,7 @@
 #include "jit/JitCommon.h"
 #include "js/CharacterEncoding.h"
 #include "js/Conversions.h"
+#include "js/Date.h"
 #include "js/Proxy.h"
 #include "js/SliceBudget.h"
 #include "js/StructuredClone.h"
@@ -312,7 +313,7 @@ IterPerformanceStats(JSContext* cx,
             continue;
         }
 
-        if (!(*walker)(cx, group->data, closure)) {
+        if (!(*walker)(cx, group->data, group->uid, closure)) {
             // Issue in callback
             return false;
         }
@@ -5223,11 +5224,11 @@ JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min,
 }
 
 JS_PUBLIC_API(JSObject*)
-JS_NewDateObjectMsec(JSContext* cx, double msec)
+JS::NewDateObject(JSContext* cx, JS::ClippedTime time)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    return NewDateObjectMsec(cx, msec);
+    return NewDateObjectMsec(cx, time);
 }
 
 JS_PUBLIC_API(bool)
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index 7c2abc0266..861620104f 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4674,9 +4674,6 @@ MapEntries(JSContext* cx, HandleObject obj, MutableHandleValue rval);
 extern JS_PUBLIC_API(JSObject*)
 JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, int sec);
 
-extern JS_PUBLIC_API(JSObject*)
-JS_NewDateObjectMsec(JSContext* cx, double msec);
-
 /*
  * Infallible predicate to test whether obj is a date object.
  */
@@ -5401,6 +5398,9 @@ struct PerformanceGroup {
     // Performance data for this group.
     PerformanceData data;
 
+    // An id unique to this runtime.
+    const uint64_t uid;
+
     // `true` if an instance of `AutoStopwatch` is already monitoring
     // the performance of this performance group for this iteration
     // of the event loop, `false` otherwise.
@@ -5425,12 +5425,7 @@ struct PerformanceGroup {
         stopwatch_ = nullptr;
     }
 
-    explicit PerformanceGroup(void* key)
-      : stopwatch_(nullptr)
-      , iteration_(0)
-      , key_(key)
-      , refCount_(0)
-    { }
+    explicit PerformanceGroup(JSContext* cx, void* key);
     ~PerformanceGroup()
     {
         MOZ_ASSERT(refCount_ == 0);
@@ -5542,7 +5537,7 @@ extern JS_PUBLIC_API(PerformanceData*)
 GetPerformanceData(JSRuntime*);
 
 typedef bool
-(PerformanceStatsWalker)(JSContext* cx, const PerformanceData& stats, void* closure);
+(PerformanceStatsWalker)(JSContext* cx, const PerformanceData& stats, uint64_t uid, void* closure);
 
 /**
  * Extract the performance statistics.
diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp
index 9a9bae3c37..b250f4daed 100644
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -50,9 +50,12 @@ using namespace js;
 using mozilla::ArrayLength;
 using mozilla::IsFinite;
 using mozilla::IsNaN;
+using mozilla::NumbersAreIdentical;
 
 using JS::AutoCheckCannotGC;
+using JS::ClippedTime;
 using JS::GenericNaN;
+using JS::TimeClip;
 using JS::ToInteger;
 
 /*
@@ -350,7 +353,7 @@ MakeDate(double day, double time)
 JS_PUBLIC_API(double)
 JS::MakeDate(double year, unsigned month, unsigned day)
 {
-    return TimeClip(::MakeDate(MakeDay(year, month, day), 0));
+    return ::MakeDate(MakeDay(year, month, day), 0);
 }
 
 JS_PUBLIC_API(double)
@@ -566,14 +569,6 @@ RegionMatches(const char* s1, int s1off, const CharT* s2, int s2off, int count)
     return count == 0;
 }
 
-/* find UTC time from given date... no 1900 correction! */
-static double
-date_msecFromDate(double year, double mon, double mday, double hour,
-                  double min, double sec, double msec)
-{
-    return MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, msec));
-}
-
 /* ES6 20.3.3.4. */
 static bool
 date_UTC(JSContext* cx, unsigned argc, Value* vp)
@@ -644,7 +639,8 @@ date_UTC(JSContext* cx, unsigned argc, Value* vp)
     }
 
     // Step 16.
-    args.rval().setDouble(TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli))));
+    ClippedTime time = TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
+    args.rval().set(TimeValue(time));
     return true;
 }
 
@@ -777,7 +773,7 @@ DaysInMonth(int year, int month)
  */
 template 
 static bool
-ParseISODate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo)
+ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo)
 {
     size_t i = 0;
     int tzMul = 1;
@@ -873,19 +869,16 @@ ParseISODate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo
 
     month -= 1; /* convert month to 0-based */
 
-    double msec = date_msecFromDate(dateMul * double(year), month, day,
-                                    hour, min, sec, frac * 1000.0);
+    double msec = MakeDate(MakeDay(dateMul * double(year), month, day),
+                           MakeTime(hour, min, sec, frac * 1000.0));
 
     if (isLocalTime)
         msec = UTC(msec, dtInfo);
     else
         msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
 
-    if (msec < -8.64e15 || msec > 8.64e15)
-        return false;
-
-    *result = msec;
-    return true;
+    *result = TimeClip(msec);
+    return NumbersAreIdentical(msec, result->toDouble());
 
 #undef PEEK
 #undef NEED
@@ -895,7 +888,7 @@ ParseISODate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo
 
 template 
 static bool
-ParseDate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo)
+ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo)
 {
     if (ParseISODate(s, length, result, dtInfo))
         return true;
@@ -1156,19 +1149,19 @@ ParseDate(const CharT* s, size_t length, double* result, DateTimeInfo* dtInfo)
     if (hour < 0)
         hour = 0;
 
-    double msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
+    double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0));
 
     if (tzOffset == -1) /* no time zone specified, have to use local */
         msec = UTC(msec, dtInfo);
     else
         msec += tzOffset * msPerMinute;
 
-    *result = msec;
+    *result = TimeClip(msec);
     return true;
 }
 
 static bool
-ParseDate(JSLinearString* s, double* result, DateTimeInfo* dtInfo)
+ParseDate(JSLinearString* s, ClippedTime* result, DateTimeInfo* dtInfo)
 {
     AutoCheckCannotGC nogc;
     return s->hasLatin1Chars()
@@ -1193,45 +1186,44 @@ date_parse(JSContext* cx, unsigned argc, Value* vp)
     if (!linearStr)
         return false;
 
-    double result;
+    ClippedTime result;
     if (!ParseDate(linearStr, &result, &cx->runtime()->dateTimeInfo)) {
         args.rval().setNaN();
         return true;
     }
 
-    result = TimeClip(result);
-    args.rval().setNumber(result);
+    args.rval().set(TimeValue(result));
     return true;
 }
 
-static inline double
+static ClippedTime
 NowAsMillis()
 {
-    return (double) (PRMJ_Now() / PRMJ_USEC_PER_MSEC);
+    return TimeClip(static_cast(PRMJ_Now()) / PRMJ_USEC_PER_MSEC);
 }
 
 bool
 js::date_now(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setDouble(NowAsMillis());
+    args.rval().set(TimeValue(NowAsMillis()));
     return true;
 }
 
 void
-DateObject::setUTCTime(double t)
+DateObject::setUTCTime(ClippedTime t)
 {
     for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
         setReservedSlot(ind, UndefinedValue());
 
-    setFixedSlot(UTC_TIME_SLOT, DoubleValue(t));
+    setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
 }
 
 void
-DateObject::setUTCTime(double t, MutableHandleValue vp)
+DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp)
 {
     setUTCTime(t);
-    vp.setDouble(t);
+    vp.set(TimeValue(t));
 }
 
 void
@@ -1694,7 +1686,7 @@ date_setTime_impl(JSContext* cx, const CallArgs& args)
 {
     Rooted dateObj(cx, &args.thisv().toObject().as());
     if (args.length() == 0) {
-        dateObj->setUTCTime(GenericNaN(), args.rval());
+        dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
         return true;
     }
 
@@ -1759,7 +1751,7 @@ date_setMilliseconds_impl(JSContext* cx, const CallArgs& args)
     double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
 
     /* Step 3. */
-    double u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo));
 
     /* Steps 4-5. */
     dateObj->setUTCTime(u, args.rval());
@@ -1789,7 +1781,7 @@ date_setUTCMilliseconds_impl(JSContext* cx, const CallArgs& args)
     double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
 
     /* Step 3. */
-    double v = TimeClip(MakeDate(Day(t), time));
+    ClippedTime v = TimeClip(MakeDate(Day(t), time));
 
     /* Steps 4-5. */
     dateObj->setUTCTime(v, args.rval());
@@ -1826,7 +1818,7 @@ date_setSeconds_impl(JSContext* cx, const CallArgs& args)
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
 
     /* Step 5. */
-    double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
 
     /* Steps 6-7. */
     dateObj->setUTCTime(u, args.rval());
@@ -1863,7 +1855,7 @@ date_setUTCSeconds_impl(JSContext* cx, const CallArgs& args)
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
 
     /* Step 5. */
-    double v = TimeClip(date);
+    ClippedTime v = TimeClip(date);
 
     /* Steps 6-7. */
     dateObj->setUTCTime(v, args.rval());
@@ -1905,7 +1897,7 @@ date_setMinutes_impl(JSContext* cx, const CallArgs& args)
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
 
     /* Step 6. */
-    double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(u, args.rval());
@@ -1947,7 +1939,7 @@ date_setUTCMinutes_impl(JSContext* cx, const CallArgs& args)
     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
 
     /* Step 6. */
-    double v = TimeClip(date);
+    ClippedTime v = TimeClip(date);
 
     /* Steps 7-8. */
     dateObj->setUTCTime(v, args.rval());
@@ -1994,7 +1986,7 @@ date_setHours_impl(JSContext* cx, const CallArgs& args)
     double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
 
     /* Step 6. */
-    double u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(u, args.rval());
@@ -2041,7 +2033,7 @@ date_setUTCHours_impl(JSContext* cx, const CallArgs& args)
     double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
 
     /* Step 7. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 8-9. */
     dateObj->setUTCTime(v, args.rval());
@@ -2073,7 +2065,7 @@ date_setDate_impl(JSContext* cx, const CallArgs& args)
     double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t));
 
     /* Step 4. */
-    double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
 
     /* Steps 5-6. */
     dateObj->setUTCTime(u, args.rval());
@@ -2105,7 +2097,7 @@ date_setUTCDate_impl(JSContext* cx, const CallArgs& args)
     double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t));
 
     /* Step 4. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 5-6. */
     dateObj->setUTCTime(v, args.rval());
@@ -2162,7 +2154,7 @@ date_setMonth_impl(JSContext* cx, const CallArgs& args)
     double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
 
     /* Step 5. */
-    double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
 
     /* Steps 6-7. */
     dateObj->setUTCTime(u, args.rval());
@@ -2199,7 +2191,7 @@ date_setUTCMonth_impl(JSContext* cx, const CallArgs& args)
     double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
 
     /* Step 5. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 6-7. */
     dateObj->setUTCTime(v, args.rval());
@@ -2257,7 +2249,7 @@ date_setFullYear_impl(JSContext* cx, const CallArgs& args)
     double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
 
     /* Step 6. */
-    double u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
+    ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo));
 
     /* Steps 7-8. */
     dateObj->setUTCTime(u, args.rval());
@@ -2299,7 +2291,7 @@ date_setUTCFullYear_impl(JSContext* cx, const CallArgs& args)
     double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
 
     /* Step 6. */
-    double v = TimeClip(newDate);
+    ClippedTime v = TimeClip(newDate);
 
     /* Steps 7-8. */
     dateObj->setUTCTime(v, args.rval());
@@ -2329,7 +2321,7 @@ date_setYear_impl(JSContext* cx, const CallArgs& args)
 
     /* Step 3. */
     if (IsNaN(y)) {
-        dateObj->setUTCTime(GenericNaN(), args.rval());
+        dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
         return true;
     }
 
@@ -2373,7 +2365,7 @@ static const char * const months[] =
 static void
 print_gmt_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(TimeClip(utctime) == utctime);
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
     JS_snprintf(buf, size, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
                 days[int(WeekDay(utctime))],
                 int(DateFromTime(utctime)),
@@ -2387,7 +2379,7 @@ print_gmt_string(char* buf, size_t size, double utctime)
 static void
 print_iso_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(TimeClip(utctime) == utctime);
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
     JS_snprintf(buf, size, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
                 int(YearFromTime(utctime)),
                 int(MonthFromTime(utctime)) + 1,
@@ -2401,7 +2393,7 @@ print_iso_string(char* buf, size_t size, double utctime)
 static void
 print_iso_extended_string(char* buf, size_t size, double utctime)
 {
-    MOZ_ASSERT(TimeClip(utctime) == utctime);
+    MOZ_ASSERT(NumbersAreIdentical(TimeClip(utctime).toDouble(), utctime));
     JS_snprintf(buf, size, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
                 int(YearFromTime(utctime)),
                 int(MonthFromTime(utctime)) + 1,
@@ -2557,7 +2549,7 @@ date_format(JSContext* cx, double date, formatspec format, MutableHandleValue rv
     if (!IsFinite(date)) {
         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
     } else {
-        MOZ_ASSERT(TimeClip(date) == date);
+        MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).toDouble(), date));
 
         double local = LocalTime(date, &cx->runtime()->dateTimeInfo);
 
@@ -2876,18 +2868,30 @@ date_toSource(JSContext* cx, unsigned argc, Value* vp)
 }
 #endif
 
-MOZ_ALWAYS_INLINE bool
-date_toString_impl(JSContext* cx, const CallArgs& args)
-{
-    return date_format(cx, args.thisv().toObject().as().UTCTime().toNumber(),
-                       FORMATSPEC_FULL, args.rval());
-}
-
+// ES6 final draft 20.3.4.41.
 static bool
 date_toString(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    return CallNonGenericMethod(cx, args);
+    // Step 2.a. (reordered)
+    double tv = GenericNaN();
+    if (args.thisv().isObject()) {
+        // Step 1.
+        RootedObject obj(cx, &args.thisv().toObject());
+        // Step 2.
+        if (ObjectClassIs(obj, ESClass_Date, cx)) {
+            // Step 3.a.
+            RootedValue unboxed(cx);
+            if (!Unbox(cx, obj, &unboxed))
+                return false;\
+            tv = unboxed.toNumber();
+        }
+        // ObjectClassIs can throw for objects from other compartments.
+        if (cx->isExceptionPending())
+            return false;
+    }
+    // Step 4.
+    return date_format(cx, tv, FORMATSPEC_FULL, args.rval());
 }
 
 MOZ_ALWAYS_INLINE bool
@@ -2972,9 +2976,9 @@ static const JSFunctionSpec date_methods[] = {
 };
 
 static bool
-NewDateObject(JSContext* cx, const CallArgs& args, double d)
+NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t)
 {
-    JSObject* obj = NewDateObjectMsec(cx, d);
+    JSObject* obj = NewDateObjectMsec(cx, t);
     if (!obj)
         return false;
 
@@ -2983,9 +2987,9 @@ NewDateObject(JSContext* cx, const CallArgs& args, double d)
 }
 
 static bool
-ToDateString(JSContext* cx, const CallArgs& args, double d)
+ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t)
 {
-    return date_format(cx, d, FORMATSPEC_FULL, args.rval());
+    return date_format(cx, t.toDouble(), FORMATSPEC_FULL, args.rval());
 }
 
 static bool
@@ -2993,7 +2997,7 @@ DateNoArguments(JSContext* cx, const CallArgs& args)
 {
     MOZ_ASSERT(args.length() == 0);
 
-    double now = NowAsMillis();
+    ClippedTime now = NowAsMillis();
 
     if (args.isConstructing())
         return NewDateObject(cx, args, now);
@@ -3007,7 +3011,7 @@ DateOneArgument(JSContext* cx, const CallArgs& args)
     MOZ_ASSERT(args.length() == 1);
 
     if (args.isConstructing()) {
-        double d;
+        ClippedTime t;
 
         if (!ToPrimitive(cx, args[0]))
             return false;
@@ -3017,17 +3021,16 @@ DateOneArgument(JSContext* cx, const CallArgs& args)
             if (!linearStr)
                 return false;
 
-            if (!ParseDate(linearStr, &d, &cx->runtime()->dateTimeInfo))
-                d = GenericNaN();
-            else
-                d = TimeClip(d);
+            if (!ParseDate(linearStr, &t, &cx->runtime()->dateTimeInfo))
+                t = ClippedTime::invalid();
         } else {
+            double d;
             if (!ToNumber(cx, args[0], &d))
                 return false;
-            d = TimeClip(d);
+            t = TimeClip(d);
         }
 
-        return NewDateObject(cx, args, d);
+        return NewDateObject(cx, args, t);
     }
 
     return ToDateString(cx, args, NowAsMillis());
@@ -3127,11 +3130,16 @@ js::DateConstructor(JSContext* cx, unsigned argc, Value* vp)
     return DateMultipleArguments(cx, args);
 }
 
+// ES6 final draft 20.3.4.
+static JSObject*
+CreateDatePrototype(JSContext* cx, JSProtoKey key)
+{
+    return cx->global()->createBlankPrototype(cx, &DateObject::protoClass_);
+}
+
 static bool
 FinishDateClassInit(JSContext* cx, HandleObject ctor, HandleObject proto)
 {
-    proto->as().setUTCTime(GenericNaN());
-
     /*
      * Date.prototype.toGMTString has the same initial value as
      * Date.prototype.toUTCString.
@@ -3139,8 +3147,8 @@ FinishDateClassInit(JSContext* cx, HandleObject ctor, HandleObject proto)
     RootedValue toUTCStringFun(cx);
     RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
     RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
-    return NativeGetProperty(cx, proto.as(), toUTCStringId, &toUTCStringFun) &&
-           NativeDefineProperty(cx, proto.as(), toGMTStringId, toUTCStringFun,
+    return NativeGetProperty(cx, proto.as(), toUTCStringId, &toUTCStringFun) &&
+           NativeDefineProperty(cx, proto.as(), toGMTStringId, toUTCStringFun,
                                 nullptr, nullptr, 0);
 }
 
@@ -3163,7 +3171,7 @@ const Class DateObject::class_ = {
     nullptr, /* trace */
     {
         GenericCreateConstructor,
-        GenericCreatePrototype,
+        CreateDatePrototype,
         date_static_methods,
         nullptr,
         date_methods,
@@ -3172,13 +3180,41 @@ const Class DateObject::class_ = {
     }
 };
 
-JS_FRIEND_API(JSObject*)
-js::NewDateObjectMsec(JSContext *cx, double msec_time)
+const Class DateObject::protoClass_ = {
+    js_Object_str,
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* mayResolve */
+    nullptr, /* convert */
+    nullptr, /* finalize */
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    nullptr, /* trace  */
+    {
+        DELEGATED_CLASSSPEC(&DateObject::class_.spec),
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        nullptr,
+        ClassSpec::IsDelegated
+    }
+};
+
+JSObject*
+js::NewDateObjectMsec(JSContext* cx, ClippedTime t)
 {
     JSObject* obj = NewBuiltinClassInstance(cx, &DateObject::class_);
     if (!obj)
         return nullptr;
-    obj->as().setUTCTime(msec_time);
+    obj->as().setUTCTime(t);
     return obj;
 }
 
@@ -3187,8 +3223,8 @@ js::NewDateObject(JSContext *cx, int year, int mon, int mday,
                 int hour, int min, int sec)
 {
     MOZ_ASSERT(mon < 12);
-    double msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
-    return NewDateObjectMsec(cx, UTC(msec_time, &cx->runtime()->dateTimeInfo));
+    double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
+    return NewDateObjectMsec(cx, TimeClip(UTC(msec_time, &cx->runtime()->dateTimeInfo)));
 }
 
 JS_FRIEND_API(bool)
diff --git a/js/src/jsdate.h b/js/src/jsdate.h
index 37b4f51e39..397a1906b6 100644
--- a/js/src/jsdate.h
+++ b/js/src/jsdate.h
@@ -13,9 +13,12 @@
 
 #include "jstypes.h"
 
+#include "js/Date.h"
 #include "js/RootingAPI.h"
 #include "js/TypeDecls.h"
 
+#include "vm/DateTime.h"
+
 namespace js {
 
 /*
@@ -26,8 +29,8 @@ namespace js {
  * Construct a new Date Object from a time value given in milliseconds UTC
  * since the epoch.
  */
-extern JS_FRIEND_API(JSObject*)
-NewDateObjectMsec(JSContext *cx, double msec_time);
+extern JSObject*
+NewDateObjectMsec(JSContext* cx, JS::ClippedTime t);
 
 /*
  * Construct a new Date Object from an exploded local time value.
diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp
index 52bf1c7355..e8c5c88048 100644
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -612,7 +612,7 @@ ToLowerCase(JSContext* cx, JSLinearString* str)
         size_t i = 0;
         for (; i < length; i++) {
             char16_t c = chars[i];
-            if (unicode::ToLowerCase(c) != c)
+            if (unicode::CanLowerCase(c))
                 break;
         }
 
@@ -731,7 +731,7 @@ ToUpperCase(JSContext* cx, JSLinearString* str)
         size_t i = 0;
         for (; i < length; i++) {
             char16_t c = chars[i];
-            if (unicode::ToUpperCase(c) != c)
+            if (unicode::CanUpperCase(c))
                 break;
         }
 
@@ -901,13 +901,13 @@ str_normalize(JSContext* cx, unsigned argc, Value* vp)
             return false;
 
         // Step 7.
-        if (formStr == cx->names().NFC) {
+        if (EqualStrings(formStr, cx->names().NFC)) {
             form = UNORM_NFC;
-        } else if (formStr == cx->names().NFD) {
+        } else if (EqualStrings(formStr, cx->names().NFD)) {
             form = UNORM_NFD;
-        } else if (formStr == cx->names().NFKC) {
+        } else if (EqualStrings(formStr, cx->names().NFKC)) {
             form = UNORM_NFKC;
-        } else if (formStr == cx->names().NFKD) {
+        } else if (EqualStrings(formStr, cx->names().NFKD)) {
             form = UNORM_NFKD;
         } else {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
@@ -1518,7 +1518,8 @@ RopeMatch(JSContext* cx, JSRope* text, JSLinearString* pat, int* match)
     return true;
 }
 
-/* ES6 2015 ST 21.1.3.7 String.prototype.includes */
+
+/* ES6 draft rc4 21.1.3.7. */
 static bool
 str_includes(JSContext* cx, unsigned argc, Value* vp)
 {
@@ -1575,10 +1576,16 @@ str_includes(JSContext* cx, unsigned argc, Value* vp)
     return true;
 }
 
-/* ES6 draft  "foo"[method](re), TypeError);
 
diff --git a/js/src/tests/ecma_6/String/normalize-form-non-atom.js b/js/src/tests/ecma_6/String/normalize-form-non-atom.js
new file mode 100644
index 0000000000..ee6e12c37f
--- /dev/null
+++ b/js/src/tests/ecma_6/String/normalize-form-non-atom.js
@@ -0,0 +1,18 @@
+var BUGNUMBER = 1145326;
+var summary = 'String.prototype.normalize error when normalization form parameter is not an atom';
+
+print(BUGNUMBER + ": " + summary);
+
+function test() {
+  assertEq("abc".normalize("NFKC".split("").join("")), "abc");
+  assertEq("abc".normalize("NFKCabc".replace("abc", "")), "abc");
+  assertEq("abc".normalize("N" + "F" + "K" + "C"), "abc");
+}
+
+if ("normalize" in String.prototype) {
+  // String.prototype.normalize is not enabled in all builds.
+  test();
+}
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
diff --git a/js/src/tests/js1_4/Regress/date-001-n.js b/js/src/tests/js1_4/Regress/date-001-n.js
deleted file mode 100644
index cf18b33fe2..0000000000
--- a/js/src/tests/js1_4/Regress/date-001-n.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
-
-
-/**
- *  File Name:          date-001-n.js
- *  Description:
- *
- *  http://scopus.mcom.com/bugsplat/show_bug.cgi?id=299903
- *
- *  Author:             christine@netscape.com
- *  Date:               11 August 1998
- */
-var SECTION = "date-001-n.js";
-var VERSION = "JS1_4";
-var TITLE   = "Regression test case for 299903";
-var BUGNUMBER="299903";
-
-startTest();
-
-writeHeaderToLog( SECTION + " "+ TITLE);
-
-function MyDate() {
-  this.foo = "bar";
-}
-MyDate.prototype = new Date();
-
-DESCRIPTION =
-  "function MyDate() { this.foo = \"bar\"; }; MyDate.prototype = new Date(); new MyDate().toString()";
-EXPECTED = "error";
-
-new TestCase(
-  SECTION,
-  "function MyDate() { this.foo = \"bar\"; }; "+
-  "MyDate.prototype = new Date(); " +
-  "new MyDate().toString()",
-  "error",
-  new MyDate().toString() );
-
-test();
diff --git a/js/src/vm/DateObject.h b/js/src/vm/DateObject.h
index 1eb0114ae5..7b28b9db33 100644
--- a/js/src/vm/DateObject.h
+++ b/js/src/vm/DateObject.h
@@ -9,6 +9,7 @@
 
 #include "jsobj.h"
 
+#include "js/Date.h"
 #include "js/Value.h"
 
 namespace js {
@@ -40,14 +41,22 @@ class DateObject : public NativeObject
 
   public:
     static const Class class_;
+    static const Class protoClass_;
 
-    inline const js::Value& UTCTime() const {
+    JS::ClippedTime clippedTime() const {
+        double t = getFixedSlot(UTC_TIME_SLOT).toDouble();
+        JS::ClippedTime clipped = JS::TimeClip(t);
+        MOZ_ASSERT(mozilla::NumbersAreIdentical(clipped.toDouble(), t));
+        return clipped;
+    }
+
+    const js::Value& UTCTime() const {
         return getFixedSlot(UTC_TIME_SLOT);
     }
 
     // Set UTC time to a given time and invalidate cached local time.
-    void setUTCTime(double t);
-    void setUTCTime(double t, MutableHandleValue vp);
+    void setUTCTime(JS::ClippedTime t);
+    void setUTCTime(JS::ClippedTime t, MutableHandleValue vp);
 
     inline double cachedLocalTime(DateTimeInfo* dtInfo);
 
diff --git a/js/src/vm/DateTime.h b/js/src/vm/DateTime.h
index 4b00a64234..4ec477b3ee 100644
--- a/js/src/vm/DateTime.h
+++ b/js/src/vm/DateTime.h
@@ -39,19 +39,6 @@ const unsigned SecondsPerDay = SecondsPerHour * 24;
 
 const double StartOfTime = -8.64e15;
 const double EndOfTime = 8.64e15;
-const double MaxTimeMagnitude = 8.64e15;
-
-/* ES5 15.9.1.14. */
-inline double
-TimeClip(double time)
-{
-    /* Steps 1-2. */
-    if (!mozilla::IsFinite(time) || mozilla::Abs(time) > MaxTimeMagnitude)
-        return JS::GenericNaN();
-
-    /* Step 3. */
-    return JS::ToInteger(time + (+0.0));
-}
 
 /*
  * Stores date/time information, particularly concerning the current local
diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp
index 5dd3d5ceab..bb3db740a5 100644
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -153,8 +153,8 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle global, JS
     // |createPrototype|, |prototypeFunctions|, and |prototypeProperties|
     // should all be null.
     RootedObject proto(cx);
-    if (clasp->spec.createPrototype) {
-        proto = clasp->spec.createPrototype(cx, key);
+    if (clasp->spec.createPrototypeHook()) {
+        proto = clasp->spec.createPrototypeHook()(cx, key);
         if (!proto)
             return false;
 
@@ -162,7 +162,7 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle global, JS
     }
 
     // Create the constructor.
-    RootedObject ctor(cx, clasp->spec.createConstructor(cx, key));
+    RootedObject ctor(cx, clasp->spec.createConstructorHook()(cx, key));
     if (!ctor)
         return false;
 
@@ -178,19 +178,19 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle global, JS
     // Define any specified functions and properties, unless we're a dependent
     // standard class (in which case they live on the prototype).
     if (!StandardClassIsDependent(key)) {
-        if (const JSFunctionSpec* funs = clasp->spec.prototypeFunctions) {
+        if (const JSFunctionSpec* funs = clasp->spec.prototypeFunctions()) {
             if (!JS_DefineFunctions(cx, proto, funs, DontDefineLateProperties))
                 return false;
         }
-        if (const JSPropertySpec* props = clasp->spec.prototypeProperties) {
+        if (const JSPropertySpec* props = clasp->spec.prototypeProperties()) {
             if (!JS_DefineProperties(cx, proto, props))
                 return false;
         }
-        if (const JSFunctionSpec* funs = clasp->spec.constructorFunctions) {
+        if (const JSFunctionSpec* funs = clasp->spec.constructorFunctions()) {
             if (!JS_DefineFunctions(cx, ctor, funs, DontDefineLateProperties))
                 return false;
         }
-        if (const JSPropertySpec *props = clasp->spec.constructorProperties) {
+        if (const JSPropertySpec* props = clasp->spec.constructorProperties()) {
             if (!JS_DefineProperties(cx, ctor, props))
                 return false;
         }
@@ -201,7 +201,7 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle global, JS
         return false;
 
     // Call the post-initialization hook, if provided.
-    if (clasp->spec.finishInit && !clasp->spec.finishInit(cx, ctor, proto))
+    if (clasp->spec.finishInitHook() && !clasp->spec.finishInitHook()(cx, ctor, proto))
         return false;
 
     if (clasp->spec.shouldDefineConstructor()) {
@@ -354,11 +354,11 @@ InitBareBuiltinCtor(JSContext* cx, Handle global, JSProtoKey prot
     MOZ_ASSERT(cx->runtime()->isSelfHostingGlobal(global));
     const Class* clasp = ProtoKeyToClass(protoKey);
     RootedObject proto(cx);
-    proto = clasp->spec.createPrototype(cx, protoKey);
+    proto = clasp->spec.createPrototypeHook()(cx, protoKey);
     if (!proto)
         return false;
 
-    RootedObject ctor(cx, clasp->spec.createConstructor(cx, protoKey));
+    RootedObject ctor(cx, clasp->spec.createConstructorHook()(cx, protoKey));
     if (!ctor)
         return false;
 
@@ -426,17 +426,23 @@ GlobalObject::isRuntimeCodeGenEnabled(JSContext* cx, Handle globa
 }
 
 /* static */ bool
-GlobalObject::warnOnceAbout(JSContext* cx, HandleObject obj, uint32_t slot, unsigned errorNumber)
+GlobalObject::warnOnceAbout(JSContext* cx, HandleObject obj, WarnOnceFlag flag,
+                            unsigned errorNumber)
 {
     Rooted global(cx, &obj->global());
-    HeapSlot& v = global->getSlotRef(slot);
-    if (v.isUndefined()) {
+    HeapSlot& v = global->getSlotRef(WARNED_ONCE_FLAGS);
+    MOZ_ASSERT_IF(!v.isUndefined(), v.toInt32());
+    int32_t flags = v.isUndefined() ? 0 : v.toInt32();
+    if (!(flags & flag)) {
         if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
                                           errorNumber))
         {
             return false;
         }
-        v.init(global, HeapSlot::Slot, slot, BooleanValue(true));
+        if (v.isUndefined())
+            v.init(global, HeapSlot::Slot, WARNED_ONCE_FLAGS, Int32Value(flags | flag));
+        else
+            v.set(global, HeapSlot::Slot, WARNED_ONCE_FLAGS, Int32Value(flags | flag));
     }
     return true;
 }
diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h
index 0bf40335da..fecb54aed8 100644
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -108,9 +108,8 @@ class GlobalObject : public NativeObject
     static const unsigned NUMBER_FORMAT_PROTO     = COLLATOR_PROTO + 1;
     static const unsigned DATE_TIME_FORMAT_PROTO  = NUMBER_FORMAT_PROTO + 1;
     static const unsigned REGEXP_STATICS          = DATE_TIME_FORMAT_PROTO + 1;
-    static const unsigned WARNED_WATCH_DEPRECATED = REGEXP_STATICS + 1;
-    static const unsigned WARNED_PROTO_SETTING_SLOW = WARNED_WATCH_DEPRECATED + 1;
-    static const unsigned RUNTIME_CODEGEN_ENABLED = WARNED_PROTO_SETTING_SLOW + 1;
+    static const unsigned WARNED_ONCE_FLAGS       = REGEXP_STATICS + 1;
+    static const unsigned RUNTIME_CODEGEN_ENABLED = WARNED_ONCE_FLAGS + 1;
     static const unsigned DEBUGGERS               = RUNTIME_CODEGEN_ENABLED + 1;
     static const unsigned INTRINSICS              = DEBUGGERS + 1;
     static const unsigned FLOAT32X4_TYPE_DESCR    = INTRINSICS + 1;
@@ -129,12 +128,18 @@ class GlobalObject : public NativeObject
     static_assert(JSCLASS_GLOBAL_SLOT_COUNT == RESERVED_SLOTS,
                   "global object slot counts are inconsistent");
 
+    enum WarnOnceFlag : int32_t {
+        WARN_WATCH_DEPRECATED                   = 0x00000001,
+        WARN_PROTO_SETTING_SLOW                 = 0x00000002,
+        WARN_STRING_CONTAINS_DEPRECATED         = 0x00000004
+    };
+
     // Emit the specified warning if the given slot in |obj|'s global isn't
     // true, then set the slot to true.  Thus calling this method warns once
     // for each global object it's called on, and every other call does
     // nothing.
     static bool
-    warnOnceAbout(JSContext* cx, HandleObject obj, uint32_t slot, unsigned errorNumber);
+    warnOnceAbout(JSContext* cx, HandleObject obj, WarnOnceFlag flag, unsigned errorNumber);
 
 
   public:
@@ -638,14 +643,20 @@ class GlobalObject : public NativeObject
     static bool warnOnceAboutWatch(JSContext* cx, HandleObject obj) {
         // Temporarily disabled until we've provided a watch/unwatch workaround for
         // debuggers like Firebug (bug 934669).
-        //return warnOnceAbout(cx, obj, WARNED_WATCH_DEPRECATED, JSMSG_OBJECT_WATCH_DEPRECATED);
+        //return warnOnceAbout(cx, obj, WARN_WATCH_DEPRECATED, JSMSG_OBJECT_WATCH_DEPRECATED);
         return true;
     }
 
     // Warn about use of the given __proto__ setter to attempt to mutate an
     // object's [[Prototype]], if no prior warning was given.
     static bool warnOnceAboutPrototypeMutation(JSContext* cx, HandleObject protoSetter) {
-        return warnOnceAbout(cx, protoSetter, WARNED_PROTO_SETTING_SLOW, JSMSG_PROTO_SETTING_SLOW);
+        return warnOnceAbout(cx, protoSetter, WARN_PROTO_SETTING_SLOW, JSMSG_PROTO_SETTING_SLOW);
+    }
+
+    // Warn about use of the deprecated String.prototype.contains method
+    static bool warnOnceAboutStringContains(JSContext *cx, HandleObject strContains) {
+        return warnOnceAbout(cx, strContains, WARN_STRING_CONTAINS_DEPRECATED,
+                             JSMSG_DEPRECATED_STRING_CONTAINS);
     }
 
     static bool getOrCreateEval(JSContext* cx, Handle global,
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index 8b6ce9aac0..a35b7f0e8d 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -934,7 +934,7 @@ js::PerformanceGroupHolder::getGroup(JSContext* cx)
         group_ = ptr->value();
         MOZ_ASSERT(group_);
     } else {
-        group_ = runtime_->new_(key);
+        group_ = runtime_->new_(cx, key);
         runtime_->stopwatch.groups_.add(ptr, key, group_);
     }
 
@@ -949,6 +949,15 @@ js::GetPerformanceData(JSRuntime* rt)
     return &rt->stopwatch.performance;
 }
 
+js::PerformanceGroup::PerformanceGroup(JSContext* cx, void* key)
+  : uid(cx->runtime()->stopwatch.uniqueId())
+  , stopwatch_(nullptr)
+  , iteration_(0)
+  , key_(key)
+  , refCount_(0)
+{
+}
+
 void
 JS_SetCurrentPerfGroupCallback(JSRuntime *rt, JSCurrentPerfGroupCallback cb)
 {
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index 259a482f7c..96733efe40 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1490,6 +1490,7 @@ struct JSRuntime : public JS::shadow::Runtime,
           , currentPerfGroupCallback(nullptr)
           , isMonitoringJank_(false)
           , isMonitoringCPOW_(false)
+          , idCounter_(0)
         { }
 
         /**
@@ -1543,6 +1544,13 @@ struct JSRuntime : public JS::shadow::Runtime,
             return isMonitoringCPOW_;
         }
 
+        /**
+         * Return a identifier for a group, unique to the runtime.
+         */
+        uint64_t uniqueId() {
+            return idCounter_++;
+        }
+
         // Some systems have non-monotonic clocks. While we cannot
         // improve the precision, we can make sure that our measures
         // are monotonic nevertheless. We do this by storing the
@@ -1593,6 +1601,11 @@ struct JSRuntime : public JS::shadow::Runtime,
          */
         bool isMonitoringJank_;
         bool isMonitoringCPOW_;
+
+        /**
+         * A counter used to generate unique identifiers for groups.
+         */
+        uint64_t idCounter_;
     };
     Stopwatch stopwatch;
 };
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index 455ede5946..cef0bba13f 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -25,6 +25,7 @@
 #include "builtin/TypedObject.h"
 #include "builtin/WeakSetObject.h"
 #include "gc/Marking.h"
+#include "js/Date.h"
 #include "vm/Compression.h"
 #include "vm/GeneratorObject.h"
 #include "vm/Interpreter.h"
@@ -1844,7 +1845,7 @@ CloneObject(JSContext* cx, HandleNativeObject selfHostedObject)
         MOZ_ASSERT(source->isPermanentAtom());
         clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
     } else if (selfHostedObject->is()) {
-        clone = JS_NewDateObjectMsec(cx, selfHostedObject->as().UTCTime().toNumber());
+        clone = JS::NewDateObject(cx, selfHostedObject->as().clippedTime());
     } else if (selfHostedObject->is()) {
         clone = BooleanObject::create(cx, selfHostedObject->as().unbox());
     } else if (selfHostedObject->is()) {
diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp
index 93dbcaffee..fd80a9997e 100644
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -41,6 +41,7 @@
 #include "jswrapper.h"
 
 #include "builtin/MapObject.h"
+#include "js/Date.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
@@ -54,6 +55,7 @@ using mozilla::BitwiseCast;
 using mozilla::IsNaN;
 using mozilla::LittleEndian;
 using mozilla::NativeEndian;
+using mozilla::NumbersAreIdentical;
 using JS::CanonicalizeNaN;
 
 // When you make updates here, make sure you consider whether you need to bump the
@@ -1634,12 +1636,13 @@ JSStructuredCloneReader::startRead(MutableHandleValue vp)
         double d;
         if (!in.readDouble(&d) || !checkDouble(d))
             return false;
-        if (!IsNaN(d) && d != TimeClip(d)) {
+        JS::ClippedTime t = JS::TimeClip(d);
+        if (!NumbersAreIdentical(d, t.toDouble())) {
             JS_ReportErrorNumber(context(), GetErrorMessage, nullptr,
                                  JSMSG_SC_BAD_SERIALIZED_DATA, "date");
             return false;
         }
-        JSObject* obj = NewDateObjectMsec(context(), d);
+        JSObject* obj = NewDateObjectMsec(context(), t);
         if (!obj)
             return false;
         vp.setObject(*obj);
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
index b71641bb5d..5efe6556e3 100644
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -1836,8 +1836,8 @@ const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = {
 // @@toStringTag) a custom class.  The third requirement mandates that each
 // prototype's class have the relevant typed array's cached JSProtoKey in them.
 // Thus we need one class with cached prototype per kind of typed array, with a
-// dummy createConstructor to placate js::ClassSpec::defined().
-#define IMPL_TYPED_ARRAY_PROTO_CLASS(typedArray) \
+// delegated ClassSpec.
+#define IMPL_TYPED_ARRAY_PROTO_CLASS(typedArray, i) \
 { \
     /*
      * Actually ({}).toString.call(Uint8Array.prototype) should throw, because
@@ -1862,27 +1862,27 @@ const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = {
     nullptr, /* construct */ \
     nullptr, /* trace  */ \
     { \
-        typedArray::createConstructor, \
-        typedArray::createPrototype, \
+        DELEGATED_CLASSSPEC(&TypedArrayObject::classes[i].spec), \
         nullptr, \
         nullptr, \
         nullptr, \
         nullptr, \
         nullptr, \
-        JSProto_TypedArray \
+        nullptr, \
+        JSProto_TypedArray | ClassSpec::IsDelegated \
     } \
 }
 
 const Class TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = {
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Int8Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Int16Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint16Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Int32Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint32Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Float32Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Float64Array),
-    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8ClampedArray)
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Int8Array, 0),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Array, 1),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Int16Array, 2),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint16Array, 3),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Int32Array, 4),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint32Array, 5),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Float32Array, 6),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Float64Array, 7),
+    IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8ClampedArray, 8)
 };
 
 /* static */ bool
diff --git a/js/src/vm/Unicode.h b/js/src/vm/Unicode.h
index 591e46f56e..ea853442c7 100644
--- a/js/src/vm/Unicode.h
+++ b/js/src/vm/Unicode.h
@@ -191,6 +191,12 @@ IsSpaceOrBOM2(char16_t ch)
 inline char16_t
 ToUpperCase(char16_t ch)
 {
+    if (ch < 128) {
+        if (ch >= 'a' && ch <= 'z')
+            return ch - ('a' - 'A');
+        return ch;
+    }
+
     const CharacterInfo& info = CharInfo(ch);
 
     return uint16_t(ch) + info.upperCase;
@@ -199,11 +205,35 @@ ToUpperCase(char16_t ch)
 inline char16_t
 ToLowerCase(char16_t ch)
 {
+    if (ch < 128) {
+        if (ch >= 'A' && ch <= 'Z')
+            return ch + ('a' - 'A');
+        return ch;
+    }
+
     const CharacterInfo& info = CharInfo(ch);
 
     return uint16_t(ch) + info.lowerCase;
 }
 
+// Returns true iff ToUpperCase(ch) != ch.
+inline bool
+CanUpperCase(char16_t ch)
+{
+    if (ch < 128)
+        return ch >= 'a' && ch <= 'z';
+    return CharInfo(ch).upperCase != 0;
+}
+
+// Returns true iff ToLowerCase(ch) != ch.
+inline bool
+CanLowerCase(char16_t ch)
+{
+    if (ch < 128)
+        return ch >= 'A' && ch <= 'Z';
+    return CharInfo(ch).lowerCase != 0;
+}
+
 } /* namespace unicode */
 } /* namespace js */
 
diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h
index 9cf985901f..d9e303d89c 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 = 282;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 283;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
-static_assert(JSErr_Limit == 392,
+static_assert(JSErr_Limit == 393,
               "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/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp
index 9c8f5a9183..b0067849a8 100644
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -32,14 +32,18 @@
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/CSSBinding.h"
 #include "mozilla/dom/indexedDB/IndexedDatabaseManager.h"
+#include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/FileBinding.h"
 #include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/ResponseBinding.h"
 #ifdef MOZ_WEBRTC
 #include "mozilla/dom/RTCIdentityProviderRegistrar.h"
 #endif
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TextDecoderBinding.h"
 #include "mozilla/dom/TextEncoderBinding.h"
+#include "mozilla/dom/UnionConversions.h"
 #include "mozilla/dom/URLBinding.h"
 #include "mozilla/dom/URLSearchParamsBinding.h"
 
@@ -240,6 +244,84 @@ SandboxCreateRTCIdentityProvider(JSContext* cx, JS::HandleObject obj)
 }
 #endif
 
+static bool
+SetFetchRequestFromValue(JSContext *cx, RequestOrUSVString& request,
+                         const MutableHandleValue& requestOrUrl)
+{
+    RequestOrUSVStringArgument requestHolder(request);
+    bool noMatch = true;
+    if (requestOrUrl.isObject() &&
+        !requestHolder.TrySetToRequest(cx, requestOrUrl, noMatch, false)) {
+        return false;
+    }
+    if (noMatch &&
+        !requestHolder.TrySetToUSVString(cx, requestOrUrl, noMatch)) {
+        return false;
+    }
+    if (noMatch) {
+        return false;
+    }
+    return true;
+}
+
+static bool
+SandboxFetch(JSContext* cx, JS::HandleObject scope, const CallArgs& args)
+{
+    if (args.length() < 1) {
+        JS_ReportError(cx, "fetch requires at least 1 argument");
+        return false;
+    }
+
+    RequestOrUSVString request;
+    if (!SetFetchRequestFromValue(cx, request, args[0])) {
+        JS_ReportError(cx, "fetch requires a string or Request in argument 1");
+        return false;
+    }
+    RootedDictionary options(cx);
+    if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue,
+                      "Argument 2 of fetch", false)) {
+        return false;
+    }
+    nsCOMPtr global = xpc::NativeGlobal(scope);
+    if (!global) {
+        return false;
+    }
+    ErrorResult rv;
+    nsRefPtr response =
+        FetchRequest(global, Constify(request), Constify(options), rv);
+    rv.WouldReportJSException();
+    if (rv.Failed()) {
+        return ThrowMethodFailedWithDetails(cx, rv, "Sandbox", "fetch");
+    }
+    if (!GetOrCreateDOMReflector(cx, scope, response, args.rval())) {
+        return false;
+    }
+    return true;
+}
+
+static bool SandboxFetchPromise(JSContext* cx, unsigned argc, jsval* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedObject callee(cx, &args.callee());
+    RootedObject scope(cx, JS::CurrentGlobalOrNull(cx));
+    if (SandboxFetch(cx, scope, args)) {
+        return true;
+    }
+    return ConvertExceptionToPromise(cx, scope, args.rval());
+}
+
+
+static bool
+SandboxCreateFetch(JSContext* cx, HandleObject obj)
+{
+    MOZ_ASSERT(JS_IsGlobalObject(obj));
+
+    return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) &&
+        dom::RequestBinding::GetConstructorObject(cx, obj) &&
+        dom::ResponseBinding::GetConstructorObject(cx, obj) &&
+        dom::HeadersBinding::GetConstructorObject(cx, obj);
+}
+
 static bool
 SandboxIsProxy(JSContext* cx, unsigned argc, jsval* vp)
 {
@@ -824,6 +906,8 @@ xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj)
         } else if (!strcmp(name.ptr(), "rtcIdentityProvider")) {
             rtcIdentityProvider = true;
 #endif
+        } else if (!strcmp(name.ptr(), "fetch")) {
+            fetch = true;
         } else {
             JS_ReportError(cx, "Unknown property name: %s", name.ptr());
             return false;
@@ -838,7 +922,7 @@ xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj)
     if (CSS && !dom::CSSBinding::GetConstructorObject(cx, obj))
         return false;
 
-    if (indexedDB && AccessCheck::isChrome(obj) &&
+    if (indexedDB &&
         !IndexedDatabaseManager::DefineIndexedDB(cx, obj))
         return false;
 
@@ -886,6 +970,9 @@ xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj)
         return false;
 #endif
 
+    if (fetch && !SandboxCreateFetch(cx, obj))
+        return false;
+
     return true;
 }
 
diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h
index 0b2fc8e0fa..8ea17d03c6 100644
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -3399,6 +3399,7 @@ struct GlobalProperties {
     bool File : 1;
     bool crypto : 1;
     bool rtcIdentityProvider : 1;
+    bool fetch : 1;
 };
 
 // Infallible.
diff --git a/js/xpconnect/tests/mochitest/mochitest.ini b/js/xpconnect/tests/mochitest/mochitest.ini
index 015b7d813d..6ad8a8de0f 100644
--- a/js/xpconnect/tests/mochitest/mochitest.ini
+++ b/js/xpconnect/tests/mochitest/mochitest.ini
@@ -105,3 +105,6 @@ skip-if= buildapp == 'mulet'
 skip-if = (debug == false || os == "android")
 [test_nac.xhtml]
 [test_sameOriginPolicy.html]
+[test_sandbox_fetch.html]
+  support-files =
+    ../../../../dom/tests/mochitest/fetch/test_fetch_basic.js
diff --git a/js/xpconnect/tests/mochitest/test_sandbox_fetch.html b/js/xpconnect/tests/mochitest/test_sandbox_fetch.html
new file mode 100644
index 0000000000..b206efdb8c
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/test_sandbox_fetch.html
@@ -0,0 +1,54 @@
+
+
+
+  Fetch in JS Sandbox
+  
+  
+  
+
+
+
+
+
diff --git a/js/xpconnect/tests/unit/test_bug809652.js b/js/xpconnect/tests/unit/test_bug809652.js
index 3a27243032..30e985f203 100644
--- a/js/xpconnect/tests/unit/test_bug809652.js
+++ b/js/xpconnect/tests/unit/test_bug809652.js
@@ -40,7 +40,6 @@ function run_test() {
   checkThrows("Date.prototype.getYear.call(d)", sb);
   checkThrows("Date.prototype.valueOf.call(d)", sb);
   checkThrows("d.valueOf()", sb);
-  checkThrows("Date.prototype.toString.call(d)", sb);
   checkThrows("d.toString()", sb);
 
   /* Typed arrays. */
diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp
index c41509af71..8f116934b9 100644
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -486,7 +486,7 @@ JSXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper,
 
     // Scan through the functions. Indexed array properties are handled above.
     const JSFunctionSpec* fsMatch = nullptr;
-    for (const JSFunctionSpec* fs = clasp->spec.prototypeFunctions; fs && fs->name; ++fs) {
+    for (const JSFunctionSpec* fs = clasp->spec.prototypeFunctions(); fs && fs->name; ++fs) {
         if (PropertySpecNameEqualsId(fs->name, id)) {
             fsMatch = fs;
             break;
@@ -514,7 +514,7 @@ JSXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper,
 
     // Scan through the properties.
     const JSPropertySpec* psMatch = nullptr;
-    for (const JSPropertySpec* ps = clasp->spec.prototypeProperties; ps && ps->name; ++ps) {
+    for (const JSPropertySpec* ps = clasp->spec.prototypeProperties(); ps && ps->name; ++ps) {
         if (PropertySpecNameEqualsId(ps->name, id)) {
             psMatch = ps;
             break;
@@ -747,14 +747,14 @@ JSXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags
     MOZ_ASSERT(clasp->spec.defined());
 
     // Convert the method and property names to jsids and pass them to the caller.
-    for (const JSFunctionSpec* fs = clasp->spec.prototypeFunctions; fs && fs->name; ++fs) {
+    for (const JSFunctionSpec* fs = clasp->spec.prototypeFunctions(); fs && fs->name; ++fs) {
         jsid id;
         if (!PropertySpecNameToPermanentId(cx, fs->name, &id))
             return false;
         if (!MaybeAppend(id, flags, props))
             return false;
     }
-    for (const JSPropertySpec* ps = clasp->spec.prototypeProperties; ps && ps->name; ++ps) {
+    for (const JSPropertySpec* ps = clasp->spec.prototypeProperties(); ps && ps->name; ++ps) {
         jsid id;
         if (!PropertySpecNameToPermanentId(cx, ps->name, &id))
             return false;
diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp
index fbe9492499..aa7a3467e3 100644
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -1503,7 +1503,7 @@ void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrai
       AutoFallibleTArray buffer;
       uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
       if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
-          !buffer.AppendElements(bufferSize)) {
+          !buffer.AppendElements(bufferSize, fallible)) {
         return;
       }
       textRun = BuildTextRunForFrames(buffer.Elements());
@@ -2037,7 +2037,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
         // Need to expand the text. First transform it into a temporary buffer,
         // then expand.
         AutoFallibleTArray tempBuf;
-        uint8_t* bufStart = tempBuf.AppendElements(contentLength);
+        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
         if (!bufStart) {
           DestroyUserData(userDataToDestroy);
           return nullptr;
@@ -2255,7 +2255,7 @@ BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
   if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
     return false;
   }
-  void *textPtr = buffer.AppendElements(bufferSize);
+  void *textPtr = buffer.AppendElements(bufferSize, fallible);
   if (!textPtr) {
     return false;
   }
@@ -2328,7 +2328,7 @@ BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
         // Need to expand the text. First transform it into a temporary buffer,
         // then expand.
         AutoFallibleTArray tempBuf;
-        uint8_t* bufStart = tempBuf.AppendElements(contentLength);
+        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
         if (!bufStart) {
           DestroyUserData(userDataToDestroy);
           return false;
@@ -5656,7 +5656,7 @@ nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
   // Figure out which selections control the colors to use for each character.
   AutoFallibleTArray prevailingSelectionsBuffer;
   SelectionDetails** prevailingSelections =
-    prevailingSelectionsBuffer.AppendElements(aContentLength);
+    prevailingSelectionsBuffer.AppendElements(aContentLength, fallible);
   if (!prevailingSelections) {
     return false;
   }
@@ -5796,7 +5796,7 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
   // Figure out which characters will be decorated for this selection.
   AutoFallibleTArray selectedCharsBuffer;
   SelectionDetails** selectedChars =
-    selectedCharsBuffer.AppendElements(aContentLength);
+    selectedCharsBuffer.AppendElements(aContentLength, fallible);
   if (!selectedChars) {
     return;
   }
@@ -7468,7 +7468,8 @@ nsTextFrame::AddInlineMinISizeForFlow(nsRenderingContext *aRenderingContext,
   AutoFallibleTArray hyphBuffer;
   bool *hyphBreakBefore = nullptr;
   if (hyphenating) {
-    hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start);
+    hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start,
+                                                fallible);
     if (hyphBreakBefore) {
       provider.GetHyphenationBreaks(start, flowEndInTextRun - start,
                                     hyphBreakBefore);
diff --git a/layout/style/FontFaceSet.cpp b/layout/style/FontFaceSet.cpp
index 857af4389b..d8868e8312 100644
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -1481,7 +1481,8 @@ FontFaceSet::DispatchLoadingFinishedEvent(
   init.mBubbles = false;
   init.mCancelable = false;
   OwningNonNull* elements =
-    init.mFontfaces.AppendElements(aFontFaces.Length());
+    init.mFontfaces.AppendElements(aFontFaces.Length(), fallible);
+  MOZ_ASSERT(elements);
   for (size_t i = 0; i < aFontFaces.Length(); i++) {
     elements[i] = aFontFaces[i];
   }
diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp
index 8d48f4809d..26ad74f4bb 100644
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -1436,7 +1436,7 @@ GetStrokeDashData(nsIFrame* aFrame,
 
   } else {
     uint32_t count = style->mStrokeDasharrayLength;
-    if (!count || !aDashes.SetLength(count)) {
+    if (!count || !aDashes.SetLength(count, fallible)) {
       return false;
     }
 
diff --git a/media/libstagefright/binding/BufferStream.cpp b/media/libstagefright/binding/BufferStream.cpp
index 3c86f37422..01801c278d 100644
--- a/media/libstagefright/binding/BufferStream.cpp
+++ b/media/libstagefright/binding/BufferStream.cpp
@@ -66,7 +66,7 @@ BufferStream::DiscardBefore(int64_t aOffset)
 bool
 BufferStream::AppendBytes(const uint8_t* aData, size_t aLength)
 {
-  return mData->AppendElements(aData, aLength);
+  return mData->AppendElements(aData, aLength, fallible);
 }
 
 MediaByteRange
diff --git a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
index fc7e02ef9e..97b3c5aab0 100644
--- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp
@@ -2250,7 +2250,7 @@ status_t MPEG4Extractor::parseMetaData(off64_t offset, size_t size) {
     }
 
     FallibleTArray bufferBackend;
-    if (!bufferBackend.SetLength(size + 1)) {
+    if (!bufferBackend.SetLength(size + 1, mozilla::fallible)) {
         // OOM ignore metadata.
         return OK;
     }
@@ -3396,7 +3396,7 @@ bool MPEG4Source::ensureSrcBufferAllocated(int32_t aSize) {
     if (mSrcBackend.Length() >= aSize) {
         return true;
     }
-    if (!mSrcBackend.SetLength(aSize)) {
+    if (!mSrcBackend.SetLength(aSize, mozilla::fallible)) {
         ALOGE("Error insufficient memory, requested %u bytes (had:%u)",
               aSize, mSrcBackend.Length());
         return false;
diff --git a/media/libstagefright/frameworks/av/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/frameworks/av/media/libstagefright/MediaBuffer.cpp
index 0526ecc9f8..e41afcc76c 100644
--- a/media/libstagefright/frameworks/av/media/libstagefright/MediaBuffer.cpp
+++ b/media/libstagefright/frameworks/av/media/libstagefright/MediaBuffer.cpp
@@ -204,7 +204,7 @@ bool MediaBuffer::ensuresize(size_t length) {
     if (!mOwnsData || refcount()) {
         return false;
     }
-    if (!mBufferBackend.SetLength(length)) {
+    if (!mBufferBackend.SetLength(length, mozilla::fallible)) {
         return false;
     }
     mData = mBufferBackend.Elements();
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
index db75f53cd7..113007b96e 100644
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2290,9 +2290,8 @@ PeerConnectionImpl::Close()
 }
 
 bool
-PeerConnectionImpl::PluginCrash(uint64_t aPluginID,
-                                const nsAString& aPluginName,
-                                const nsAString& aPluginDumpID)
+PeerConnectionImpl::PluginCrash(uint32_t aPluginID,
+                                const nsAString& aPluginName)
 {
   // fire an event to the DOM window if this is "ours"
   bool result = mMedia ? mMedia->AnyCodecHasPluginID(aPluginID) : false;
@@ -2310,7 +2309,7 @@ PeerConnectionImpl::PluginCrash(uint64_t aPluginID,
   }
 
   PluginCrashedEventInit init;
-  init.mPluginDumpID = aPluginDumpID;
+  init.mPluginID = aPluginID;
   init.mPluginName = aPluginName;
   init.mSubmittedCrashReport = false;
   init.mGmpPlugin = true;
diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
index c543fb24ae..d3611abbe0 100644
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -526,9 +526,8 @@ public:
     rv = Close();
   }
 
-  bool PluginCrash(uint64_t aPluginID,
-                   const nsAString& aPluginName,
-                   const nsAString& aPluginDumpID);
+  bool PluginCrash(uint32_t aPluginID,
+                   const nsAString& aPluginName);
 
   nsresult InitializeDataChannel();
 
diff --git a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
index 01e3e3135b..3bd2b32bee 100644
--- a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
+++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
@@ -662,7 +662,7 @@ WebrtcGlobalParent::RecvGetLogResult(const int& aRequestId,
     CSFLogError(logTag, "Bad RequestId");
     return false;
   }
-  request->mResult.AppendElements(aLog);
+  request->mResult.AppendElements(aLog, fallible);
 
   auto next = request->GetNextParent();
   if (next) {
diff --git a/memory/mozalloc/msvc_raise_wrappers.cpp b/memory/mozalloc/msvc_raise_wrappers.cpp
index 45cb481ec3..94331c9cea 100644
--- a/memory/mozalloc/msvc_raise_wrappers.cpp
+++ b/memory/mozalloc/msvc_raise_wrappers.cpp
@@ -61,4 +61,10 @@ moz_Xruntime_error(const char* what)
     abort_from_exception("runtime_error", what);
 }
 
+void
+moz_Xbad_function_call()
+{
+    abort_from_exception("bad_function_call", "bad function call");
+}
+
 } // namespace std
diff --git a/memory/mozalloc/msvc_raise_wrappers.h b/memory/mozalloc/msvc_raise_wrappers.h
index 98e233bbad..1a68ed5d68 100644
--- a/memory/mozalloc/msvc_raise_wrappers.h
+++ b/memory/mozalloc/msvc_raise_wrappers.h
@@ -12,7 +12,10 @@
 #  error "Unable to wrap _RAISE(); CRT _RAISE() already defined"
 #endif
 #ifdef _XUTILITY_
-#  error "Unabled to wrap _X[exception]"(); CRT versions already declared"
+#  error "Unable to wrap _X[exception](); CRT versions already declared"
+#endif
+#ifdef _FUNCTIONAL_
+#  error "Unable to wrap _Xbad_function_call(); CRT version already declared"
 #endif
 
 #include "mozilla/mozalloc_abort.h"
@@ -38,6 +41,8 @@ MOZALLOC_EXPORT __declspec(noreturn) void moz_Xruntime_error(const char*);
 #  define _Xout_of_range      moz_Xout_of_range
 #  define _Xoverflow_error    moz_Xoverflow_error
 #  define _Xruntime_error     moz_Xruntime_error
+// used by 
+#  define _Xbad_function_call moz_Xbad_function_call
 
 #  include 
 #  include 
diff --git a/memory/mozalloc/throw_gcc.h b/memory/mozalloc/throw_gcc.h
index 3b14647866..d055a28593 100644
--- a/memory/mozalloc/throw_gcc.h
+++ b/memory/mozalloc/throw_gcc.h
@@ -73,6 +73,12 @@ __throw_bad_typeid(void)
     mozalloc_abort("fatal: STL threw bad_typeid");
 }
 
+// used by 
+MOZ_THROW_NORETURN MOZ_THROW_EXPORT MOZ_THROW_INLINE void
+__throw_bad_function_call(void) {
+  mozalloc_abort("fatal: STL threw bad_function_call");
+}
+
 MOZ_THROW_NORETURN MOZ_THROW_EXPORT MOZ_ALWAYS_INLINE void
 __throw_logic_error(const char* msg)
 {
diff --git a/mfbt/IndexSequence.h b/mfbt/IndexSequence.h
new file mode 100644
index 0000000000..83abdc0a4a
--- /dev/null
+++ b/mfbt/IndexSequence.h
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+/* A utility for expanding a tuple into a variadic argument list.
+ * Based on std::index_sequence. */
+
+/**
+ * Example usage:
+ *
+ * Problem:
+ *
+ *   You have a variadic function Foo:
+ *
+ *     template  void Foo(Args...);
+ *
+ *   And a variadic function Bar, which contains a tuple:
+ *
+ *     template 
+ *     void Bar() {
+ *       // ...
+ *       Tuple t;
+ *     }
+ *
+ *   And inside Bar, you want to call Foo with the elements of the tuple as
+ *   arguments to Foo.
+ *
+ *   You want to write:
+ *
+ *     Foo(Get<0>(t), Get<1>(t), ..., Get(t))
+ *
+ *   but you can't literally write that, because N is different for different
+ *   instantiations of Bar.
+ *
+ * Solution:
+ *
+ *   Write a helper function which takes the tuple, and an index sequence
+ *   containing indices corresponding to the tuple indices.
+ *
+ *     template 
+ *     void Helper(const Tuple& t, IndexSequence)
+ *     {
+ *       Foo(Get(t)...);
+ *     }
+ *
+ *   Assuming 'Indices...' are 0, 1, ..., N - 1, where N is the size of the
+ *   tuple, pack expansion will expand the pack 'Get(t)...' to
+ *   'Get<0>(t), Get<1>(t), ..., Get(t)'.
+ *
+ *   Finally, call the helper, creating the index sequence to pass in like so:
+ *
+ *     template 
+ *     void Bar() {
+ *       // ...
+ *       Tuple t;
+ *       Helper(t, typename IndexSequenceFor::Type());
+ *     }
+ */
+
+#ifndef mozilla_IndexSequence_h
+#define mozilla_IndexSequence_h
+
+#include "mozilla/Attributes.h"
+
+#include 
+
+namespace mozilla {
+
+/**
+ * Represents a compile-time sequence of integer indices.
+ */
+template
+struct IndexSequence
+{
+  static MOZ_CONSTEXPR size_t Size() { return sizeof...(Indices); }
+};
+
+namespace detail {
+
+// Helpers used by MakeIndexSequence.
+
+template
+struct IndexTuple
+{
+  typedef IndexTuple Next;
+};
+
+// Builds IndexTuple<0, 1, ..., N - 1>.
+template
+struct BuildIndexTuple
+{
+  typedef typename BuildIndexTuple::Type::Next Type;
+};
+
+template<>
+struct BuildIndexTuple<0>
+{
+  typedef IndexTuple<> Type;
+};
+
+template
+struct MakeIndexSequenceImpl;
+
+template
+struct MakeIndexSequenceImpl>
+{
+  typedef IndexSequence Type;
+};
+
+}  // namespace detail
+
+/**
+ * A utility for building an IndexSequence of consecutive indices.
+ * MakeIndexSequence::Type evaluates to IndexSequence<0, 1, .., N - 1>.
+ * Note: unlike std::make_index_sequence, this is not an alias template
+ * to work around bugs in MSVC 2013.
+ */
+template
+struct MakeIndexSequence
+{
+  typedef typename detail::MakeIndexSequenceImpl::Type>::Type Type;
+};
+
+/**
+ * A utility for building an IndexSequence of consecutive indices
+ * corresponding to a variadic argument list.
+ * IndexSequenceFor evaluates to IndexSequence<0, 1, ..., N - 1>
+ * where N is the number of types in Types.
+ * Note: unlike std::index_sequence_for, this is not an alias template
+ * to work around bugs in MSVC 2013.
+ */
+template
+struct IndexSequenceFor
+{
+  typedef typename MakeIndexSequence::Type Type;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_IndexSequence_h */
diff --git a/mfbt/InitializerList.h b/mfbt/InitializerList.h
new file mode 100644
index 0000000000..0f32425327
--- /dev/null
+++ b/mfbt/InitializerList.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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/. */
+
+/* A polyfill for std::initializer_list if it doesn't exist */
+
+#ifndef mozilla_InitializerList_h
+#define mozilla_InitializerList_h
+
+#include 
+#include 
+#if MOZ_USING_LIBCXX
+#  define MOZ_HAVE_INITIALIZER_LIST
+#elif MOZ_USING_LIBSTDCXX && __GLIBCXX__ >= 20090421 // GCC 4.4.0
+#  define MOZ_HAVE_INITIALIZER_LIST
+#elif _MSC_VER
+#  define MOZ_HAVE_INITIALIZER_LIST
+#elif defined(MOZ_USING_STLPORT)
+#else
+#  error "Unknown standard library situation"
+#endif
+
+#ifdef MOZ_HAVE_INITIALIZER_LIST
+#  include 
+#else
+/* Normally we would put things in mozilla:: however, std::initializer_list is a
+ * magic name used by the compiler and so we need to name it that way.
+ */
+namespace std
+{
+
+template
+class initializer_list
+{
+    /* This matches the representation used by GCC and Clang which
+     * are the only compilers we need to polyfill */
+    const T* mBegin;
+    size_t   mSize;
+
+    /* This constructor is called directly by the compiler */
+    initializer_list(const T* begin, size_t size) : mBegin(begin), mSize(size) {}
+public:
+
+    MOZ_CONSTEXPR initializer_list() : mBegin(nullptr), mSize(0) {}
+
+    typedef T value_type;
+    typedef const T& reference;
+    typedef const T& const_reference;
+    typedef size_t size_type;
+    typedef const T* iterator;
+    typedef const T* const_iterator;
+
+    size_t size() const { return mSize; }
+    const T* begin() const { return mBegin; }
+    const T* end() const { return mBegin + mSize; }
+};
+
+}
+#endif /* MOZ_HAS_INITIALIZER_LIST */
+#endif /* mozilla_InitializerList_h */
diff --git a/mfbt/moz.build b/mfbt/moz.build
index 5e465696ba..06f719ba45 100644
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -38,6 +38,8 @@ EXPORTS.mozilla = [
     'Function.h',
     'GuardObjects.h',
     'HashFunctions.h',
+    'IndexSequence.h',
+    'InitializerList.h',
     'IntegerPrintfMacros.h',
     'IntegerRange.h',
     'IntegerTypeTraits.h',
diff --git a/mfbt/tests/TestInitializerList.cpp b/mfbt/tests/TestInitializerList.cpp
new file mode 100644
index 0000000000..03bc60bca4
--- /dev/null
+++ b/mfbt/tests/TestInitializerList.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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 "mozilla/Assertions.h"
+#include "mozilla/InitializerList.h"
+
+int sum(std::initializer_list p)
+{
+  int result = 0;
+  for (auto i : p) {
+    result += i;
+  }
+  return result;
+}
+
+int
+main()
+{
+  MOZ_RELEASE_ASSERT(sum({1, 2, 3, 4, 5, 6}) == 7 * 3);
+  return 0;
+}
diff --git a/mfbt/tests/moz.build b/mfbt/tests/moz.build
index fdda04c98f..5654d68d3e 100644
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -18,6 +18,7 @@ CppUnitTests([
     'TestEnumSet',
     'TestFloatingPoint',
     'TestFunction',
+    'TestInitializerList',
     'TestIntegerPrintfMacros',
     'TestJSONWriter',
     'TestMacroArgs',
diff --git a/netwerk/base/MemoryDownloader.cpp b/netwerk/base/MemoryDownloader.cpp
index 3e659660aa..1b9f5e94af 100644
--- a/netwerk/base/MemoryDownloader.cpp
+++ b/netwerk/base/MemoryDownloader.cpp
@@ -57,7 +57,7 @@ MemoryDownloader::ConsumeData(nsIInputStream* aIn,
                               uint32_t* aWriteCount)
 {
   MemoryDownloader* self = static_cast(aClosure);
-  if (!self->mData->AppendElements(aFromRawSegment, aCount)) {
+  if (!self->mData->AppendElements(aFromRawSegment, aCount, fallible)) {
     // The error returned by ConsumeData isn't propagated to the
     // return of ReadSegments, so it has to be passed as state.
     self->mStatus = NS_ERROR_OUT_OF_MEMORY;
diff --git a/netwerk/base/nsUDPSocket.cpp b/netwerk/base/nsUDPSocket.cpp
index b00090fce8..06961235f6 100644
--- a/netwerk/base/nsUDPSocket.cpp
+++ b/netwerk/base/nsUDPSocket.cpp
@@ -651,7 +651,7 @@ nsUDPSocket::OnSocketReady(PRFileDesc *fd, int16_t outFlags)
   SaveNetworkStats(false);
 
   FallibleTArray data;
-  if(!data.AppendElements(buff, count)){
+  if (!data.AppendElements(buff, count, fallible)) {
     mCondition = NS_ERROR_UNEXPECTED;
     return;
   }
diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
index e412cc7453..19ec2cbff7 100644
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -161,57 +161,55 @@ SandboxBroker::SetSecurityLevelForPluginProcess(int32_t aSandboxLevel)
     return false;
   }
 
-  sandbox::ResultCode result;
-  bool ret;
-  if (aSandboxLevel >= 2) {
-    result = mPolicy->SetJobLevel(sandbox::JOB_UNPROTECTED,
-                                     0 /* ui_exceptions */);
-    ret = (sandbox::SBOX_ALL_OK == result);
-
-    sandbox::TokenLevel tokenLevel;
-    if (aSandboxLevel >= 3) {
-      tokenLevel = sandbox::USER_LIMITED;
-    } else {
-      tokenLevel = sandbox::USER_INTERACTIVE;
-    }
-
-    result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
-                                    tokenLevel);
-    ret = ret && (sandbox::SBOX_ALL_OK == result);
-
-    sandbox::MitigationFlags mitigations =
-      sandbox::MITIGATION_BOTTOM_UP_ASLR |
-      sandbox::MITIGATION_HEAP_TERMINATE |
-      sandbox::MITIGATION_SEHOP |
-      sandbox::MITIGATION_DEP_NO_ATL_THUNK |
-      sandbox::MITIGATION_DEP;
-
-    result = mPolicy->SetProcessMitigations(mitigations);
-    ret = ret && (sandbox::SBOX_ALL_OK == result);
-
-    mitigations =
-      sandbox::MITIGATION_STRICT_HANDLE_CHECKS;
-
-    result = mPolicy->SetDelayedProcessMitigations(mitigations);
-    ret = ret && (sandbox::SBOX_ALL_OK == result);
-
-    // The following is required for the Java plugin.
-    result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
-                              sandbox::TargetPolicy::FILES_ALLOW_ANY,
-                              L"\\??\\pipe\\jpi2_pid*_pipe*");
-    ret = ret && (sandbox::SBOX_ALL_OK == result);
+  sandbox::JobLevel jobLevel;
+  sandbox::TokenLevel accessTokenLevel;
+  sandbox::IntegrityLevel initialIntegrityLevel;
+  sandbox::IntegrityLevel delayedIntegrityLevel;
 
+  if (aSandboxLevel > 2) {
+    jobLevel = sandbox::JOB_UNPROTECTED;
+    accessTokenLevel = sandbox::USER_LIMITED;
+    initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
+    delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
+  } else if (aSandboxLevel == 2) {
+    jobLevel = sandbox::JOB_UNPROTECTED;
+    accessTokenLevel = sandbox::USER_INTERACTIVE;
+    initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
+    delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_LOW;
   } else {
-    result = mPolicy->SetJobLevel(sandbox::JOB_NONE,
-                                     0 /* ui_exceptions */);
-    ret = (sandbox::SBOX_ALL_OK == result);
-
-    result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
-                                    sandbox::USER_NON_ADMIN);
-    ret = ret && (sandbox::SBOX_ALL_OK == result);
+    jobLevel = sandbox::JOB_NONE;
+    accessTokenLevel = sandbox::USER_NON_ADMIN;
+    initialIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM;
+    delayedIntegrityLevel = sandbox::INTEGRITY_LEVEL_MEDIUM;
   }
 
-  result = mPolicy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_MEDIUM);
+  sandbox::ResultCode result = mPolicy->SetJobLevel(jobLevel,
+                                                    0 /* ui_exceptions */);
+  bool ret = (sandbox::SBOX_ALL_OK == result);
+
+  result = mPolicy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
+                                  accessTokenLevel);
+  ret = ret && (sandbox::SBOX_ALL_OK == result);
+
+  result = mPolicy->SetIntegrityLevel(initialIntegrityLevel);
+  ret = ret && (sandbox::SBOX_ALL_OK == result);
+  result = mPolicy->SetDelayedIntegrityLevel(delayedIntegrityLevel);
+  ret = ret && (sandbox::SBOX_ALL_OK == result);
+
+  sandbox::MitigationFlags mitigations =
+    sandbox::MITIGATION_BOTTOM_UP_ASLR |
+    sandbox::MITIGATION_HEAP_TERMINATE |
+    sandbox::MITIGATION_SEHOP |
+    sandbox::MITIGATION_DEP_NO_ATL_THUNK |
+    sandbox::MITIGATION_DEP;
+
+  result = mPolicy->SetProcessMitigations(mitigations);
+  ret = ret && (sandbox::SBOX_ALL_OK == result);
+
+  mitigations =
+    sandbox::MITIGATION_STRICT_HANDLE_CHECKS;
+
+  result = mPolicy->SetDelayedProcessMitigations(mitigations);
   ret = ret && (sandbox::SBOX_ALL_OK == result);
 
   // Add the policy for the client side of a pipe. It is just a file
@@ -223,12 +221,24 @@ SandboxBroker::SetSecurityLevelForPluginProcess(int32_t aSandboxLevel)
   ret = ret && (sandbox::SBOX_ALL_OK == result);
 
   // The NPAPI process needs to be able to duplicate shared memory to the
-  // content process, which are Section type handles.
+  // content process and broker process, which are Section type handles.
+  // Content and broker are for e10s and non-e10s cases.
   result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES,
                             sandbox::TargetPolicy::HANDLES_DUP_ANY,
                             L"Section");
   ret = ret && (sandbox::SBOX_ALL_OK == result);
 
+  result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_HANDLES,
+                            sandbox::TargetPolicy::HANDLES_DUP_BROKER,
+                            L"Section");
+  ret = ret && (sandbox::SBOX_ALL_OK == result);
+
+  // The following is required for the Java plugin.
+  result = mPolicy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
+                            sandbox::TargetPolicy::FILES_ALLOW_ANY,
+                            L"\\??\\pipe\\jpi2_pid*_pipe*");
+  ret = ret && (sandbox::SBOX_ALL_OK == result);
+
   return ret;
 }
 
diff --git a/storage/Variant.h b/storage/Variant.h
index 7cca7f84a2..ce65adc112 100644
--- a/storage/Variant.h
+++ b/storage/Variant.h
@@ -263,7 +263,7 @@ struct variant_storage_traits
   {
     _outData->Clear();
     (void)_outData->AppendElements(static_cast(aBlob.first),
-                                   aBlob.second);
+                                   aBlob.second, fallible);
   }
   static inline void destroy(const StorageType& _outData)
   { }
diff --git a/testing/cppunittest.ini b/testing/cppunittest.ini
index 9ff49c3afa..8cb722f578 100644
--- a/testing/cppunittest.ini
+++ b/testing/cppunittest.ini
@@ -40,6 +40,7 @@ run-if = os == 'win'
 [TestGetURL]
 [TestHashtables]
 [TestID]
+[TestInitializerList]
 [TestIntegerPrintfMacros]
 [TestJSONWriter]
 [TestJemalloc]
diff --git a/toolkit/components/aboutperformance/content/aboutPerformance.js b/toolkit/components/aboutperformance/content/aboutPerformance.js
index 3dfebff76e..d4f22c384b 100644
--- a/toolkit/components/aboutperformance/content/aboutPerformance.js
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.js
@@ -9,20 +9,85 @@
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+const { AddonWatcher } = Cu.import("resource://gre/modules/AddonWatcher.jsm", {});
 const { PerformanceStats } = Cu.import("resource://gre/modules/PerformanceStats.jsm", {});
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+
+// about:performance observes notifications on this topic.
+// if a notification is sent, this causes the page to be updated immediately,
+// regardless of whether the page is on pause.
+const UPDATE_IMMEDIATELY_TOPIC = "about:performance-update-immediately";
+
+// about:performance posts notifications on this topic whenever the page
+// is updated.
+const UPDATE_COMPLETE_TOPIC = "about:performance-update-complete";
 
 /**
  * The various measures we display.
  */
 const MEASURES = [
-  {key: "longestDuration", percentOfDeltaT: false, label: "Jank level"},
-  {key: "totalUserTime", percentOfDeltaT: true, label: "User (%)"},
-  {key: "totalSystemTime", percentOfDeltaT: true, label: "System (%)"},
-  {key: "totalCPOWTime", percentOfDeltaT: true, label: "Cross-Process (%)"},
-  {key: "ticks", percentOfDeltaT: false, label: "Activations"},
+  {probe: "jank", key: "longestDuration", percentOfDeltaT: false, label: "Jank level"},
+  {probe: "jank", key: "totalUserTime", percentOfDeltaT: true, label: "User (%)"},
+  {probe: "jank", key: "totalSystemTime", percentOfDeltaT: true, label: "System (%)"},
+  {probe: "cpow", key: "totalCPOWTime", percentOfDeltaT: true, label: "Cross-Process (%)"},
+  {probe: "ticks",key: "ticks", percentOfDeltaT: false, label: "Activations"},
 ];
 
+/**
+ * Used to control the live updates in the performance page.
+ */
+let AutoUpdate = {
+
+  /**
+   * The timer that is created when setInterval is called.
+   */
+  _timerId: null,
+
+  /**
+   * The dropdown DOM element.
+   */
+  _intervalDropdown: null,
+
+  /**
+   * Starts updating the performance data if the updates are paused.
+   */
+  start: function () {
+    if (AutoUpdate._intervalDropdown == null){
+      AutoUpdate._intervalDropdown = document.getElementById("intervalDropdown");
+    }
+
+    if (AutoUpdate._timerId == null) {
+      let dropdownIndex = AutoUpdate._intervalDropdown.selectedIndex;
+      let dropdownValue = AutoUpdate._intervalDropdown.options[dropdownIndex].value;
+      AutoUpdate._timerId = window.setInterval(update, dropdownValue);
+    }
+  },
+
+  /**
+   * Stops the updates if the data is updating.
+   */
+  stop: function () {
+    if (AutoUpdate._timerId == null) {
+      return;
+    }
+    clearInterval(AutoUpdate._timerId);
+    AutoUpdate._timerId = null;
+  },
+
+  /**
+   * Updates the refresh interval when the dropdown selection is changed.
+   */
+  updateRefreshRate: function () {
+    AutoUpdate.stop();
+    AutoUpdate.start();
+  }
+
+};
+
 let State = {
+  _monitor: PerformanceStats.getMonitor(["jank", "cpow", "ticks"]),
+
   /**
    * @type{PerformanceData}
    */
@@ -42,27 +107,30 @@ let State = {
   /**
    * Fetch the latest information, compute diffs.
    *
-   * @return {object} An object with the following fields:
+   * @return {Promise}
+   * @resolve An object with the following fields:
    * - `components`: an array of `PerformanceDiff` representing
    *   the components, sorted by `longestDuration`, then by `totalUserTime`
    * - `process`: a `PerformanceDiff` representing the entire process;
    * - `deltaT`: the number of milliseconds elapsed since the data
    *   was last displayed.
    */
-  update: function() {
-    let snapshot = PerformanceStats.getSnapshot();
+  update: Task.async(function*() {
+    let snapshot = yield this._monitor.promiseSnapshot();
     let newData = new Map();
     let deltas = [];
     for (let componentNew of snapshot.componentsData) {
-      let componentOld = State._componentsData.get(componentNew.name);
-      deltas.push(componentNew.substract(componentOld));
-      newData.set(componentNew.name, componentNew);
+      let key = componentNew.groupId;
+      let componentOld = State._componentsData.get(key);
+      deltas.push(componentNew.subtract(componentOld));
+      newData.set(key, componentNew);
     }
     State._componentsData = newData;
     let now = window.performance.now();
+    let process = snapshot.processData.subtract(State._processData);
     let result = {
-      components: deltas.filter(x => x.ticks > 0),
-      process: snapshot.processData.substract(State._processData),
+      components: deltas.filter(x => x.ticks.ticks > 0),
+      process: snapshot.processData.subtract(State._processData),
       deltaT: now - State._date
     };
     result.components.sort((a, b) => {
@@ -77,13 +145,99 @@ let State = {
     State._processData = snapshot.processData;
     State._date = now;
     return result;
-  }
+  })
 };
 
 
-function update() {
+let update = Task.async(function*() {
+  yield updateLiveData();
+  yield updateSlowAddons();
+  Services.obs.notifyObservers(null, UPDATE_COMPLETE_TOPIC, "");
+});
+
+/**
+ * Update the list of slow addons
+ */
+let updateSlowAddons = Task.async(function*() {
   try {
-    let dataElt = document.getElementById("data");
+    let data = AddonWatcher.alerts;
+    if (data.size == 0) {
+      // Nothing to display.
+      return;
+    }
+    let alerts = 0;
+    for (let [addonId, details] of data) {
+      for (let k of Object.keys(details.alerts)) {
+        alerts += details.alerts[k];
+      }
+    }
+
+    if (!alerts) {
+      // Still nothing to display.
+      return;
+    }
+
+
+    let elData = document.getElementById("slowAddonsList");
+    elData.innerHTML = "";
+    let elTable = document.createElement("table");
+    elData.appendChild(elTable);
+
+    // Generate header
+    let elHeader = document.createElement("tr");
+    elTable.appendChild(elHeader);
+    for (let name of [
+      "Alerts",
+      "Jank level alerts",
+      "(highest jank)",
+      "Cross-Process alerts",
+      "(highest CPOW)"
+    ]) {
+      let elName = document.createElement("td");
+      elName.textContent = name;
+      elHeader.appendChild(elName);
+      elName.classList.add("header");
+    }
+    for (let [addonId, details] of data) {
+      let elAddon = document.createElement("tr");
+
+      // Display the number of occurrences of each alerts
+      let elTotal = document.createElement("td");
+      let total = 0;
+      for (let k of Object.keys(details.alerts)) {
+        total += details.alerts[k];
+      }
+      elTotal.textContent = total;
+      elAddon.appendChild(elTotal);
+
+      for (let filter of ["longestDuration", "totalCPOWTime"]) {
+        for (let stat of ["alerts", "peaks"]) {
+          let el = document.createElement("td");
+          el.textContent = details[stat][filter] || 0;
+          elAddon.appendChild(el);
+        }
+      }
+
+      // Display the name of the add-on
+      let elName = document.createElement("td");
+      elAddon.appendChild(elName);
+      AddonManager.getAddonByID(addonId, a => {
+        elName.textContent = a ? a.name : addonId
+      });
+
+      elTable.appendChild(elAddon);
+    }
+  } catch (ex) {
+    console.error(ex);
+  }
+});
+
+/**
+ * Update the table of live data.
+ */
+let updateLiveData = Task.async(function*() {
+  try {
+    let dataElt = document.getElementById("liveData");
     dataElt.innerHTML = "";
 
     // Generate table headers
@@ -97,9 +251,9 @@ function update() {
       headerElt.appendChild(el);
     }
 
-    let deltas = State.update();
+    let deltas = yield State.update();
 
-    for (let item of deltas.components) {
+    for (let item of [deltas.process, ...deltas.components]) {
       let row = document.createElement("tr");
       if (item.addonId) {
         row.classList.add("addon");
@@ -111,13 +265,14 @@ function update() {
       dataElt.appendChild(row);
 
       // Measures
-      for (let {key, percentOfDeltaT} of MEASURES) {
+      for (let {probe, key, percentOfDeltaT} of MEASURES) {
         let el = document.createElement("td");
         el.classList.add(key);
         el.classList.add("contents");
         row.appendChild(el);
 
-        let value = percentOfDeltaT ? Math.round(item[key] / deltas.deltaT) : item[key];
+        let rawValue = item[probe][key];
+        let value = percentOfDeltaT ? Math.round(rawValue / deltas.deltaT) : rawValue;
         if (key == "longestDuration") {
           value += 1;
           el.classList.add("jank" + value);
@@ -135,23 +290,31 @@ function update() {
         let _el = el;
         let _item = item;
         AddonManager.getAddonByID(item.addonId, a => {
-          _el.textContent = a?a.name:_item.name
+          _el.textContent = a ? a.name : _item.name
         });
       } else {
-        el.textContent = item.name;
+        el.textContent = item.title || item.name;
       }
     }
   } catch (ex) {
     console.error(ex);
   }
-}
+});
 
 function go() {
+  document.getElementById("playButton").addEventListener("click", () => AutoUpdate.start());
+  document.getElementById("pauseButton").addEventListener("click", () => AutoUpdate.stop());
+
+  document.getElementById("intervalDropdown").addEventListener("change", () => AutoUpdate.updateRefreshRate());
+
+
   // Compute initial state immediately, then wait a little
   // before we start computing diffs and refreshing.
   State.update();
+  window.setTimeout(update, 500);
 
-  window.setTimeout(() => {
-    window.setInterval(update, 2000);
-  }, 1000);
+  let observer = update;
+
+  Services.obs.addObserver(update, UPDATE_IMMEDIATELY_TOPIC, false);
+  window.addEventListener("unload", () => Services.obs.removeObserver(update, UPDATE_IMMEDIATELY_TOPIC));
 }
diff --git a/toolkit/components/aboutperformance/content/aboutPerformance.xhtml b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
index da41fe97ab..e13aa07b13 100644
--- a/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
+++ b/toolkit/components/aboutperformance/content/aboutPerformance.xhtml
@@ -85,7 +85,25 @@
     
   
   
-    
+
+    

Performance monitor

+ + + + +
+ +

Slow add-ons alerts

+
+ (none) +
+ diff --git a/toolkit/components/aboutperformance/moz.build b/toolkit/components/aboutperformance/moz.build index 0ce9ae6ae8..5b0937d42c 100644 --- a/toolkit/components/aboutperformance/moz.build +++ b/toolkit/components/aboutperformance/moz.build @@ -4,29 +4,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -FAIL_ON_WARNINGS = True - JAR_MANIFESTS += ['jar.mn'] -XPIDL_MODULE = 'toolkit_perfmonitoring' - -XPIDL_SOURCES += [ - 'nsIPerformanceStats.idl', -] - -UNIFIED_SOURCES += [ - 'nsPerformanceStats.cpp' -] - -EXPORTS += [ - 'nsPerformanceStats.h' -] - -LOCAL_INCLUDES += [ - '/dom/base', -] - BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini'] -XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] - -FINAL_LIBRARY = 'xul' diff --git a/toolkit/components/aboutperformance/tests/browser/browser.ini b/toolkit/components/aboutperformance/tests/browser/browser.ini index b1a8805178..87eb74f607 100644 --- a/toolkit/components/aboutperformance/tests/browser/browser.ini +++ b/toolkit/components/aboutperformance/tests/browser/browser.ini @@ -1,12 +1,9 @@ -# 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/. - [DEFAULT] head = head.js -support-files = +support-files = browser_compartments.html + browser_compartments_frame.html + browser_compartments_script.js [browser_aboutperformance.js] skip-if = e10s # Feature not implemented yet – bug 1140310 -[browser_compartments.js] diff --git a/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js index c4e57740fd..eb48f0437e 100644 --- a/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js +++ b/toolkit/components/aboutperformance/tests/browser/browser_aboutperformance.js @@ -11,55 +11,78 @@ const URL = "http://example.com/browser/toolkit/components/aboutperformance/test function frameScript() { "use strict"; - addMessageListener("aboutperformance-test:hasItems", ({data: url}) => { - let hasPlatform = false; - let hasURL = false; + addMessageListener("aboutperformance-test:done", () => { + content.postMessage("stop", "*"); + sendAsyncMessage("aboutperformance-test:done", null); + }); + addMessageListener("aboutperformance-test:setTitle", ({data: title}) => { + content.document.title = title; + sendAsyncMessage("aboutperformance-test:setTitle", null); + }); + + addMessageListener("aboutperformance-test:hasItems", ({data: title}) => { + let observer = function() { + Services.obs.removeObserver(observer, "about:performance-update-complete"); + let hasPlatform = false; + let hasTitle = false; - try { - let eltData = content.document.getElementById("data"); - if (!eltData) { - return; - } - - // Find if we have a row for "platform" - hasPlatform = eltData.querySelector("tr.platform") != null; - - // Find if we have a row for our URL - hasURL = false; - for (let eltContent of eltData.querySelectorAll("tr.content td.name")) { - if (eltContent.textContent == url) { - hasURL = true; - break; + try { + let eltData = content.document.getElementById("liveData"); + if (!eltData) { + return; } - } - } catch (ex) { - Cu.reportError("Error in content: " + ex); - Cu.reportError(ex.stack); - } finally { - sendAsyncMessage("aboutperformance-test:hasItems", {hasPlatform, hasURL}); + // Find if we have a row for "platform" + hasPlatform = eltData.querySelector("tr.platform") != null; + + // Find if we have a row for our content page + let titles = [for (eltContent of eltData.querySelectorAll("td.contents.name")) eltContent.textContent]; + + hasTitle = titles.includes(title); + } catch (ex) { + Cu.reportError("Error in content: " + ex); + Cu.reportError(ex.stack); + } finally { + sendAsyncMessage("aboutperformance-test:hasItems", {hasPlatform, hasTitle}); + } } + Services.obs.addObserver(observer, "about:performance-update-complete", false); + Services.obs.notifyObservers(null, "about:performance-update-immediately", ""); }); } -add_task(function* test() { - let tabAboutPerformance = gBrowser.addTab("about:performance"); - let tabContent = gBrowser.addTab(URL); - +add_task(function* go() { + info("Setting up about:performance"); + let tabAboutPerformance = gBrowser.selectedTab = gBrowser.addTab("about:performance"); yield ContentTask.spawn(tabAboutPerformance.linkedBrowser, null, frameScript); + info(`Setting up ${URL}`); + let tabContent = gBrowser.addTab(URL); + yield ContentTask.spawn(tabContent.linkedBrowser, null, frameScript); + + let title = "Testing about:performance " + Math.random(); + info(`Setting up title ${title}`); while (true) { + yield promiseContentResponse(tabContent.linkedBrowser, "aboutperformance-test:setTitle", title); yield new Promise(resolve => setTimeout(resolve, 100)); - let {hasPlatform, hasURL} = (yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", URL)); - info(`Platform: ${hasPlatform}, url: ${hasURL}`); - if (hasPlatform && hasURL) { - Assert.ok(true, "Found a row for and a row for our URL"); + let {hasPlatform, hasTitle} = (yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:hasItems", title)); + info(`Platform: ${hasPlatform}, title: ${hasTitle}`); + if (hasPlatform && hasTitle) { + Assert.ok(true, "Found a row for and a row for our page"); break; } } // Cleanup - gBrowser.removeTab(tabContent); - gBrowser.removeTab(tabAboutPerformance); + info("Cleaning up"); + yield promiseContentResponse(tabAboutPerformance.linkedBrowser, "aboutperformance-test:done", null); + + info("Closing tabs"); + for (let tab of gBrowser.tabs) { + yield BrowserTestUtils.removeTab(tab); + } + + info("Done"); + gBrowser.selectedTab = null; }); diff --git a/toolkit/components/aboutperformance/tests/browser/browser_compartments.js b/toolkit/components/aboutperformance/tests/browser/browser_compartments.js deleted file mode 100644 index 15b230db91..0000000000 --- a/toolkit/components/aboutperformance/tests/browser/browser_compartments.js +++ /dev/null @@ -1,161 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://gre/modules/PerformanceStats.jsm", this); -Cu.import("resource://testing-common/ContentTask.jsm", this); - -const URL = "http://example.com/browser/toolkit/components/aboutperformance/tests/browser/browser_compartments.html?test=" + Math.random(); - -// This function is injected as source as a frameScript -function frameScript() { - "use strict"; - - const { utils: Cu, classes: Cc, interfaces: Ci } = Components; - Cu.import("resource://gre/modules/PerformanceStats.jsm"); - - let performanceStatsService = - Cc["@mozilla.org/toolkit/performance-stats-service;1"]. - getService(Ci.nsIPerformanceStatsService); - - // Make sure that the stopwatch is now active. - performanceStatsService.isStopwatchActive = true; - - addMessageListener("compartments-test:getStatistics", () => { - try { - sendAsyncMessage("compartments-test:getStatistics", PerformanceStats.getSnapshot()); - } catch (ex) { - Cu.reportError("Error in content: " + ex); - Cu.reportError(ex.stack); - } - }); -} - -function Assert_leq(a, b, msg) { - Assert.ok(a <= b, `${msg}: ${a} <= ${b}`); -} - -function monotinicity_tester(source, testName) { - // In the background, check invariants: - // - numeric data can only ever increase; - // - the name, addonId, isSystem of a component never changes; - // - the name, addonId, isSystem of the process data; - // - there is at most one component with a combination of `name` and `addonId`; - // - types, etc. - let previous = { - processData: null, - componentsMap: new Map(), - }; - - let sanityCheck = function(prev, next) { - dump(`Sanity check: ${JSON.stringify(next, null, "\t")}\n`); - if (prev == null) { - return; - } - for (let k of ["name", "addonId", "isSystem"]) { - Assert.equal(prev[k], next[k], `Sanity check (${name}): ${k} hasn't changed.`); - } - for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"]) { - Assert.equal(typeof next[k], "number", `Sanity check (${name}): ${k} is a number.`); - Assert_leq(prev[k], next[k], `Sanity check (${name}): ${k} is monotonic.`); - Assert_leq(0, next[k], `Sanity check (${name}): ${k} is >= 0.`) - } - Assert.equal(prev.durations.length, next.durations.length); - for (let i = 0; i < next.durations.length; ++i) { - Assert.ok(typeof next.durations[i] == "number" && next.durations[i] >= 0, - `Sanity check (${name}): durations[${i}] is a non-negative number.`); - Assert_leq(prev.durations[i], next.durations[i], - `Sanity check (${name}): durations[${i}] is monotonic.`) - } - for (let i = 0; i < next.durations.length - 1; ++i) { - Assert_leq(next.durations[i + 1], next.durations[i], - `Sanity check (${name}): durations[${i}] >= durations[${i + 1}].`) - } - }; - let iteration = 0; - let frameCheck = Task.async(function*() { - let name = `${testName}: ${iteration++}`; - let snapshot = yield source(); - if (!snapshot) { - // This can happen at the end of the test when we attempt - // to communicate too late with the content process. - window.clearInterval(interval); - return; - } - - // Sanity check on the process data. - sanityCheck(previous.processData, snapshot.processData); - Assert.equal(snapshot.processData.isSystem, true); - Assert.equal(snapshot.processData.name, ""); - Assert.equal(snapshot.processData.addonId, ""); - previous.procesData = snapshot.processData; - - // Sanity check on components data. - let set = new Set(); - let keys = []; - for (let item of snapshot.componentsData) { - let key = `{name: ${item.name}, addonId: ${item.addonId}, isSystem: ${item.isSystem}}`; - keys.push(key); - set.add(key); - sanityCheck(previous.componentsMap.get(key), item); - previous.componentsMap.set(key, item); - - for (let k of ["totalUserTime", "totalSystemTime", "totalCPOWTime"]) { - Assert_leq(item[k], snapshot.processData[k], - `Sanity check (${name}): component has a lower ${k} than process`); - } - } - // Check that we do not have duplicate components. - info(`Before deduplication, we had the following components: ${keys.sort().join(", ")}`); - info(`After deduplication, we have the following components: ${[...set.keys()].sort().join(", ")}`); - - info(`Deactivating deduplication check (Bug 1150045)`); - if (false) { - Assert.equal(set.size, snapshot.componentsData.length); - } - }); - let interval = window.setInterval(frameCheck, 300); - registerCleanupFunction(() => { - window.clearInterval(interval); - }); -} - -add_task(function* test() { - info("Extracting initial state"); - let stats0 = PerformanceStats.getSnapshot(); - Assert.notEqual(stats0.componentsData.length, 0, "There is more than one component"); - Assert.ok(!stats0.componentsData.find(stat => stat.name.indexOf(URL) != -1), - "The url doesn't appear yet"); - - let newTab = gBrowser.addTab(); - let browser = newTab.linkedBrowser; - // Setup monitoring in the tab - info("Setting up monitoring in the tab"); - yield ContentTask.spawn(newTab.linkedBrowser, null, frameScript); - - info("Opening URL"); - newTab.linkedBrowser.loadURI(URL); - - info("Setting up monotonicity testing"); - monotinicity_tester(() => PerformanceStats.getSnapshot(), "parent process"); - monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" ); - - let skipTotalUserTime = hasLowPrecision(); - - while (true) { - let stats = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null)); - let found = stats.componentsData.find(stat => { - return (stat.name.indexOf(URL) != -1) - && (skipTotalUserTime || stat.totalUserTime > 1000) - }); - if (found) { - info(`Expected totalUserTime > 1000, got ${found.totalUserTime}`); - break; - } - yield new Promise(resolve => setTimeout(resolve, 100)); - } - - // Cleanup - gBrowser.removeTab(newTab); -}); diff --git a/toolkit/components/build/moz.build b/toolkit/components/build/moz.build index 3fcd62632d..91af6b881e 100644 --- a/toolkit/components/build/moz.build +++ b/toolkit/components/build/moz.build @@ -16,12 +16,12 @@ FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '../../xre', - '../aboutperformance', '../alerts', '../downloads', '../feeds', '../find', '../jsdownloads/src', + '../perfmonitoring', '../protobuf', '../startup', '../statusfilter', diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build index e25c9c62f1..0a1a00bc7a 100644 --- a/toolkit/components/moz.build +++ b/toolkit/components/moz.build @@ -37,6 +37,7 @@ DIRS += [ 'parentalcontrols', 'passwordmgr', 'perf', + 'perfmonitoring', 'places', 'processsingleton', 'promiseworker', diff --git a/toolkit/components/perfmonitoring/AddonWatcher.jsm b/toolkit/components/perfmonitoring/AddonWatcher.jsm new file mode 100644 index 0000000000..81ab4fe3cf --- /dev/null +++ b/toolkit/components/perfmonitoring/AddonWatcher.jsm @@ -0,0 +1,267 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["AddonWatcher"]; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Preferences", + "resource://gre/modules/Preferences.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "console", + "resource://gre/modules/devtools/Console.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats", + "resource://gre/modules/PerformanceStats.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", + "@mozilla.org/base/telemetry;1", + Ci.nsITelemetry); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +const FILTERS = [ + {probe: "jank", field: "longestDuration"}, + {probe: "cpow", field: "totalCPOWTime"}, +]; + +let AddonWatcher = { + _previousPerformanceIndicators: {}, + + /** + * Stats, designed to be consumed by clients of AddonWatcher. + * + */ + _stats: new Map(), + _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), + _callback: null, + /** + * A performance monitor used to pull data from SpiderMonkey. + * + * @type {PerformanceStats Monitor} + */ + _monitor: null, + /** + * The interval at which we poll the available performance information + * to find out about possibly slow add-ons, in milliseconds. + */ + _interval: 15000, + _ignoreList: null, + /** + * Initialize and launch the AddonWatcher. + * + * @param {function} callback A callback, called whenever we determine + * that an add-on is causing performance issues. It takes as argument + * {string} addonId The identifier of the add-on known to cause issues. + * {string} reason The reason for which the add-on has been flagged, + * as one of "totalCPOWTime" (the add-on has caused blocking process + * communications, which freeze the UX) + * Use preference "browser.addon-watch.limits.totalCPOWTime" to control + * the maximal amount of CPOW time per watch interval. + * + * or "longestDuration" (the add-on has caused user-visible missed frames). + * Use preference "browser.addon-watch.limits.longestDuration" to control + * the longest uninterrupted execution of code of an add-on during a watch + * interval. + */ + init: function(callback) { + if (!callback) { + return; + } + + if (this._callback) { + // Already initialized + return; + } + + this._interval = Preferences.get("browser.addon-watch.interval", 15000); + if (this._interval == -1) { + // Deactivated by preferences + return; + } + + this._callback = callback; + try { + this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null))); + } catch (ex) { + // probably some malformed JSON, ignore and carry on + this._ignoreList = new Set(); + } + + // Start monitoring + this.paused = false; + + Services.obs.addObserver(() => { + this.uninit(); + }, "profile-before-change", false); + }, + uninit: function() { + this.paused = true; + this._callback = null; + }, + + /** + * Interrupt temporarily add-on watching. + */ + set paused(isPaused) { + if (!this._callback || this._interval == -1) { + return; + } + if (isPaused) { + this._timer.cancel(); + if (this._monitor) { + // We don't need the probes anymore, release them. + this._monitor.dispose(); + } + this._monitor = null; + } else { + this._monitor = PerformanceStats.getMonitor([for (filter of FILTERS) filter.probe]); + this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK); + } + this._isPaused = isPaused; + }, + get paused() { + return this._isPaused; + }, + _isPaused: true, + + /** + * Check the performance of add-ons during the latest slice of time. + * + * We consider that an add-on is causing slowdown if it has executed + * without interruption for at least 64ms (4 frames) at least once + * during the latest slice, or if it has used any CPOW during the latest + * slice. + */ + _checkAddons: function() { + return Task.spawn(function*() { + try { + let snapshot = yield this._monitor.promiseSnapshot(); + + let limits = { + // By default, warn if we have a total time of 1s of CPOW per 15 seconds + totalCPOWTime: Math.round(Preferences.get("browser.addon-watch.limits.totalCPOWTime", 1000000) * this._interval / 15000), + // By default, warn if we have skipped 4 consecutive frames + // at least once during the latest slice. + longestDuration: Math.round(Math.log2(Preferences.get("browser.addon-watch.limits.longestDuration", 128))), + }; + + // By default, warn only after an add-on has been spotted misbehaving 3 times. + let tolerance = Preferences.get("browser.addon-watch.tolerance", 3); + + for (let item of snapshot.componentsData) { + let addonId = item.addonId; + if (!item.isSystem || !addonId) { + // We are only interested in add-ons. + continue; + } + if (this._ignoreList.has(addonId)) { + // This add-on has been explicitly put in the ignore list + // by the user. Don't waste time with it. + continue; + } + let previous = this._previousPerformanceIndicators[addonId]; + this._previousPerformanceIndicators[addonId] = item; + + if (!previous) { + // This is the first time we see the addon, so we are probably + // executed right during/after startup. Performance is always + // weird during startup, with the JIT warming up, competition + // in disk access, etc. so we do not take this as a reason to + // display the slow addon warning. + continue; + } + + // Report misbehaviors to Telemetry + + let diff = item.subtract(previous); + if ("jank" in diff && diff.jank.longestDuration > 5) { + Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_JANK_LEVEL"). + add(addonId, diff.jank.longestDuration); + } + if ("cpow" in diff && diff.cpow.totalCPOWTime > 0) { + Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_CPOW_TIME_MS"). + add(addonId, diff.cpow.totalCPOWTime / 1000); + } + + // Store misbehaviors for about:performance and other clients + let stats = this._stats.get(addonId); + if (!stats) { + stats = { + peaks: {}, + alerts: {}, + }; + this._stats.set(addonId, stats); + } + + // Report misbehaviors to the user. + + for (let {probe, field: filter} of FILTERS) { + let peak = stats.peaks[filter] || 0; + let value = diff[probe][filter]; + stats.peaks[filter] = Math.max(value, peak); + + if (limits[filter] <= 0 || value <= limits[filter]) { + continue; + } + + stats.alerts[filter] = (stats.alerts[filter] || 0) + 1; + + if (stats.alerts[filter] % tolerance != 0) { + continue; + } + + try { + this._callback(addonId, filter); + } catch (ex) { + Cu.reportError("Error in AddonWatcher._checkAddons callback " + ex); + Cu.reportError(ex.stack); + } + } + } + } catch (ex) { + Cu.reportError("Error in AddonWatcher._checkAddons " + ex); + Cu.reportError(Task.Debugging.generateReadableStack(ex.stack)); + } + }.bind(this)); + }, + ignoreAddonForSession: function(addonid) { + this._ignoreList.add(addonid); + }, + ignoreAddonPermanently: function(addonid) { + this._ignoreList.add(addonid); + try { + let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]")) + if (!ignoreList.includes(addonid)) { + ignoreList.push(addonid); + Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList)); + } + } catch (ex) { + Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid])); + } + }, + /** + * The list of alerts for this session. + * + * @type {Map} A map associating addonId to + * objects with fields + * - {Object} peaks The highest values encountered for each filter. + * - {number} longestDuration + * - {number} totalCPOWTime + * - {Object} alerts The number of alerts for each filter. + * - {number} longestDuration + * - {number} totalCPOWTime + */ + get alerts() { + let result = new Map(); + for (let [k, v] of this._stats) { + result.set(k, Cu.cloneInto(v, this)); + } + return result; + }, +}; diff --git a/toolkit/components/perfmonitoring/PerformanceStats.jsm b/toolkit/components/perfmonitoring/PerformanceStats.jsm new file mode 100644 index 0000000000..8ff56cf47e --- /dev/null +++ b/toolkit/components/perfmonitoring/PerformanceStats.jsm @@ -0,0 +1,538 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["PerformanceStats"]; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + +/** + * API for querying and examining performance data. + * + * This API exposes data from several probes implemented by the JavaScript VM. + * See `PerformanceStats.getMonitor()` for information on how to monitor data + * from one or more probes and `PerformanceData` for the information obtained + * from the probes. + * + * Data is collected by "Performance Group". Typically, a Performance Group + * is an add-on, or a frame, or the internals of the application. + */ + +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); +Cu.import("resource://gre/modules/Services.jsm", this); + +// The nsIPerformanceStatsService provides lower-level +// access to SpiderMonkey and the probes. +XPCOMUtils.defineLazyServiceGetter(this, "performanceStatsService", + "@mozilla.org/toolkit/performance-stats-service;1", + Ci.nsIPerformanceStatsService); + +// The finalizer lets us automatically release (and when possible deactivate) +// probes when a monitor is garbage-collected. +XPCOMUtils.defineLazyServiceGetter(this, "finalizer", + "@mozilla.org/toolkit/finalizationwitness;1", + Ci.nsIFinalizationWitnessService +); + + +// The topic used to notify that a PerformanceMonitor has been garbage-collected +// and that we can release/close the probes it holds. +const FINALIZATION_TOPIC = "performancemonitor-finalize"; + +const PROPERTIES_META_IMMUTABLE = ["name", "addonId", "isSystem", "groupId"]; +const PROPERTIES_META = [...PROPERTIES_META_IMMUTABLE, "windowId", "title"]; + +/** + * Access to a low-level performance probe. + * + * Each probe is dedicated to some form of performance monitoring. + * As each probe may have a performance impact, a probe is activated + * only when a client has requested a PerformanceMonitor for this probe, + * and deactivated once all clients are disposed of. + */ +function Probe(name, impl) { + this._name = name; + this._counter = 0; + this._impl = impl; +} +Probe.prototype = { + /** + * Acquire the probe on behalf of a client. + * + * If the probe was inactive, activate it. Note that activating a probe + * can incur a memory or performance cost. + */ + acquire: function() { + if (this._counter == 0) { + this._impl.isActive = true; + } + this._counter++; + }, + + /** + * Release the probe on behalf of a client. + * + * If this was the last client for this probe, deactivate it. + */ + release: function() { + this._counter--; + if (this._counter == 0) { + this._impl.isActive = false; + } + }, + + /** + * Obtain data from this probe, once it is available. + * + * @param {nsIPerformanceStats} xpcom A xpcom object obtained from + * SpiderMonkey. Only the fields updated by the low-level probe + * are in a specified state. + * @return {object} An object containing the data extracted from this + * probe. Actual format depends on the probe. + */ + extract: function(xpcom) { + if (!this._impl.isActive) { + throw new Error(`Probe is inactive: ${this._name}`); + } + return this._impl.extract(xpcom); + }, + + /** + * @param {object} a An object returned by `this.extract()`. + * @param {object} b An object returned by `this.extract()`. + * + * @return {true} If `a` and `b` hold identical values. + */ + isEqual: function(a, b) { + if (a == null && b == null) { + return true; + } + if (a != null && b != null) { + return this._impl.isEqual(a, b); + } + return false; + }, + + /** + * @param {object} a An object returned by `this.extract()`. May + * NOT be `null`. + * @param {object} b An object returned by `this.extract()`. May + * be `null`. + * + * @return {object} An object representing `a - b`. If `b` is + * `null`, this is `a`. + */ + substract: function(a, b) { + if (a == null) { + throw new TypeError(); + } + if (b == null) { + return a; + } + return this._impl.substract(a, b); + }, + + /** + * The name of the probe. + */ + get name() { + return this._name; + } +}; + +// Utility function. Return the position of the last non-0 item in an +// array, or -1 if there isn't any such item. +function lastNonZero(array) { + for (let i = array.length - 1; i >= 0; --i) { + if (array[i] != 0) { + return i; + } + } + return -1; +} + +/** + * The actual Probes implemented by SpiderMonkey. + */ +let Probes = { + /** + * A probe measuring jank. + * + * Data provided by this probe uses the following format: + * + * @field {number} totalCPUTime The total amount of time spent using the + * CPU for this performance group, in µs. + * @field {number} totalSystemTime The total amount of time spent in the + * kernel for this performance group, in µs. + * @field {Array} durations An array containing at each position `i` + * the number of times execution of this component has lasted at least `2^i` + * milliseconds. + * @field {number} longestDuration The index of the highest non-0 value in + * `durations`. + */ + jank: new Probe("jank", { + set isActive(x) { + performanceStatsService.isMonitoringJank = x; + }, + get isActive() { + return performanceStatsService.isMonitoringJank; + }, + extract: function(xpcom) { + let durations = xpcom.getDurations(); + return { + totalUserTime: xpcom.totalUserTime, + totalSystemTime: xpcom.totalSystemTime, + durations: durations, + longestDuration: lastNonZero(durations) + } + }, + isEqual: function(a, b) { + // invariant: `a` and `b` are both non-null + if (a.totalUserTime != b.totalUserTime) { + return false; + } + if (a.totalSystemTime != b.totalSystemTime) { + return false; + } + for (let i = 0; i < a.durations.length; ++i) { + if (a.durations[i] != b.durations[i]) { + return false; + } + } + return true; + }, + substract: function(a, b) { + // invariant: `a` and `b` are both non-null + let result = { + totalUserTime: a.totalUserTime - b.totalUserTime, + totalSystemTime: a.totalSystemTime - b.totalSystemTime, + durations: [], + longestDuration: -1, + }; + for (let i = 0; i < a.durations.length; ++i) { + result.durations[i] = a.durations[i] - b.durations[i]; + } + result.longestDuration = lastNonZero(result.durations); + return result; + } + }), + + /** + * A probe measuring CPOW activity. + * + * Data provided by this probe uses the following format: + * + * @field {number} totalCPOWTime The amount of wallclock time + * spent executing blocking cross-process calls, in µs. + */ + cpow: new Probe("cpow", { + set isActive(x) { + performanceStatsService.isMonitoringCPOW = x; + }, + get isActive() { + return performanceStatsService.isMonitoringCPOW; + }, + extract: function(xpcom) { + return { + totalCPOWTime: xpcom.totalCPOWTime + }; + }, + isEqual: function(a, b) { + return a.totalCPOWTime == b.totalCPOWTime; + }, + substract: function(a, b) { + return { + totalCPOWTime: a.totalCPOWTime - b.totalCPOWTime + }; + } + }), + + /** + * A probe measuring activations, i.e. the number + * of times code execution has entered a given + * PerformanceGroup. + * + * Note that this probe is always active. + * + * Data provided by this probe uses the following format: + * @type {number} ticks The number of times execution has entered + * this performance group. + */ + ticks: new Probe("ticks", { + set isActive(x) { /* this probe cannot be deactivated */ }, + get isActive() { return true; }, + extract: function(xpcom) { + return { + ticks: xpcom.ticks + }; + }, + isEqual: function(a, b) { + return a.ticks == b.ticks; + }, + substract: function(a, b) { + return { + ticks: a.ticks - b.ticks + }; + } + }), +}; + + +/** + * A monitor for a set of probes. + * + * Keeping probes active when they are unused is often a bad + * idea for performance reasons. Upon destruction, or whenever + * a client calls `dispose`, this monitor releases the probes, + * which may let the system deactivate them. + */ +function PerformanceMonitor(probes) { + this._probes = probes; + + // Activate low-level features as needed + for (let probe of probes) { + probe.acquire(); + } + + // A finalization witness. At some point after the garbage-collection of + // `this` object, a notification of `FINALIZATION_TOPIC` will be triggered + // with `id` as message. + this._id = PerformanceMonitor.makeId(); + this._finalizer = finalizer.make(FINALIZATION_TOPIC, this._id) + PerformanceMonitor._monitors.set(this._id, probes); +} +PerformanceMonitor.prototype = { + /** + * Return asynchronously a snapshot with the data + * for each probe monitored by this PerformanceMonitor. + * + * All numeric values are non-negative and can only increase. Depending on + * the probe and the underlying operating system, probes may not be available + * immediately and may miss some activity. + * + * Clients should NOT expect that the first call to `promiseSnapshot()` + * will return a `Snapshot` in which all values are 0. For most uses, + * the appropriate scenario is to perform a first call to `promiseSnapshot()` + * to obtain a baseline, and then watch evolution of the values by calling + * `promiseSnapshot()` and `substract()`. + * + * On the other hand, numeric values are also monotonic across several instances + * of a PerformanceMonitor with the same probes. + * let a = PerformanceStats.getMonitor(someProbes); + * let snapshot1 = yield a.promiseSnapshot(); + * + * // ... + * let b = PerformanceStats.getMonitor(someProbes); // Same list of probes + * let snapshot2 = yield b.promiseSnapshot(); + * + * // all values of `snapshot2` are greater or equal to values of `snapshot1`. + * + * @return {Promise} + * @resolve {Snapshot} + */ + promiseSnapshot: function() { + if (!this._finalizer) { + throw new Error("dispose() has already been called, this PerformanceMonitor is not usable anymore"); + } + // Current implementation is actually synchronous. + return Promise.resolve().then(() => new Snapshot({ + xpcom: performanceStatsService.getSnapshot(), + probes: this._probes + })); + }, + + /** + * Release the probes used by this monitor. + * + * Releasing probes as soon as they are unused is a good idea, as some probes + * cost CPU and/or memory. + */ + dispose: function() { + if (!this._finalizer) { + return; + } + this._finalizer.forget(); + PerformanceMonitor.dispose(this._id); + + // As a safeguard against double-release, reset everything to `null` + this._probes = null; + this._id = null; + this._finalizer = null; + } +}; +/** + * @type {Map>} A map from id (as produced by `makeId`) + * to list of probes. Used to deallocate a list of probes during finalization. + */ +PerformanceMonitor._monitors = new Map(); + +/** + * Create a `PerformanceMonitor` for a list of probes, register it for + * finalization. + */ +PerformanceMonitor.make = function(probeNames) { + // Sanity checks + if (!Array.isArray(probeNames)) { + throw new TypeError("Expected an array, got " + probes); + } + let probes = []; + for (let probeName of probeNames) { + if (!(probeName in Probes)) { + throw new TypeError("Probe not implemented: " + k); + } + probes.push(Probes[probeName]); + } + + return new PerformanceMonitor(probes); +}; + +/** + * Implementation of `dispose`. + * + * The actual implementation of `dispose` is as a method of `PerformanceMonitor`, + * rather than `PerformanceMonitor.prototype`, to avoid needing a strong reference + * to instances of `PerformanceMonitor`, which would defeat the purpose of + * finalization. + */ +PerformanceMonitor.dispose = function(id) { + let probes = PerformanceMonitor._monitors.get(id); + if (!probes) { + throw new TypeError("`dispose()` has already been called on this monitor"); + } + + PerformanceMonitor._monitors.delete(id); + for (let probe of probes) { + probe.release(); + } +} + +// Generate a unique id for each PerformanceMonitor. Used during +// finalization. +PerformanceMonitor._counter = 0; +PerformanceMonitor.makeId = function() { + return "PerformanceMonitor-" + (this._counter++); +} + +// Once a `PerformanceMonitor` has been garbage-collected, +// release the probes unless `dispose()` has already been called. +Services.obs.addObserver(function(subject, topic, value) { + PerformanceMonitor.dispose(value); +}, FINALIZATION_TOPIC, false); + +// Public API +this.PerformanceStats = { + /** + * Create a monitor for observing a set of performance probes. + */ + getMonitor: function(probes) { + return PerformanceMonitor.make(probes); + } +}; + + +/** + * Information on a single performance group. + * + * This offers the following fields: + * + * @field {string} name The name of the performance group: + * - 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. + * + * @field {string} addonId The identifier of the addon (e.g. "myaddon@foo.bar"). + * + * @field {string|null} title The title of the webpage to which this code + * belongs. Note that this is the title of the entire webpage (i.e. the tab), + * even if the code is executed in an iframe. Also note that this title may + * change over time. + * + * @field {number} windowId The outer window ID of the top-level nsIDOMWindow + * to which this code belongs. May be 0 if the code doesn't belong to any + * nsIDOMWindow. + * + * @field {boolean} isSystem `true` if the component is a system component (i.e. + * an add-on or platform-code), `false` otherwise (i.e. a webpage). + * + * @field {object|undefined} activations See the documentation of probe "ticks". + * `undefined` if this probe is not active. + * + * @field {object|undefined} jank See the documentation of probe "jank". + * `undefined` if this probe is not active. + * + * @field {object|undefined} cpow See the documentation of probe "cpow". + * `undefined` if this probe is not active. + */ +function PerformanceData({xpcom, probes}) { + for (let k of PROPERTIES_META) { + this[k] = xpcom[k]; + } + for (let probe of probes) { + this[probe.name] = probe.extract(xpcom); + } +} +PerformanceData.prototype = { + /** + * Compare two instances of `PerformanceData` + * + * @return `true` if `this` and `to` have equal values in all fields. + */ + equals: function(to) { + if (!(to instanceof PerformanceData)) { + throw new TypeError(); + } + for (let probeName of Object.keys(Probes)) { + let probe = Probes[probeName]; + if (!probe.isEqual(this[probeName], to[probeName])) { + return false; + } + } + }, + + /** + * Compute the delta between two instances of `PerformanceData`. + * + * @param {PerformanceData|null} to. If `null`, assumed an instance of + * `PerformanceData` in which all numeric values are 0. + * + * @return {PerformanceDiff} The performance usage between `to` and `this`. + */ + subtract: function(to = null) { + return new PerformanceDiff(this, to); + } +}; + +/** + * The delta between two instances of `PerformanceData`. + * + * Used to monitor resource usage between two timestamps. + */ +function PerformanceDiff(current, old = null) { + for (let k of PROPERTIES_META) { + this[k] = current[k]; + } + + for (let probeName of Object.keys(Probes)) { + let other = old ? old[probeName] : null; + if (probeName in current) { + this[probeName] = Probes[probeName].substract(current[probeName], other); + } + } +} + +/** + * A snapshot of the performance usage of the process. + */ +function Snapshot({xpcom, probes}) { + this.componentsData = []; + let enumeration = xpcom.getComponentsData().enumerate(); + while (enumeration.hasMoreElements()) { + let stat = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats); + this.componentsData.push(new PerformanceData({xpcom: stat, probes})); + } + this.processData = new PerformanceData({xpcom: xpcom.getProcessData(), probes}); +} diff --git a/toolkit/components/perfmonitoring/moz.build b/toolkit/components/perfmonitoring/moz.build new file mode 100644 index 0000000000..a649065295 --- /dev/null +++ b/toolkit/components/perfmonitoring/moz.build @@ -0,0 +1,35 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] +BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini'] + +FAIL_ON_WARNINGS = True + +XPIDL_MODULE = 'toolkit_perfmonitoring' + +EXTRA_JS_MODULES += [ + 'AddonWatcher.jsm', + 'PerformanceStats.jsm', +] + +XPIDL_SOURCES += [ + 'nsIPerformanceStats.idl', +] + +UNIFIED_SOURCES += [ + 'nsPerformanceStats.cpp' +] + +EXPORTS += [ + 'nsPerformanceStats.h' +] + +LOCAL_INCLUDES += [ + '/dom/base', +] + +FINAL_LIBRARY = 'xul' diff --git a/toolkit/components/aboutperformance/nsIPerformanceStats.idl b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl similarity index 93% rename from toolkit/components/aboutperformance/nsIPerformanceStats.idl rename to toolkit/components/perfmonitoring/nsIPerformanceStats.idl index eaae3a860c..272178e253 100644 --- a/toolkit/components/aboutperformance/nsIPerformanceStats.idl +++ b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl @@ -22,8 +22,16 @@ * All values are monotonic and are updated only when * `nsIPerformanceStatsService.isStopwatchActive` is `true`. */ -[scriptable, uuid(b060d75d-55bc-4c82-a4ff-458fc5ab2a69)] +[scriptable, uuid(47f8d36d-1d67-43cb-befd-d2f4720ac568)] interface nsIPerformanceStats: nsISupports { + /** + * An identifier unique to the component. + * + * This identifier is somewhat human-readable to aid with debugging, + * but clients should not rely upon the format. + */ + readonly attribute AString groupId; + /** * The name of the component: * - for the process itself, ""; diff --git a/toolkit/components/aboutperformance/nsPerformanceStats.cpp b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp similarity index 84% rename from toolkit/components/aboutperformance/nsPerformanceStats.cpp rename to toolkit/components/perfmonitoring/nsPerformanceStats.cpp index 6a9114c31e..cb28de6616 100644 --- a/toolkit/components/aboutperformance/nsPerformanceStats.cpp +++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp @@ -22,15 +22,23 @@ #include "nsIDOMWindow.h" #include "nsGlobalWindow.h" +#if defined(XP_WIN) +#include "windows.h" +#else +#include +#endif + class nsPerformanceStats: public nsIPerformanceStats { public: nsPerformanceStats(const nsAString& aName, + const nsAString& aGroupId, const nsAString& aAddonId, const nsAString& aTitle, const uint64_t aWindowId, const bool aIsSystem, const js::PerformanceData& aPerformanceData) : mName(aName) + , mGroupId(aGroupId) , mAddonId(aAddonId) , mTitle(aTitle) , mWindowId(aWindowId) @@ -48,7 +56,13 @@ public: return NS_OK; }; - /* readonly attribute AString addon id; */ + /* readonly attribute AString groupId; */ + NS_IMETHOD GetGroupId(nsAString& aGroupId) override { + aGroupId.Assign(mGroupId); + return NS_OK; + }; + + /* readonly attribute AString addonId; */ NS_IMETHOD GetAddonId(nsAString& aAddonId) override { aAddonId.Assign(mAddonId); return NS_OK; @@ -111,6 +125,7 @@ public: private: nsString mName; + nsString mGroupId; nsString mAddonId; nsString mTitle; uint64_t mWindowId; @@ -131,7 +146,7 @@ public: NS_DECL_NSIPERFORMANCESNAPSHOT nsPerformanceSnapshot(); - nsresult Init(JSContext*); + nsresult Init(JSContext*, uint64_t processId); private: virtual ~nsPerformanceSnapshot(); @@ -145,20 +160,22 @@ private: * entire process, rather than the statistics for a specific set of * compartments. */ - already_AddRefed ImportStats(JSContext* cx, const js::PerformanceData& data); + already_AddRefed ImportStats(JSContext* cx, const js::PerformanceData& data, uint64_t uid); /** * Callbacks for iterating through the `PerformanceStats` of a runtime. */ - bool IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats); - static bool IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, void* self); + bool IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats, uint64_t uid); + static bool IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, uint64_t uid, 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); // If the context presents an add-on, extract the addon ID. // Otherwise, extract "". static void GetAddonId(JSContext*, @@ -172,6 +189,7 @@ private: private: nsCOMArray mComponentsData; nsCOMPtr mProcessData; + uint64_t mProcessId; }; NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot) @@ -199,7 +217,8 @@ nsPerformanceSnapshot::GetWindowData(JSContext* cx, return; } - nsCOMPtr top = win->GetTop(); + nsCOMPtr top; + top = win->GetTop(); if (!top) { return; } @@ -232,6 +251,22 @@ nsPerformanceSnapshot::GetAddonId(JSContext*, 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.AssignLiteral(", thread: "); + groupId.AppendInt(runtimeId); + groupId.AppendLiteral(", group: "); + groupId.AppendInt(uid); +} + /* static */ bool nsPerformanceSnapshot::GetIsSystem(JSContext*, JS::Handle global) @@ -240,7 +275,7 @@ nsPerformanceSnapshot::GetIsSystem(JSContext*, } already_AddRefed -nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance) { +nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance, const uint64_t uid) { JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); if (!global) { @@ -249,6 +284,9 @@ nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& per return nullptr; } + nsString groupId; + GetGroupId(cx, uid, groupId); + nsString addonId; GetAddonId(cx, global, addonId); @@ -264,18 +302,18 @@ nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& per bool isSystem = GetIsSystem(cx, global); nsCOMPtr result = - new nsPerformanceStats(name, addonId, title, windowId, isSystem, performance); + new nsPerformanceStats(name, groupId, addonId, title, windowId, isSystem, performance); return result.forget(); } /*static*/ bool -nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, void* self) { - return reinterpret_cast(self)->IterPerformanceStatsCallbackInternal(cx, stats); +nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, const uint64_t uid, void* self) { + return reinterpret_cast(self)->IterPerformanceStatsCallbackInternal(cx, stats, uid); } bool -nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats) { - nsCOMPtr result = ImportStats(cx, stats); +nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats, const uint64_t uid) { + nsCOMPtr result = ImportStats(cx, stats, uid); if (result) { mComponentsData.AppendElement(result); } @@ -284,13 +322,15 @@ nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, const } nsresult -nsPerformanceSnapshot::Init(JSContext* cx) { +nsPerformanceSnapshot::Init(JSContext* cx, uint64_t processId) { + mProcessId = processId; js::PerformanceData processStats; if (!js::IterPerformanceStats(cx, nsPerformanceSnapshot::IterPerformanceStatsCallback, &processStats, this)) { return NS_ERROR_UNEXPECTED; } mProcessData = new nsPerformanceStats(NS_LITERAL_STRING(""), // name + NS_LITERAL_STRING(""), // group id NS_LITERAL_STRING(""), // add-on id NS_LITERAL_STRING(""), // title 0, // window id @@ -325,6 +365,11 @@ NS_IMETHODIMP nsPerformanceSnapshot::GetProcessData(nsIPerformanceStats * *aProc NS_IMPL_ISUPPORTS(nsPerformanceStatsService, nsIPerformanceStatsService) nsPerformanceStatsService::nsPerformanceStatsService() +#if defined(XP_WIN) + : mProcessId(GetCurrentProcessId()) +#else + : mProcessId(getpid()) +#endif { } @@ -366,7 +411,7 @@ NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot) { nsRefPtr snapshot = new nsPerformanceSnapshot(); - nsresult rv = snapshot->Init(cx); + nsresult rv = snapshot->Init(cx, mProcessId); if (NS_FAILED(rv)) { return rv; } diff --git a/toolkit/components/aboutperformance/nsPerformanceStats.h b/toolkit/components/perfmonitoring/nsPerformanceStats.h similarity index 95% rename from toolkit/components/aboutperformance/nsPerformanceStats.h rename to toolkit/components/perfmonitoring/nsPerformanceStats.h index 54e1a89bbb..cb95891369 100644 --- a/toolkit/components/aboutperformance/nsPerformanceStats.h +++ b/toolkit/components/perfmonitoring/nsPerformanceStats.h @@ -19,6 +19,7 @@ public: private: virtual ~nsPerformanceStatsService(); + const uint64_t mProcessId; protected: }; diff --git a/toolkit/components/perfmonitoring/tests/browser/browser.ini b/toolkit/components/perfmonitoring/tests/browser/browser.ini new file mode 100644 index 0000000000..4975f2c88d --- /dev/null +++ b/toolkit/components/perfmonitoring/tests/browser/browser.ini @@ -0,0 +1,10 @@ +[DEFAULT] +head = head.js +support-files = + browser_Addons_sample.xpi + browser_compartments.html + browser_compartments_frame.html + browser_compartments_script.js + +[browser_AddonWatcher.js] +[browser_compartments.js] diff --git a/toolkit/modules/tests/browser/browser_AddonWatcher.js b/toolkit/components/perfmonitoring/tests/browser/browser_AddonWatcher.js similarity index 95% rename from toolkit/modules/tests/browser/browser_AddonWatcher.js rename to toolkit/components/perfmonitoring/tests/browser/browser_AddonWatcher.js index ba4bbdd91f..b6bf958c4f 100644 --- a/toolkit/modules/tests/browser/browser_AddonWatcher.js +++ b/toolkit/components/perfmonitoring/tests/browser/browser_AddonWatcher.js @@ -5,14 +5,12 @@ "use strict"; -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; - Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/AddonManager.jsm", this); Cu.import("resource://gre/modules/AddonWatcher.jsm", this); Cu.import("resource://gre/modules/Services.jsm", this); -const ADDON_URL = "http://example.com/browser/toolkit/modules/tests/browser/browser_Addons_sample.xpi"; +const ADDON_URL = "http://example.com/browser/toolkit/components/perfmonitoring/tests/browser/browser_Addons_sample.xpi"; const ADDON_ID = "addonwatcher-test@mozilla.com"; add_task(function* init() { @@ -60,7 +58,9 @@ let burn_rubber = Task.async(function*({histogramName, topic, expectedReason, pr info("Preparing add-on watcher"); let wait = new Promise(resolve => AddonWatcher.init((id, reason) => { Assert.equal(id, ADDON_ID, "The add-on watcher has detected the misbehaving addon"); - resolve(reason); + if (reason == expectedReason) { + resolve(reason); + } })); let done = false; wait = wait.then(result => { diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_Addons_sample.xpi b/toolkit/components/perfmonitoring/tests/browser/browser_Addons_sample.xpi new file mode 100644 index 0000000000000000000000000000000000000000..92e0019d6c7e9ce8cb479628fd1332ddd9d8406f GIT binary patch literal 2133 zcmaKtdpMM7AIBeup&eQj%Fu*f6N@pEsqi|d$v78NPVcVC5^4@RGiYbCX;2P{%QPf2 ziX5W1Op!1`7?dtC`f<5D0E|EYKo!sf4wA`q8l6g@Afsq_zb^mqOeH>Gcfim~kUoO9Ag{Yyu>NxI56vRHaRt${T zIfuxa`1Kv!A$G%~#C(QQN#~(0)3pi&gW=qe?kQD{(#}!Ttgp$&;4vnY?E;`;uh|+U z>UeSeDJ5ynrg;%8)(z-`a{uY(bi`KNDMyxzh5@Q%w~6)rsuN`o-8;KNEjp{t`?QPu z|Kq(Q6*fraJy;`#4HzFhTA6S_npjnuxuA{0T^%9Z3DiG39F7gRscmYN;J2Z8f3P*b zn5^6)nVniB(OF+gQAI!3L=$1#EP8T9zGOeqIP+G(OSE(k1!e6qKB7JG&6)Pw7jl$e z#B-oV{n>oo_l$bj8+GqJJuRa6>$P_z4_|M>1Y zV)#;5O4ERszuBlff5(q$@iFOf2`sPDN-rQ!A{9)bkDaJc>qu*=D17pbKm}4@XV11j z*WEWWGf|foi;p2!qf(~#71{;WJrZ}q2A>9a6Pf0ib3^^+H7SRW2l8hgGIiM52atM> z%B-h@$@O=fq6aii^qWR43IigX2sEo-zZNYll^u;gm~Tbf@hthW*|k}crJHBv71a9S zD8$h1G6t)bK^FD%JuIFaYS8(54B`+nut#VwuhdBZ-Yr1V^g*27hHHVhJ5Wnv zop0N0TvYb?)A!Ky!&p~@W1Sr+@e$B@70~xc5{*tcas)~JHhfw4Tnd1hjBAvT8 zdY_7V<03=a{rNg)eBMIPqvOqZaggiXW=O#KLG^1kKb$PfN!=dH)>=F7pG>?j=XQ)| zv$-fZLfPY>J zt3xa4`9JmHDZ|HnP0pjbG)pr)wr5?sdOaf4Q&JNb6z#p(=kVS1By)4G*L$r}{UvF^ zL$$v;;OR%i`oTFh24I400ZR?+KxhP&ObkO338cvIFdBWi3x*Z)pX-AA1EDFd%XF`+ z2kx%W)Y}aAz}5VbInErp*Y#w_7)=h4?F0q60PqXz2zGh#`)iGCCo+j1Mxt9RZxPfb z_qkmA>#VI{Ix;SBNfw|j?Oas#`Zb<6?S;j!Vm*JZeGR&&!pfS#PESbu%`Q zxQP3;`nESjh7k>24z4PR2HTxFh;}u$TJB5RJTLL0X4=Nq#F?E&NFw8fn#1(7LuH9f z@xW4DW#C0$cs3+yYRF>{sx@}Ho6t1Br3B*{QcqL4YG$<|%=w0*(dFGSEHhF5ZQmHY z_PGE`=f2rWhUjPYc{1LLxT7BG+Glo_Wz9DxsfbC*~q|dE)3OUz6Vv2lZPw((|Fpr+)S_+K+y@= z^6$e6@^M&w(twr3)d8@Q_!^9azr_Ivl*Pfn09X6G5=d19R@)6iWp;lS`cLY9gaUv% zFyYiMgZ?QpNRx^DS=z^2T&?lSBf`P4{|D_;sX?|(>Q!urlI-pw;8O^;g1h|n8(FJ1 AzyJUM literal 0 HcmV?d00001 diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html new file mode 100644 index 0000000000..d7ee6c4189 --- /dev/null +++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.html @@ -0,0 +1,20 @@ + + + + + Main frame for test browser_compartments.js + + + +Main frame. + + + + + + + diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js new file mode 100644 index 0000000000..2575c5d0b1 --- /dev/null +++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js @@ -0,0 +1,226 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Test that we see jank that takes place in a webpage, + * and that jank from several iframes are actually charged + * to the top window. + */ +Cu.import("resource://gre/modules/PerformanceStats.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()}`; + +// This function is injected as source as a frameScript +function frameScript() { + try { + "use strict"; + + const { utils: Cu, classes: Cc, interfaces: Ci } = Components; + Cu.import("resource://gre/modules/PerformanceStats.jsm"); + + let performanceStatsService = + Cc["@mozilla.org/toolkit/performance-stats-service;1"]. + getService(Ci.nsIPerformanceStatsService); + + // Make sure that the stopwatch is now active. + let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks"]); + + addMessageListener("compartments-test:getStatistics", () => { + try { + monitor.promiseSnapshot().then(snapshot => { + sendAsyncMessage("compartments-test:getStatistics", snapshot); + }); + } catch (ex) { + Cu.reportError("Error in content (getStatistics): " + ex); + Cu.reportError(ex.stack); + } + }); + + addMessageListener("compartments-test:setTitles", titles => { + try { + content.document.title = titles.data.parent; + for (let i = 0; i < content.frames.length; ++i) { + content.frames[i].postMessage({title: titles.data.frames}, "*"); + } + console.log("content", "Done setting titles", content.document.title); + sendAsyncMessage("compartments-test:setTitles"); + } catch (ex) { + Cu.reportError("Error in content (setTitles): " + ex); + Cu.reportError(ex.stack); + } + }); + } catch (ex) { + Cu.reportError("Error in content (setup): " + ex); + Cu.reportError(ex.stack); + } +} + +function Assert_leq(a, b, msg) { + Assert.ok(a <= b, `${msg}: ${a} <= ${b}`); +} + +function monotinicity_tester(source, testName) { + // In the background, check invariants: + // - numeric data can only ever increase; + // - the name, addonId, isSystem of a component never changes; + // - the name, addonId, isSystem of the process data; + // - there is at most one component with a combination of `name` and `addonId`; + // - types, etc. + let previous = { + processData: null, + componentsMap: new Map(), + }; + + let sanityCheck = function(prev, next) { + if (prev == null) { + return; + } + for (let k of ["groupId", "addonId", "isSystem"]) { + SilentAssert.equal(prev[k], next[k], `Sanity check (${testName}): ${k} hasn't changed (${prev.name}).`); + } + for (let [probe, k] of [ + ["jank", "totalUserTime"], + ["jank", "totalSystemTime"], + ["cpow", "totalCPOWTime"], + ["ticks", "ticks"] + ]) { + SilentAssert.equal(typeof next[probe][k], "number", `Sanity check (${testName}): ${k} is a number.`); + SilentAssert.leq(prev[probe][k], next[probe][k], `Sanity check (${testName}): ${k} is monotonic.`); + SilentAssert.leq(0, next[probe][k], `Sanity check (${testName}): ${k} is >= 0.`) + } + SilentAssert.equal(prev.jank.durations.length, next.jank.durations.length); + for (let i = 0; i < next.jank.durations.length; ++i) { + SilentAssert.ok(typeof next.jank.durations[i] == "number" && next.jank.durations[i] >= 0, + `Sanity check (${testName}): durations[${i}] is a non-negative number.`); + SilentAssert.leq(prev.jank.durations[i], next.jank.durations[i], + `Sanity check (${testName}): durations[${i}] is monotonic.`); + } + for (let i = 0; i < next.jank.durations.length - 1; ++i) { + SilentAssert.leq(next.jank.durations[i + 1], next.jank.durations[i], + `Sanity check (${testName}): durations[${i}] >= durations[${i + 1}].`) + } + }; + let iteration = 0; + let frameCheck = Task.async(function*() { + let name = `${testName}: ${iteration++}`; + let snapshot = yield source(); + if (!snapshot) { + // This can happen at the end of the test when we attempt + // to communicate too late with the content process. + window.clearInterval(interval); + return; + } + + // Sanity check on the process data. + sanityCheck(previous.processData, snapshot.processData); + Assert.equal(snapshot.processData.isSystem, true); + Assert.equal(snapshot.processData.name, ""); + Assert.equal(snapshot.processData.addonId, ""); + previous.procesData = snapshot.processData; + + // Sanity check on components data. + let map = new Map(); + for (let item of snapshot.componentsData) { + for (let [probe, k] of [ + ["jank", "totalUserTime"], + ["jank", "totalSystemTime"], + ["cpow", "totalCPOWTime"] + ]) { + SilentAssert.leq(item[probe][k], snapshot.processData[probe][k], + `Sanity check (${testName}): component has a lower ${k} than process`); + } + + let key = item.groupId; + SilentAssert.ok(!map.has(key), "The component hasn't been seen yet."); + map.set(key, item); + } + for (let [key, item] of map) { + sanityCheck(previous.componentsMap.get(key), item); + previous.componentsMap.set(key, item); + } + }); + let interval = window.setInterval(frameCheck, 300); + registerCleanupFunction(() => { + window.clearInterval(interval); + }); +} + +add_task(function* test() { + let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks"]); + + info("Extracting initial state"); + let stats0 = yield monitor.promiseSnapshot(); + Assert.notEqual(stats0.componentsData.length, 0, "There is more than one component"); + Assert.ok(!stats0.componentsData.find(stat => stat.name.indexOf(URL) != -1), + "The url doesn't appear yet"); + + let newTab = gBrowser.addTab(); + let browser = newTab.linkedBrowser; + // Setup monitoring in the tab + info("Setting up monitoring in the tab"); + yield ContentTask.spawn(newTab.linkedBrowser, null, frameScript); + + info("Opening URL"); + newTab.linkedBrowser.loadURI(URL); + + if (Services.sysinfo.getPropertyAsAString("name") == "Windows_NT") { + info("Deactivating sanity checks under Windows (bug 1151240)"); + } else { + info("Setting up sanity checks"); + monotinicity_tester(() => monitor.promiseSnapshot(), "parent process"); + monotinicity_tester(() => promiseContentResponseOrNull(browser, "compartments-test:getStatistics", null), "content process" ); + } + + let skipTotalUserTime = hasLowPrecision(); + + + while (true) { + yield new Promise(resolve => setTimeout(resolve, 100)); + + // We may have race conditions with DOM loading. + // Don't waste too much brainpower here, let's just ask + // repeatedly for the title to be changed, until this works. + info("Setting titles"); + yield promiseContentResponse(browser, "compartments-test:setTitles", { + parent: PARENT_TITLE, + frames: FRAME_TITLE + }); + info("Titles set"); + + let stats = (yield promiseContentResponse(browser, "compartments-test:getStatistics", null)); + + let titles = [for(stat of stats.componentsData) stat.title]; + + for (let stat of stats.componentsData) { + info(`Compartment: ${stat.name} => ${stat.title} (${stat.isSystem?"system":"web"})`); + } + + // While the webpage consists in three compartments, we should see only + // one `PerformanceData` in `componentsData`. Its `name` is undefined + // (could be either the main frame or one of its subframes), but its + // `title` should be the title of the main frame. + info(`Searching for frame title '${FRAME_TITLE}' in ${JSON.stringify(titles)} (I hope not to find it)`); + Assert.ok(!titles.includes(FRAME_TITLE), "Searching by title, the frames don't show up in the list of components"); + + info(`Searching for window title '${PARENT_TITLE}' in ${JSON.stringify(titles)} (I hope to find it)`); + let parent = stats.componentsData.find(x => x.title == PARENT_TITLE); + if (!parent) { + info("Searching by title, we didn't find the main frame"); + continue; + } + + if (skipTotalUserTime || parent.jank.totalUserTime > 1000) { + break; + } else { + info(`Not enough CPU time detected: ${parent.jank.totalUserTime}`) + } + } + + // Cleanup + gBrowser.removeTab(newTab); +}); diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_compartments_frame.html b/toolkit/components/perfmonitoring/tests/browser/browser_compartments_frame.html new file mode 100644 index 0000000000..69edfe871b --- /dev/null +++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments_frame.html @@ -0,0 +1,12 @@ + + + + + Subframe for test browser_compartments.html (do not change this title) + + + + +Subframe loaded. + + diff --git a/toolkit/components/perfmonitoring/tests/browser/browser_compartments_script.js b/toolkit/components/perfmonitoring/tests/browser/browser_compartments_script.js new file mode 100644 index 0000000000..3d5f7114f6 --- /dev/null +++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments_script.js @@ -0,0 +1,29 @@ + +var carryOn = true; + +window.addEventListener("message", e => { + console.log("frame content", "message", e); + if ("title" in e.data) { + document.title = e.data.title; + } + if ("stop" in e.data) { + carryOn = false; + } +}); + +// Use some CPU. +var interval = window.setInterval(() => { + if (!carryOn) { + window.clearInterval(interval); + return; + } + + // Compute an arbitrary value, print it out to make sure that the JS + // engine doesn't discard all our computation. + var date = Date.now(); + var array = []; + var i = 0; + while (Date.now() - date <= 100) { + array[i%2] = i++; + } +}, 300); diff --git a/toolkit/components/perfmonitoring/tests/browser/head.js b/toolkit/components/perfmonitoring/tests/browser/head.js new file mode 100644 index 0000000000..60ca6dd6e9 --- /dev/null +++ b/toolkit/components/perfmonitoring/tests/browser/head.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { utils: Cu, interfaces: Ci, classes: Cc } = Components; + +function promiseContentResponse(browser, name, message) { + let mm = browser.messageManager; + let promise = new Promise(resolve => { + function removeListener() { + mm.removeMessageListener(name, listener); + } + + function listener(msg) { + removeListener(); + resolve(msg.data); + } + + mm.addMessageListener(name, listener); + registerCleanupFunction(removeListener); + }); + mm.sendAsyncMessage(name, message); + return promise; +} +function promiseContentResponseOrNull(browser, name, message) { + if (!browser.messageManager) { + return null; + } + return promiseContentResponse(browser, name, message); +} + +/** + * `true` if we are running an OS in which the OS performance + * clock has a low precision and might unpredictably + * never be updated during the execution of the test. + */ +function hasLowPrecision() { + let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")]; + info(`Running ${sysName} version ${sysVersion}`); + + if (sysName == "Windows_NT" && sysVersion < 6) { + info("Running old Windows, need to deactivate tests due to bad precision."); + return true; + } + if (sysName == "Linux" && sysVersion <= 2.6) { + info("Running old Linux, need to deactivate tests due to bad precision."); + return true; + } + info("This platform has good precision.") + return false; +} diff --git a/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js b/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js similarity index 75% rename from toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js rename to toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js index 88c0712ee4..44a10a08ba 100644 --- a/toolkit/components/aboutperformance/tests/xpcshell/test_compartments.js +++ b/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js @@ -13,24 +13,33 @@ function run_test() { let promiseStatistics = Task.async(function*(name) { yield Promise.resolve(); // Make sure that we wait until // statistics have been updated. - let snapshot = PerformanceStats.getSnapshot(); - do_print("Statistics: " + name); - do_print(JSON.stringify(snapshot.processData, null, "\t")); - do_print(JSON.stringify(snapshot.componentsData, null, "\t")); - return snapshot; + let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"]. + getService(Ci.nsIPerformanceStatsService); + let snapshot = service.getSnapshot(); + let componentsData = []; + let componentsEnum = snapshot.getComponentsData().enumerate(); + while (componentsEnum.hasMoreElements()) { + componentsData.push(componentsEnum.getNext().QueryInterface(Ci.nsIPerformanceStats)); + } + return { + processData: snapshot.getProcessData(), + componentsData + }; }); let promiseSetMonitoring = Task.async(function*(to) { - Cc["@mozilla.org/toolkit/performance-stats-service;1"]. - getService(Ci.nsIPerformanceStatsService). - isStopwatchActive = to; + let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"]. + getService(Ci.nsIPerformanceStatsService); + service.isMonitoringJank = to; + service.isMonitoringCPOW = to; yield Promise.resolve(); }); -function getBuiltinStatistics(snapshot) { +function getBuiltinStatistics(name, snapshot) { let stats = snapshot.componentsData.find(stats => stats.isSystem && !stats.addonId ); + do_print(`Built-in statistics for ${name} were ${stats?"":"not "}found`); return stats; } @@ -65,16 +74,16 @@ function hasLowPrecision() { let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")]; do_print(`Running ${sysName} version ${sysVersion}`); - if (sysName != "Windows_NT") { - do_print("Not running Windows, precision should be good."); - return false; + if (sysName == "Windows_NT" && sysVersion < 6) { + do_print("Running old Windows, need to deactivate tests due to bad precision."); + return true; } - if (sysVersion >= 6) { - do_print("Running a recent version of Windows, precision should be good."); - return false; + if (sysName == "Linux" && sysVersion <= 2.6) { + do_print("Running old Linux, need to deactivate tests due to bad precision."); + return true; } - do_print("Running old Windows, need to deactivate tests due to bad precision."); - return true; + do_print("This platform has good precision.") + return false; } add_task(function* test_measure() { @@ -111,10 +120,10 @@ add_task(function* test_measure() { Assert.equal(process4.totalUserTime, process3.totalUserTime, "After deactivating the stopwatch, we didn't count any time"); Assert.equal(process4.totalCPOWTime, process3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time"); - let builtin1 = getBuiltinStatistics(stats1) || { totalUserTime: 0, totalCPOWTime: 0 }; - let builtin2 = getBuiltinStatistics(stats2); - let builtin3 = getBuiltinStatistics(stats3); - let builtin4 = getBuiltinStatistics(stats4); + let builtin1 = getBuiltinStatistics("Built-ins 1", stats1) || { totalUserTime: 0, totalCPOWTime: 0 }; + let builtin2 = getBuiltinStatistics("Built-ins 2", stats2); + let builtin3 = getBuiltinStatistics("Built-ins 3", stats3); + let builtin4 = getBuiltinStatistics("Built-ins 4", stats4); Assert.notEqual(builtin2, null, "Found the statistics for built-ins 2"); Assert.notEqual(builtin3, null, "Found the statistics for built-ins 3"); Assert.notEqual(builtin4, null, "Found the statistics for built-ins 4"); diff --git a/toolkit/components/aboutperformance/tests/xpcshell/xpcshell.ini b/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini similarity index 100% rename from toolkit/components/aboutperformance/tests/xpcshell/xpcshell.ini rename to toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini diff --git a/toolkit/components/startup/nsAppStartup.cpp b/toolkit/components/startup/nsAppStartup.cpp index 00e85a436b..ec052d8454 100644 --- a/toolkit/components/startup/nsAppStartup.cpp +++ b/toolkit/components/startup/nsAppStartup.cpp @@ -35,6 +35,7 @@ #include "mozilla/Services.h" #include "nsIXPConnect.h" #include "jsapi.h" +#include "js/Date.h" #include "prenv.h" #include "nsAppDirectoryServiceDefs.h" @@ -774,7 +775,7 @@ nsAppStartup::GetStartupInfo(JSContext* aCx, JS::MutableHandle aRetva if (stamp >= procTime) { PRTime prStamp = ComputeAbsoluteTimestamp(absNow, now, stamp) / PR_USEC_PER_MSEC; - JS::Rooted date(aCx, JS_NewDateObjectMsec(aCx, prStamp)); + JS::Rooted date(aCx, JS::NewDateObject(aCx, JS::TimeClip(prStamp))); JS_DefineProperty(aCx, obj, StartupTimeline::Describe(ev), date, JSPROP_ENUMERATE); } else { Telemetry::Accumulate(Telemetry::STARTUP_MEASUREMENT_ERRORS, ev); diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 78e5f5fc52..bf6881ec15 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -6464,6 +6464,24 @@ "extended_statistics_ok": true, "description": "Sanitize: Time it takes to sanitize cookies (ms)" }, + "FX_SANITIZE_COOKIES_2": { + "alert_emails": ["firefox-dev@mozilla.org"], + "expires_in_version": "50", + "kind": "exponential", + "high": "30000", + "n_buckets": 20, + "extended_statistics_ok": true, + "description": "Sanitize: Time it takes to sanitize firefox cookies (ms). A subset of FX_SANITIZE_COOKIES." + }, + "FX_SANITIZE_PLUGINS": { + "alert_emails": ["firefox-dev@mozilla.org"], + "expires_in_version": "50", + "kind": "exponential", + "high": "30000", + "n_buckets": 20, + "extended_statistics_ok": true, + "description": "Sanitize: Time it takes to sanitize plugin cookies (ms). A subset of FX_SANITIZE_COOKIES." + }, "FX_SANITIZE_OFFLINEAPPS": { "alert_emails": ["firefox-dev@mozilla.org", "gavin@mozilla.com"], "expires_in_version": "40", diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index b5041059d9..34f8fe94e3 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -3797,10 +3797,6 @@ DebuggerServer.ObjectActorPreviewers = { }], Date: [function({obj, threadActor}, aGrip) { - if (!obj.proto || obj.proto.class != "Date") { - return false; - } - let time = Date.prototype.getTime.call(obj.unsafeDereference()); aGrip.preview = { diff --git a/toolkit/devtools/webconsole/console-output.js b/toolkit/devtools/webconsole/console-output.js index f9243f9433..db5aa1dda1 100644 --- a/toolkit/devtools/webconsole/console-output.js +++ b/toolkit/devtools/webconsole/console-output.js @@ -1061,7 +1061,7 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype, * DOM node or a function to invoke. * @return Element */ - _renderBodyPiece: function(piece) + _renderBodyPiece: function(piece, options = {}) { if (piece instanceof Ci.nsIDOMNode) { return piece; @@ -1070,7 +1070,7 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype, return piece(this); } - return this._renderValueGrip(piece); + return this._renderValueGrip(piece, options); }, /** @@ -1409,20 +1409,21 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype, _renderBodyPieces: function(container) { let lastStyle = null; + let stylePieces = this._styles.length > 0 ? this._styles.length : 1; for (let i = 0; i < this._messagePieces.length; i++) { - let separator = i > 0 ? this._renderBodyPieceSeparator() : null; - if (separator) { - container.appendChild(separator); + // Pieces with an associated style definition come from "%c" formatting. + // For body pieces beyond that, add a separator before each one. + if (i >= stylePieces) { + container.appendChild(this._renderBodyPieceSeparator()); } let piece = this._messagePieces[i]; let style = this._styles[i]; // No long string support. - if (style && typeof style == "string" ) { - lastStyle = this.cleanupStyle(style); - } + lastStyle = (style && typeof style == "string") ? + this.cleanupStyle(style) : null; container.appendChild(this._renderBodyPiece(piece, lastStyle)); } @@ -1433,7 +1434,9 @@ Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype, _renderBodyPiece: function(piece, style) { - let elem = Messages.Extended.prototype._renderBodyPiece.call(this, piece); + // Skip quotes for top-level strings. + let options = { noStringQuotes: true }; + let elem = Messages.Extended.prototype._renderBodyPiece.call(this, piece, options); let result = elem; if (style) { @@ -3295,11 +3298,16 @@ Widgets.ObjectRenderers.add({ * The owning message. * @param object longStringActor * The LongStringActor to display. + * @param object options + * Options, such as noStringQuotes */ -Widgets.LongString = function(message, longStringActor) +Widgets.LongString = function(message, longStringActor, options) { Widgets.BaseWidget.call(this, message); this.longStringActor = longStringActor; + this.noStringQuotes = (options && "noStringQuotes" in options) ? + options.noStringQuotes : !this.message._quoteStrings; + this._onClick = this._onClick.bind(this); this._onSubstring = this._onSubstring.bind(this); }; @@ -3335,7 +3343,7 @@ Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype, _renderString: function(str) { this.element.textContent = VariablesView.getString(str, { - noStringQuotes: !this.message._quoteStrings, + noStringQuotes: this.noStringQuotes, noEllipsis: true, }); }, diff --git a/toolkit/forgetaboutsite/ForgetAboutSite.jsm b/toolkit/forgetaboutsite/ForgetAboutSite.jsm index d2afbf4953..5320420ae6 100644 --- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm +++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm @@ -119,67 +119,11 @@ this.ForgetAboutSite = { } // Downloads - promises.push(Task.spawn(function*() { - let useJSTransfer = false; - try { - // This method throws an exception if the old Download Manager is disabled. - Services.downloads.activeDownloadCount; - } catch (ex) { - useJSTransfer = true; - } - - if (useJSTransfer) { - let list = yield Downloads.getList(Downloads.ALL); - list.removeFinished(download => hasRootDomain( - NetUtil.newURI(download.source.url).host, aDomain)); - } else { - let dm = Cc["@mozilla.org/download-manager;1"]. - getService(Ci.nsIDownloadManager); - // Active downloads - for (let enumerator of [dm.activeDownloads, dm.activePrivateDownloads]) { - while (enumerator.hasMoreElements()) { - let dl = enumerator.getNext().QueryInterface(Ci.nsIDownload); - if (hasRootDomain(dl.source.host, aDomain)) { - dl.cancel(); - dl.remove(); - } - } - - const deleteAllLike = function(db) { - // NOTE: This is lossy, but we feel that it is OK to be lossy here and not - // invoke the cost of creating a URI for each download entry and - // ensure that the hostname matches. - let stmt = db.createStatement( - "DELETE FROM moz_downloads " + - "WHERE source LIKE ?1 ESCAPE '/' " + - "AND state NOT IN (?2, ?3, ?4)" - ); - let pattern = stmt.escapeStringForLIKE(aDomain, "/"); - stmt.bindByIndex(0, "%" + pattern + "%"); - stmt.bindByIndex(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING); - stmt.bindByIndex(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED); - stmt.bindByIndex(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED); - try { - stmt.execute(); - } finally { - stmt.finalize(); - } - } - - // Completed downloads - deleteAllLike(dm.DBConnection); - deleteAllLike(dm.privateDBConnection); - - // We want to rebuild the list if the UI is showing, so dispatch the - // observer topic - let os = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); - os.notifyObservers(null, "download-manager-remove-download", null); - } - } - }).catch(ex => { - throw new Error("Exception in clearing Downloads: " + ex); - })); + Task.spawn(function*() { + let list = yield Downloads.getList(Downloads.ALL); + list.removeFinished(download => hasRootDomain( + NetUtil.newURI(download.source.url).host, aDomain)); + }).then(null, Cu.reportError); // Passwords promises.push(Task.spawn(function*() { diff --git a/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js b/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js index cb78f4cc75..8e7a9ea4c4 100644 --- a/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js +++ b/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js @@ -15,7 +15,6 @@ var profileDir = do_get_profile(); function cleanUp() { let files = [ - "downloads.sqlite", "places.sqlite", "cookies.sqlite", "signons.sqlite", @@ -30,14 +29,3 @@ function cleanUp() } } cleanUp(); - -function oldDownloadManagerDisabled() -{ - try { - // This method throws an exception if the old Download Manager is disabled. - Services.downloads.activeDownloadCount; - } catch (ex) { - return true; - } - return false; -} diff --git a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js index e29548b722..32981e6833 100644 --- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js +++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js @@ -107,74 +107,6 @@ function check_cookie_exists(aDomain, aExists) checker(cm.cookieExists(cookie)); } -/** - * Adds a download to download history. - * - * @param aURIString - * The string of the URI to add. - * @param aIsActive - * If it should be set to an active state in the database. This does not - * make it show up in the list of active downloads however! - */ -function add_download(aURIString, aIsActive) -{ - function makeGUID() { - let guid = ""; - for (var i = 0; i < 12; i++) - guid += Math.floor(Math.random() * 10); - return guid; - } - - check_downloaded(aURIString, false); - let db = Cc["@mozilla.org/download-manager;1"]. - getService(Ci.nsIDownloadManager). - DBConnection; - let stmt = db.createStatement( - "INSERT INTO moz_downloads (source, state, guid) " + - "VALUES (:source, :state, :guid)" - ); - stmt.params.source = aURIString; - stmt.params.state = aIsActive ? Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING : - Ci.nsIDownloadManager.DOWNLOAD_FINISHED; - stmt.params.guid = makeGUID(); - try { - stmt.execute(); - } - finally { - stmt.finalize(); - } - check_downloaded(aURIString, true); -} - -/** - * Checks to ensure a URI string is in download history or not. - * - * @param aURIString - * The string of the URI to check. - * @param aIsDownloaded - * True if the URI should be downloaded, false otherwise. - */ -function check_downloaded(aURIString, aIsDownloaded) -{ - let db = Cc["@mozilla.org/download-manager;1"]. - getService(Ci.nsIDownloadManager). - DBConnection; - let stmt = db.createStatement( - "SELECT * " + - "FROM moz_downloads " + - "WHERE source = :source" - ); - stmt.params.source = aURIString; - - let checker = aIsDownloaded ? do_check_true : do_check_false; - try { - checker(stmt.executeStep()); - } - finally { - stmt.finalize(); - } -} - /** * Adds a disabled host to the login manager. * @@ -380,51 +312,6 @@ function* test_cookie_not_cleared_with_uri_contains_domain() check_cookie_exists(TEST_DOMAIN, true); } -// Download Manager -function* test_download_history_cleared_with_direct_match() -{ - if (oldDownloadManagerDisabled()) { - return; - } - - const TEST_URI = "http://mozilla.org/foo"; - add_download(TEST_URI, false); - yield ForgetAboutSite.removeDataFromDomain("mozilla.org"); - check_downloaded(TEST_URI, false); -} - -function* test_download_history_cleared_with_subdomain() -{ - if (oldDownloadManagerDisabled()) { - return; - } - - const TEST_URI = "http://www.mozilla.org/foo"; - add_download(TEST_URI, false); - yield ForgetAboutSite.removeDataFromDomain("mozilla.org"); - check_downloaded(TEST_URI, false); -} - -function* test_download_history_not_cleared_with_active_direct_match() -{ - if (oldDownloadManagerDisabled()) { - return; - } - - // Tests that downloads marked as active in the db are not deleted from the db - const TEST_URI = "http://mozilla.org/foo"; - add_download(TEST_URI, true); - yield ForgetAboutSite.removeDataFromDomain("mozilla.org"); - check_downloaded(TEST_URI, true); - - // Reset state - let db = Cc["@mozilla.org/download-manager;1"]. - getService(Ci.nsIDownloadManager). - DBConnection; - db.executeSimpleSQL("DELETE FROM moz_downloads"); - check_downloaded(TEST_URI, false); -} - // Login Manager function* test_login_manager_disabled_hosts_cleared_with_direct_match() { @@ -650,12 +537,6 @@ let tests = [ test_cookie_cleared_with_subdomain, test_cookie_not_cleared_with_uri_contains_domain, - // Download Manager - // Note: active downloads tested in test_removeDataFromDomain_activeDownloads.js - test_download_history_cleared_with_direct_match, - test_download_history_cleared_with_subdomain, - test_download_history_not_cleared_with_active_direct_match, - // Login Manager test_login_manager_disabled_hosts_cleared_with_direct_match, test_login_manager_disabled_hosts_cleared_with_subdomain, diff --git a/toolkit/modules/AddonWatcher.jsm b/toolkit/modules/AddonWatcher.jsm deleted file mode 100644 index eb26579cbe..0000000000 --- a/toolkit/modules/AddonWatcher.jsm +++ /dev/null @@ -1,204 +0,0 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["AddonWatcher"]; - -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Preferences", - "resource://gre/modules/Preferences.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "console", - "resource://gre/modules/devtools/Console.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PerformanceStats", - "resource://gre/modules/PerformanceStats.jsm"); -XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", - "@mozilla.org/base/telemetry;1", - Ci.nsITelemetry); -XPCOMUtils.defineLazyModuleGetter(this, "Services", - "resource://gre/modules/Services.jsm"); - -let AddonWatcher = { - _previousPerformanceIndicators: {}, - _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), - _callback: null, - /** - * The interval at which we poll the available performance information - * to find out about possibly slow add-ons, in milliseconds. - */ - _interval: 15000, - _ignoreList: null, - /** - * Initialize and launch the AddonWatcher. - * - * @param {function} callback A callback, called whenever we determine - * that an add-on is causing performance issues. It takes as argument - * {string} addonId The identifier of the add-on known to cause issues. - * {string} reason The reason for which the add-on has been flagged, - * as one of "totalCPOWTime" (the add-on has caused blocking process - * communications, which freeze the UX) - * Use preference "browser.addon-watch.limits.totalCPOWTime" to control - * the maximal amount of CPOW time per watch interval. - * - * or "longestDuration" (the add-on has caused user-visible missed frames). - * Use preference "browser.addon-watch.limits.longestDuration" to control - * the longest uninterrupted execution of code of an add-on during a watch - * interval. - */ - init: function(callback) { - if (!callback) { - return; - } - - if (this._callback) { - // Already initialized - return; - } - - this._interval = Preferences.get("browser.addon-watch.interval", 15000); - if (this._interval == -1) { - // Deactivated by preferences - return; - } - - this._callback = callback; - try { - this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null))); - } catch (ex) { - // probably some malformed JSON, ignore and carry on - this._ignoreList = new Set(); - } - - // Start monitoring - this.paused = false; - - Services.obs.addObserver(() => { - this.uninit(); - }, "profile-before-change", false); - }, - uninit: function() { - this.paused = true; - this._callback = null; - }, - - /** - * Interrupt temporarily add-on watching. - */ - set paused(isPaused) { - if (!this._callback || this._interval == -1) { - return; - } - if (isPaused) { - this._timer.cancel(); - } else { - PerformanceStats.init(); - this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK); - } - this._isPaused = isPaused; - }, - get paused() { - return this._isPaused; - }, - _isPaused: true, - - /** - * Check the performance of add-ons during the latest slice of time. - * - * We consider that an add-on is causing slowdown if it has executed - * without interruption for at least 64ms (4 frames) at least once - * during the latest slice, or if it has used any CPOW during the latest - * slice. - */ - _checkAddons: function() { - try { - let snapshot = PerformanceStats.getSnapshot(); - - let limits = { - // By default, warn if we have a total time of 1s of CPOW per 15 seconds - totalCPOWTime: Math.round(Preferences.get("browser.addon-watch.limits.totalCPOWTime", 1000) * this._interval / 15000), - // By default, warn if we have skipped 4 consecutive frames - // at least once during the latest slice. - longestDuration: Math.round(Math.log2(Preferences.get("browser.addon-watch.limits.longestDuration", 7))), - }; - - for (let item of snapshot.componentsData) { - let addonId = item.addonId; - if (!item.isSystem || !addonId) { - // We are only interested in add-ons. - continue; - } - if (this._ignoreList.has(addonId)) { - // This add-on has been explicitly put in the ignore list - // by the user. Don't waste time with it. - continue; - } - let previous = this._previousPerformanceIndicators[addonId]; - this._previousPerformanceIndicators[addonId] = item; - - if (!previous) { - // This is the first time we see the addon, so we are probably - // executed right during/after startup. Performance is always - // weird during startup, with the JIT warming up, competition - // in disk access, etc. so we do not take this as a reason to - // display the slow addon warning. - continue; - } - - // Report misbehaviors to Telemetry - - let diff = item.substract(previous); - if (diff.longestDuration > 5) { - Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_JANK_LEVEL"). - add(addonId, diff.longestDuration); - } - if (diff.totalCPOWTime > 0) { - Telemetry.getKeyedHistogramById("MISBEHAVING_ADDONS_CPOW_TIME_MS"). - add(addonId, diff.totalCPOWTime / 1000); - } - - // Report mibehaviors to the user. - let reason = null; - - for (let k of ["longestDuration", "totalCPOWTime"]) { - if (limits[k] > 0 && diff[k] > limits[k]) { - reason = k; - } - } - - if (!reason) { - continue; - } - - try { - this._callback(addonId, reason); - } catch (ex) { - Cu.reportError("Error in AddonWatcher._checkAddons callback " + ex); - Cu.reportError(ex.stack); - } - } - } catch (ex) { - Cu.reportError("Error in AddonWatcher._checkAddons " + ex); - Cu.reportError(ex.stack); - } - }, - ignoreAddonForSession: function(addonid) { - this._ignoreList.add(addonid); - }, - ignoreAddonPermanently: function(addonid) { - this._ignoreList.add(addonid); - try { - let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]")) - if (!ignoreList.includes(addonid)) { - ignoreList.push(addonid); - Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList)); - } - } catch (ex) { - Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid])); - } - } -}; diff --git a/toolkit/modules/PerformanceStats.jsm b/toolkit/modules/PerformanceStats.jsm deleted file mode 100644 index 47bbb4c061..0000000000 --- a/toolkit/modules/PerformanceStats.jsm +++ /dev/null @@ -1,205 +0,0 @@ -// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["PerformanceStats"]; - -const { classes: Cc, interfaces: Ci, utils: Cu } = Components; - -/** - * API for querying and examining performance data. - * - * The data exposed by this API is computed internally by the JavaScript VM. - * See `PerformanceData` for the detail of the information provided by this - * API. - */ - -Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); - -let performanceStatsService = - Cc["@mozilla.org/toolkit/performance-stats-service;1"]. - getService(Ci.nsIPerformanceStatsService); - - -const PROPERTIES_NUMBERED = ["totalUserTime", "totalSystemTime", "totalCPOWTime", "ticks"]; -const PROPERTIES_META = ["name", "addonId", "isSystem"]; -const PROPERTIES_FLAT = [...PROPERTIES_NUMBERED, ...PROPERTIES_META]; - -/** - * Information on a single component. - * - * This offers the following fields: - * - * @field {string} name 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. - * - * @field {string} addonId The identifier of the addon (e.g. "myaddon@foo.bar"). - * - * @field {boolean} isSystem `true` if the component is a system component (i.e. - * an add-on or platform-code), `false` otherwise (i.e. a webpage). - * - * @field {number} totalUserTime The total amount of time spent executing code. - * - * @field {number} totalSystemTime The total amount of time spent executing - * system calls. - * - * @field {number} totalCPOWTime The total amount of time spent waiting for - * blocking cross-process communications - * - * @field {number} ticks The number of times the JavaScript VM entered the code - * of this component to execute it. - * - * @field {Array} durations An array containing at each position `i` - * the number of times execution of this component has lasted at least `2^i` - * milliseconds. - * - * All numeric values are non-negative and can only increase. - */ -function PerformanceData(xpcom) { - for (let k of PROPERTIES_FLAT) { - this[k] = xpcom[k]; - } - this.durations = xpcom.getDurations(); -} -PerformanceData.prototype = { - /** - * Compare two instances of `PerformanceData` - * - * @return `true` if `this` and `to` have equal values in all fields. - */ - equals: function(to) { - if (!(to instanceof PerformanceData)) { - throw new TypeError(); - } - for (let k of PROPERTIES_FLAT) { - if (this[k] != to[k]) { - return false; - } - } - for (let i = 0; i < this.durations.length; ++i) { - if (to.durations[i] != this.durations[i]) { - return false; - } - } - return true; - }, - - /** - * Compute the delta between two instances of `PerformanceData`. - * - * @param {PerformanceData|null} to. If `null`, assumed an instance of - * `PerformanceData` in which all numeric values are 0. - * - * @return {PerformanceDiff} The performance usage between `to` and `this`. - */ - substract: function(to = null) { - return new PerformanceDiff(this, to); - } -}; - -/** - * The delta between two instances of `PerformanceData`. - * - * Used to monitor resource usage between two timestamps. - * - * @field {number} longestDuration An indication of the longest - * execution duration between two timestamps: - * - -1 == less than 1ms - * - 0 == [1, 2[ ms - * - 1 == [2, 4[ ms - * - 3 == [4, 8[ ms - * - 4 == [8, 16[ ms - * - ... - * - 7 == [128, ...] ms - */ -function PerformanceDiff(current, old = null) { - for (let k of PROPERTIES_META) { - this[k] = current[k]; - } - - if (old) { - if (!(old instanceof PerformanceData)) { - throw new TypeError(); - } - if (current.durations.length != old.durations.length) { - throw new TypeError("Internal error: mismatched length for `durations`."); - } - - this.durations = []; - - this.longestDuration = -1; - - for (let i = 0; i < current.durations.length; ++i) { - let delta = current.durations[i] - old.durations[i]; - this.durations[i] = delta; - if (delta > 0) { - this.longestDuration = i; - } - } - for (let k of PROPERTIES_NUMBERED) { - this[k] = current[k] - old[k]; - } - } else { - this.durations = current.durations.slice(0); - - for (let k of PROPERTIES_NUMBERED) { - this[k] = current[k]; - } - - this.longestDuration = -1; - for (let i = this.durations.length - 1; i >= 0; --i) { - if (this.durations[i] > 0) { - this.longestDuration = i; - break; - } - } - } -} - -/** - * A snapshot of the performance usage of the process. - */ -function Snapshot(xpcom) { - this.componentsData = []; - let enumeration = xpcom.getComponentsData().enumerate(); - while (enumeration.hasMoreElements()) { - let stat = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats); - this.componentsData.push(new PerformanceData(stat)); - } - this.processData = new PerformanceData(xpcom.getProcessData()); -} - - -this.PerformanceStats = { - /** - * Activate monitoring. - */ - init() { - // - // The implementation actually does nothing, as monitoring is - // initiated when loading the module. - // - // This function is actually provided as a gentle way to ensure - // that client code that imports `PerformanceStats` lazily - // does not forget to force the import, hence triggering - // actual load of the module. - // - }, - - /** - * Get a snapshot of the performance usage of the current process. - * - * @type {Snapshot} - */ - getSnapshot() { - return new Snapshot(performanceStatsService.getSnapshot()); - }, -}; - -performanceStatsService.isStopwatchActive = true; diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build index c2ea65e2fe..748cc84e6c 100644 --- a/toolkit/modules/moz.build +++ b/toolkit/modules/moz.build @@ -12,7 +12,6 @@ MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini'] SPHINX_TREES['toolkit_modules'] = 'docs' EXTRA_JS_MODULES += [ - 'AddonWatcher.jsm', 'Battery.jsm', 'BinarySearch.jsm', 'BrowserUtils.jsm', @@ -33,7 +32,6 @@ EXTRA_JS_MODULES += [ 'NewTabUtils.jsm', 'PageMenu.jsm', 'PageMetadata.jsm', - 'PerformanceStats.jsm', 'PermissionsUtils.jsm', 'PopupNotifications.jsm', 'Preferences.jsm', diff --git a/tools/profiler/GeckoProfiler.h b/tools/profiler/GeckoProfiler.h index 582bee919a..c822fa0439 100644 --- a/tools/profiler/GeckoProfiler.h +++ b/tools/profiler/GeckoProfiler.h @@ -55,7 +55,12 @@ namespace mozilla { class TimeStamp; -} + +namespace dom { +class Promise; +} // namespace dom + +} // namespace mozilla enum TracingMetadata { TRACING_DEFAULT, @@ -171,6 +176,10 @@ static inline JSObject* profiler_get_profile_jsobject(JSContext* aCx, return nullptr; } +// Get the profile encoded as a JSON object. +static inline void profiler_get_profile_jsobject_async(float aSinceTime = 0, + mozilla::dom::Promise* = 0) {} + // Get the profile and write it into a file static inline void profiler_save_profile_to_file(char* aFilename) { } diff --git a/tools/profiler/GeckoProfilerFunc.h b/tools/profiler/GeckoProfilerFunc.h index 12eb56ed8e..3f017e6f13 100644 --- a/tools/profiler/GeckoProfilerFunc.h +++ b/tools/profiler/GeckoProfilerFunc.h @@ -13,7 +13,12 @@ namespace mozilla { class TimeStamp; -} + +namespace dom { +class Promise; +} // namespace dom + +} // namespace mozilla class ProfilerBacktrace; class ProfilerMarkerPayload; @@ -38,6 +43,8 @@ void mozilla_sampler_save(); mozilla::UniquePtr mozilla_sampler_get_profile(float aSinceTime); JSObject *mozilla_sampler_get_profile_data(JSContext *aCx, float aSinceTime); +void mozilla_sampler_get_profile_data_async(float aSinceTime, + mozilla::dom::Promise* aPromise); const char** mozilla_sampler_get_features(); diff --git a/tools/profiler/GeckoProfilerImpl.h b/tools/profiler/GeckoProfilerImpl.h index b95a44bce3..6f5b09355c 100644 --- a/tools/profiler/GeckoProfilerImpl.h +++ b/tools/profiler/GeckoProfilerImpl.h @@ -152,6 +152,13 @@ JSObject* profiler_get_profile_jsobject(JSContext* aCx, float aSinceTime = 0) return mozilla_sampler_get_profile_data(aCx, aSinceTime); } +static inline +void profiler_get_profile_jsobject_async(float aSinceTime = 0, + mozilla::dom::Promise* aPromise = 0) +{ + mozilla_sampler_get_profile_data_async(aSinceTime, aPromise); +} + static inline void profiler_save_profile_to_file(const char* aFilename) { diff --git a/tools/profiler/ProfileEntry.cpp b/tools/profiler/ProfileEntry.cpp index acff853d71..7a5666d74a 100644 --- a/tools/profiler/ProfileEntry.cpp +++ b/tools/profiler/ProfileEntry.cpp @@ -14,9 +14,6 @@ #include "jsfriendapi.h" #include "js/TrackedOptimizationInfo.h" -// JSON -#include "ProfileJSONWriter.h" - // Self #include "ProfileEntry.h" diff --git a/tools/profiler/ProfileGatherer.cpp b/tools/profiler/ProfileGatherer.cpp new file mode 100644 index 0000000000..a54d563067 --- /dev/null +++ b/tools/profiler/ProfileGatherer.cpp @@ -0,0 +1,110 @@ +/* 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 "mozilla/ProfileGatherer.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "TableTicker.h" + +using mozilla::dom::AutoJSAPI; +using mozilla::dom::Promise; + +namespace mozilla { + +NS_IMPL_ISUPPORTS0(ProfileGatherer) + +ProfileGatherer::ProfileGatherer(TableTicker* aTicker, + float aSinceTime, + Promise* aPromise) + : mPromise(aPromise) + , mTicker(aTicker) + , mSinceTime(aSinceTime) + , mPendingProfiles(0) +{ +} + +void +ProfileGatherer::GatheredOOPProfile() +{ + MOZ_ASSERT(NS_IsMainThread()); + if (NS_WARN_IF(!mPromise)) { + // If we're not holding on to a Promise, then someone is + // calling us erroneously. + return; + } + + mPendingProfiles--; + + if (mPendingProfiles == 0) { + // We've got all of the async profiles now. Let's + // finish off the profile and resolve the Promise. + Finish(); + } +} + +void +ProfileGatherer::WillGatherOOPProfile() +{ + mPendingProfiles++; +} + +void +ProfileGatherer::Start() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + nsresult rv = os->NotifyObservers(this, "profiler-subprocess-gather", nullptr); + NS_WARN_IF(NS_FAILED(rv)); + } + + if (!mPendingProfiles) { + Finish(); + } +} + +void +ProfileGatherer::Finish() +{ + MOZ_ASSERT(NS_IsMainThread()); + UniquePtr buf = mTicker->ToJSON(mSinceTime); + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(mPromise->GlobalJSObject()))) { + // We're really hosed if we can't get a JS context for some reason. + // We'll tell the TableTicker that we've gathered the profile just + // so that it can drop the reference to this ProfileGatherer and maybe + // the user can try again. + mTicker->ProfileGathered(); + return; + } + + JSContext* cx = jsapi.cx(); + + // Now parse the JSON so that we resolve with a JS Object. + JS::RootedValue val(cx); + { + NS_ConvertUTF8toUTF16 js_string(nsDependentCString(buf.get())); + if (!JS_ParseJSON(cx, static_cast(js_string.get()), + js_string.Length(), &val)) { + if (!jsapi.HasException()) { + mPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR); + } else { + JS::RootedValue exn(cx); + DebugOnly gotException = jsapi.StealException(&exn); + MOZ_ASSERT(gotException); + + jsapi.ClearException(); + mPromise->MaybeReject(cx, exn); + } + } else { + mPromise->MaybeResolve(val); + } + } + + mTicker->ProfileGathered(); +} + +} // namespace mozilla diff --git a/tools/profiler/ProfileGatherer.h b/tools/profiler/ProfileGatherer.h new file mode 100644 index 0000000000..e7bb67a2a0 --- /dev/null +++ b/tools/profiler/ProfileGatherer.h @@ -0,0 +1,38 @@ +/* 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 MOZ_PROFILE_GATHERER_H +#define MOZ_PROFILE_GATHERER_H + +#include "mozilla/dom/Promise.h" + +class TableTicker; + +namespace mozilla { + +class ProfileGatherer final : public nsISupports +{ +public: + NS_DECL_ISUPPORTS + + ProfileGatherer(TableTicker* aTicker, + float aSinceTime, + mozilla::dom::Promise* aPromise); + void WillGatherOOPProfile(); + void GatheredOOPProfile(); + void Start(); + +private: + ~ProfileGatherer() {}; + void Finish(); + + nsRefPtr mPromise; + TableTicker* mTicker; + float mSinceTime; + uint32_t mPendingProfiles; +}; + +} // namespace mozilla + +#endif \ No newline at end of file diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index 16699a9e0d..35a2a01cc7 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -98,6 +98,111 @@ using namespace mozilla; std::string GetSharedLibraryInfoString(); +static bool +hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) { + for(size_t i = 0; i < aFeatureCount; i++) { + if (strcmp(aFeatures[i], aFeature) == 0) + return true; + } + return false; +} + +TableTicker::TableTicker(double aInterval, int aEntrySize, + const char** aFeatures, uint32_t aFeatureCount, + const char** aThreadNameFilters, uint32_t aFilterCount) + : Sampler(aInterval, true, aEntrySize) + , mPrimaryThreadProfile(nullptr) + , mBuffer(new ProfileBuffer(aEntrySize)) + , mSaveRequested(false) +#if defined(XP_WIN) + , mIntelPowerGadget(nullptr) +#endif +{ + mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk"); + + mProfileJS = hasFeature(aFeatures, aFeatureCount, "js"); + mProfileJava = hasFeature(aFeatures, aFeatureCount, "java"); + mProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu"); + mProfilePower = hasFeature(aFeatures, aFeatureCount, "power"); + // Users sometimes ask to filter by a list of threads but forget to request + // profiling non main threads. Let's make it implificit if we have a filter + mProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads") || aFilterCount > 0; + mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf"); + mPrivacyMode = hasFeature(aFeatures, aFeatureCount, "privacy"); + mAddMainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio"); + mProfileMemory = hasFeature(aFeatures, aFeatureCount, "memory"); + mTaskTracer = hasFeature(aFeatures, aFeatureCount, "tasktracer"); + mLayersDump = hasFeature(aFeatures, aFeatureCount, "layersdump"); + mDisplayListDump = hasFeature(aFeatures, aFeatureCount, "displaylistdump"); + mProfileRestyle = hasFeature(aFeatures, aFeatureCount, "restyle"); + +#if defined(XP_WIN) + if (mProfilePower) { + mIntelPowerGadget = new IntelPowerGadget(); + mProfilePower = mIntelPowerGadget->Init(); + } +#endif + + // Deep copy aThreadNameFilters + MOZ_ALWAYS_TRUE(mThreadNameFilters.resize(aFilterCount)); + for (uint32_t i = 0; i < aFilterCount; ++i) { + mThreadNameFilters[i] = aThreadNameFilters[i]; + } + + sStartTime = mozilla::TimeStamp::Now(); + + { + mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); + + // Create ThreadProfile for each registered thread + for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { + ThreadInfo* info = sRegisteredThreads->at(i); + + RegisterThread(info); + } + + SetActiveSampler(this); + } + +#ifdef MOZ_TASK_TRACER + if (mTaskTracer) { + mozilla::tasktracer::StartLogging(); + } +#endif +} + +TableTicker::~TableTicker() +{ + if (IsActive()) + Stop(); + + SetActiveSampler(nullptr); + + // Destroy ThreadProfile for all threads + { + mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); + + for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { + ThreadInfo* info = sRegisteredThreads->at(i); + ThreadProfile* profile = info->Profile(); + if (profile) { + delete profile; + info->SetProfile(nullptr); + } + // We've stopped profiling. We no longer need to retain + // information for an old thread. + if (info->IsPendingDelete()) { + delete info; + sRegisteredThreads->erase(sRegisteredThreads->begin() + i); + i--; + } + } + } +#if defined(XP_WIN) + delete mIntelPowerGadget; +#endif +} + void TableTicker::HandleSaveRequest() { if (!mSaveRequested) @@ -223,6 +328,22 @@ UniquePtr TableTicker::ToJSON(float aSinceTime) return b.WriteFunc()->CopyData(); } +void TableTicker::ToJSObjectAsync(float aSinceTime, + Promise* aPromise) +{ + if (NS_WARN_IF(mGatherer)) { + return; + } + + mGatherer = new ProfileGatherer(this, aSinceTime, aPromise); + mGatherer->Start(); +} + +void TableTicker::ProfileGathered() +{ + mGatherer = nullptr; +} + struct SubprocessClosure { explicit SubprocessClosure(SpliceableJSONWriter *aWriter) : mWriter(aWriter) diff --git a/tools/profiler/TableTicker.h b/tools/profiler/TableTicker.h index 2a5c6e5c6c..a52d1fa129 100644 --- a/tools/profiler/TableTicker.h +++ b/tools/profiler/TableTicker.h @@ -13,14 +13,9 @@ #include "GeckoTaskTracer.h" #endif -static bool -hasFeature(const char** aFeatures, uint32_t aFeatureCount, const char* aFeature) { - for(size_t i = 0; i < aFeatureCount; i++) { - if (strcmp(aFeatures[i], aFeature) == 0) - return true; - } - return false; -} +namespace mozilla { +class ProfileGatherer; +} // namespace mozilla static bool threadSelected(ThreadInfo* aInfo, char** aThreadNameFilters, uint32_t aFeatureCount) { @@ -46,99 +41,8 @@ class TableTicker: public Sampler { public: TableTicker(double aInterval, int aEntrySize, const char** aFeatures, uint32_t aFeatureCount, - const char** aThreadNameFilters, uint32_t aFilterCount) - : Sampler(aInterval, true, aEntrySize) - , mPrimaryThreadProfile(nullptr) - , mBuffer(new ProfileBuffer(aEntrySize)) - , mSaveRequested(false) - , mFilterCount(aFilterCount) -#if defined(XP_WIN) - , mIntelPowerGadget(nullptr) -#endif - { - mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk"); - - mProfileJS = hasFeature(aFeatures, aFeatureCount, "js"); - mProfileJava = hasFeature(aFeatures, aFeatureCount, "java"); - mProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu"); - mProfilePower = hasFeature(aFeatures, aFeatureCount, "power"); - // Users sometimes ask to filter by a list of threads but forget to request - // profiling non main threads. Let's make it implificit if we have a filter - mProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads") || aFilterCount > 0; - mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf"); - mPrivacyMode = hasFeature(aFeatures, aFeatureCount, "privacy"); - mAddMainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio"); - mProfileMemory = hasFeature(aFeatures, aFeatureCount, "memory"); - mTaskTracer = hasFeature(aFeatures, aFeatureCount, "tasktracer"); - mLayersDump = hasFeature(aFeatures, aFeatureCount, "layersdump"); - mDisplayListDump = hasFeature(aFeatures, aFeatureCount, "displaylistdump"); - mProfileRestyle = hasFeature(aFeatures, aFeatureCount, "restyle"); - -#if defined(XP_WIN) - if (mProfilePower) { - mIntelPowerGadget = new IntelPowerGadget(); - mProfilePower = mIntelPowerGadget->Init(); - } -#endif - - // Deep copy aThreadNameFilters - mThreadNameFilters = new char*[aFilterCount]; - for (uint32_t i = 0; i < aFilterCount; ++i) { - mThreadNameFilters[i] = strdup(aThreadNameFilters[i]); - } - - sStartTime = mozilla::TimeStamp::Now(); - - { - mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); - - // Create ThreadProfile for each registered thread - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - - RegisterThread(info); - } - - SetActiveSampler(this); - } - -#ifdef MOZ_TASK_TRACER - if (mTaskTracer) { - mozilla::tasktracer::StartLogging(); - } -#endif - } - - ~TableTicker() { - if (IsActive()) - Stop(); - - SetActiveSampler(nullptr); - - // Destroy ThreadProfile for all threads - { - mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex); - - for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) { - ThreadInfo* info = sRegisteredThreads->at(i); - ThreadProfile* profile = info->Profile(); - if (profile) { - delete profile; - info->SetProfile(nullptr); - } - // We've stopped profiling. We no longer need to retain - // information for an old thread. - if (info->IsPendingDelete()) { - delete info; - sRegisteredThreads->erase(sRegisteredThreads->begin() + i); - i--; - } - } - } -#if defined(XP_WIN) - delete mIntelPowerGadget; -#endif - } + const char** aThreadNameFilters, uint32_t aFilterCount); + ~TableTicker(); void RegisterThread(ThreadInfo* aInfo) { if (!aInfo->IsMainThread() && !mProfileThreads) { @@ -193,6 +97,7 @@ class TableTicker: public Sampler { void ToStreamAsJSON(std::ostream& stream, float aSinceTime = 0); virtual JSObject *ToJSObject(JSContext *aCx, float aSinceTime = 0); mozilla::UniquePtr ToJSON(float aSinceTime = 0); + virtual void ToJSObjectAsync(float aSinceTime = 0, mozilla::dom::Promise* aPromise = 0); void StreamMetaJSCustomObject(SpliceableJSONWriter& aWriter); void StreamTaskTracer(SpliceableJSONWriter& aWriter); void FlushOnJSShutdown(JSRuntime* aRuntime); @@ -211,6 +116,8 @@ class TableTicker: public Sampler { void GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration); + void ProfileGathered(); + protected: // Called within a signal. This function must be reentrant virtual void InplaceTick(TickSample* sample); @@ -246,6 +153,9 @@ protected: #if defined(XP_WIN) IntelPowerGadget* mIntelPowerGadget; #endif + +private: + nsRefPtr mGatherer; }; #endif diff --git a/tools/profiler/moz.build b/tools/profiler/moz.build index f5dc634c3e..fb8fab7d18 100644 --- a/tools/profiler/moz.build +++ b/tools/profiler/moz.build @@ -20,6 +20,9 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']: 'PseudoStack.h', 'shared-libraries.h', ] + EXPORTS.mozilla += [ + 'ProfileGatherer.h', + ] EXTRA_JS_MODULES += [ 'Profiler.jsm', ] @@ -29,6 +32,7 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']: 'nsProfilerStartParams.cpp', 'platform.cpp', 'ProfileEntry.cpp', + 'ProfileGatherer.cpp', 'ProfileJSONWriter.cpp', 'ProfilerBacktrace.cpp', 'ProfilerIOInterposeObserver.cpp', diff --git a/tools/profiler/nsIProfiler.idl b/tools/profiler/nsIProfiler.idl index 676b38d67f..46a5c86345 100644 --- a/tools/profiler/nsIProfiler.idl +++ b/tools/profiler/nsIProfiler.idl @@ -12,7 +12,7 @@ class nsCString; [ref] native StringArrayRef(const nsTArray); -[scriptable, uuid(9f3e7c97-abcf-425c-83fd-34d354eb95e8)] +[scriptable, uuid(0f474ec5-b95c-45d9-a7c8-156da0e3fa25)] interface nsIProfiler : nsISupports { void StartProfiler(in uint32_t aEntries, in double aInterval, @@ -38,6 +38,9 @@ interface nsIProfiler : nsISupports [implicit_jscontext] jsval getProfileData([optional] in float aSinceTime); + [implicit_jscontext] + nsISupports getProfileDataAsync([optional] in float aSinceTime); + boolean IsActive(); void GetFeatures(out uint32_t aCount, [retval, array, size_is(aCount)] out string aFeatures); diff --git a/tools/profiler/nsProfiler.cpp b/tools/profiler/nsProfiler.cpp index c186e039f1..1c3f79fdb2 100644 --- a/tools/profiler/nsProfiler.cpp +++ b/tools/profiler/nsProfiler.cpp @@ -17,7 +17,11 @@ #include "nsIInterfaceRequestorUtils.h" #include "shared-libraries.h" #include "js/Value.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/Promise.h" +using mozilla::ErrorResult; +using mozilla::dom::Promise; using std::string; NS_IMPL_ISUPPORTS(nsProfiler, nsIProfiler) @@ -212,6 +216,34 @@ nsProfiler::GetProfileData(float aSinceTime, JSContext* aCx, return NS_OK; } +NS_IMETHODIMP +nsProfiler::GetProfileDataAsync(float aSinceTime, JSContext* aCx, + nsISupports** aPromise) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(!aCx)) { + return NS_ERROR_FAILURE; + } + + nsIGlobalObject* go = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); + + if (NS_WARN_IF(!go)) { + return NS_ERROR_FAILURE; + } + + ErrorResult result; + nsRefPtr promise = Promise::Create(go, result); + if (NS_WARN_IF(result.Failed())) { + return result.StealNSResult(); + } + + profiler_get_profile_jsobject_async(aSinceTime, promise); + + promise.forget(aPromise); + return NS_OK; +} + NS_IMETHODIMP nsProfiler::GetElapsedTime(float* aElapsedTime) { diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index aab5a0907c..dfae1163ca 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -580,6 +580,17 @@ JSObject *mozilla_sampler_get_profile_data(JSContext *aCx, float aSinceTime) return t->ToJSObject(aCx, aSinceTime); } +void mozilla_sampler_get_profile_data_async(float aSinceTime, + mozilla::dom::Promise* aPromise) +{ + TableTicker *t = tlsTicker.get(); + if (NS_WARN_IF(!t)) { + return; + } + + t->ToJSObjectAsync(aSinceTime, aPromise); +} + void mozilla_sampler_save_profile_to_file(const char* aFilename) { TableTicker *t = tlsTicker.get(); diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index d308211aa6..1bb9010222 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -1945,7 +1945,8 @@ nsChildView::PrepareWindowEffects() CGFloat cornerRadius = [(ChildView*)mView cornerRadius]; mDevPixelCornerRadius = cornerRadius * BackingScaleFactor(); mIsCoveringTitlebar = [(ChildView*)mView isCoveringTitlebar]; - mIsFullscreen = ([[mView window] styleMask] & NSFullScreenWindowMask) != 0; + NSInteger styleMask = [[mView window] styleMask]; + mIsFullscreen = (styleMask & NSFullScreenWindowMask) || !(styleMask & NSTitledWindowMask); if (mIsCoveringTitlebar) { mTitlebarRect = RectContainingTitlebarControls(); UpdateTitlebarCGContext(); diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h index dfd34a638c..ea65ac9458 100644 --- a/widget/cocoa/nsCocoaWindow.h +++ b/widget/cocoa/nsCocoaWindow.h @@ -284,7 +284,9 @@ public: nsIWidget *aWidget, bool aActivate) override; NS_IMETHOD SetSizeMode(int32_t aMode) override; NS_IMETHOD HideWindowChrome(bool aShouldHide) override; - void EnteredFullScreen(bool aFullScreen); + virtual void PrepareForDOMFullscreenTransition() override; + void EnteredFullScreen(bool aFullScreen, bool aNativeMode = true); + inline bool ShouldToggleNativeFullscreen(bool aFullScreen); NS_IMETHOD MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override; NS_IMETHOD Resize(double aWidth, double aHeight, bool aRepaint) override; NS_IMETHOD Resize(double aX, double aY, double aWidth, double aHeight, bool aRepaint) override; @@ -408,12 +410,21 @@ protected: bool mWindowMadeHere; // true if we created the window, false for embedding bool mSheetNeedsShow; // if this is a sheet, are we waiting to be shown? // this is used for sibling sheet contention only - bool mFullScreen; + bool mInFullScreenMode; bool mInFullScreenTransition; // true from the request to enter/exit fullscreen // (MakeFullScreen() call) to EnteredFullScreen() + // We are in transition to/from DOM Fullscreen. + // XXX The transition is not implemented yet. This is currently only + // used to distinguish between DOM fullscreen and fullscreen mode. + bool mInDOMFullscreenTransition; bool mModal; - bool mUsesNativeFullScreen; // only true on Lion if SetShowsFullScreenButton(true); + // Only true on 10.7+ if SetShowsFullScreenButton(true) is called. + bool mSupportsNativeFullScreen; + // Whether we are currently using Lion native fullscreen. It could be + // false either because we are not on Lion, or we are in the DOM + // fullscreen where we do not use the native fullscreen. + bool mInNativeFullScreenMode; bool mIsAnimationSuppressed; diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm index 836920e462..4592e331d7 100644 --- a/widget/cocoa/nsCocoaWindow.mm +++ b/widget/cocoa/nsCocoaWindow.mm @@ -106,10 +106,12 @@ nsCocoaWindow::nsCocoaWindow() , mAnimationType(nsIWidget::eGenericWindowAnimation) , mWindowMadeHere(false) , mSheetNeedsShow(false) -, mFullScreen(false) +, mInFullScreenMode(false) , mInFullScreenTransition(false) +, mInDOMFullscreenTransition(false) , mModal(false) -, mUsesNativeFullScreen(false) +, mSupportsNativeFullScreen(false) +, mInNativeFullScreenMode(false) , mIsAnimationSuppressed(false) , mInReportMoveEvent(false) , mInResize(false) @@ -195,8 +197,7 @@ static NSScreen *FindTargetScreenForRect(const nsIntRect& aRect) // fits the rect to the screen that contains the largest area of it, // or to aScreen if a screen is passed in // NB: this operates with aRect in global display pixels -static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *aScreen, - bool aUsesNativeFullScreen) +static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *aScreen) { if (!aScreen) { aScreen = FindTargetScreenForRect(aRect); @@ -226,16 +227,6 @@ static void FitRectToVisibleAreaForScreen(nsIntRect &aRect, NSScreen *aScreen, if (aRect.y < screenBounds.y || aRect.y > (screenBounds.y + screenBounds.height)) { aRect.y = screenBounds.y; } - - // If aRect is filling the screen and the window supports native (Lion-style) - // fullscreen mode, reduce aRect's height and shift it down by 22 pixels - // (nominally the height of the menu bar or of a window's title bar). For - // some reason this works around bug 740923. Yes, it's a bodacious hack. - // But until we know more it will have to do. - if (aUsesNativeFullScreen && aRect.y == 0 && aRect.height == screenBounds.height) { - aRect.y = 22; - aRect.height -= 22; - } } // Some applications use native popup windows @@ -262,7 +253,7 @@ nsresult nsCocoaWindow::Create(nsIWidget *aParent, nsAutoreleasePool localPool; nsIntRect newBounds = aRect; - FitRectToVisibleAreaForScreen(newBounds, nullptr, mUsesNativeFullScreen); + FitRectToVisibleAreaForScreen(newBounds, nullptr); // Set defaults which can be overriden from aInitData in BaseCreate mWindowType = eWindowType_toplevel; @@ -524,7 +515,7 @@ NS_IMETHODIMP nsCocoaWindow::Destroy() } nsBaseWidget::OnDestroy(); - if (mFullScreen) { + if (mInFullScreenMode) { // On Lion we don't have to mess with the OS chrome when in Full Screen // mode. But we do have to destroy the native window here (and not wait // for that to happen in our destructor). We don't switch away from the @@ -532,7 +523,7 @@ NS_IMETHODIMP nsCocoaWindow::Destroy() // might not happen for several seconds (because at least one object // holding a reference to ourselves is usually waiting to be garbage- // collected). See bug 757618. - if (mUsesNativeFullScreen) { + if (mInNativeFullScreenMode) { DestroyNativeWindow(); } else if (mWindow) { nsCocoaUtils::HideOSChromeOnScreen(false, [mWindow screen]); @@ -1211,7 +1202,7 @@ NS_METHOD nsCocoaWindow::SetSizeMode(int32_t aMode) [mWindow zoom:nil]; } else if (aMode == nsSizeMode_Fullscreen) { - if (!mFullScreen) + if (!mInFullScreenMode) MakeFullScreen(true); } @@ -1270,7 +1261,10 @@ NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide) // Show the new window. if (isVisible) { + bool wasAnimationSuppressed = mIsAnimationSuppressed; + mIsAnimationSuppressed = true; rv = Show(true); + mIsAnimationSuppressed = wasAnimationSuppressed; NS_ENSURE_SUCCESS(rv, rv); } @@ -1279,16 +1273,44 @@ NS_IMETHODIMP nsCocoaWindow::HideWindowChrome(bool aShouldHide) NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } -void nsCocoaWindow::EnteredFullScreen(bool aFullScreen) +void nsCocoaWindow::PrepareForDOMFullscreenTransition() +{ + mInDOMFullscreenTransition = true; +} + +void nsCocoaWindow::EnteredFullScreen(bool aFullScreen, bool aNativeMode) { mInFullScreenTransition = false; - mFullScreen = aFullScreen; + mInFullScreenMode = aFullScreen; + if (aNativeMode || mInNativeFullScreenMode) { + mInNativeFullScreenMode = aFullScreen; + } DispatchSizeModeEvent(); if (mWidgetListener) { mWidgetListener->FullscreenChanged(aFullScreen); } } +inline bool nsCocoaWindow::ShouldToggleNativeFullscreen(bool aFullScreen) +{ + if (!mSupportsNativeFullScreen) { + // If we cannot use native fullscreen, don't touch it. + return false; + } + if (mInNativeFullScreenMode) { + // If we are using native fullscreen, go ahead to exit it. + return true; + } + if (mInDOMFullscreenTransition) { + // We shouldn't use native fullscreen for DOM fullscreen. + return false; + } + // If we are using native fullscreen, we should have returned earlier, + // which means if we reach here for exiting fullscreen, we must be + // exiting from DOM fullscreen, not native fullscreen. + return aFullScreen; +} + NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; @@ -1300,25 +1322,27 @@ NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScre // We will call into MakeFullScreen redundantly when entering/exiting // fullscreen mode via OS X controls. When that happens we should just handle // it gracefully - no need to ASSERT. - if (mFullScreen == aFullScreen) { + if (mInFullScreenMode == aFullScreen) { return NS_OK; } - // If we're using native fullscreen mode and our native window is invisible, - // our attempt to go into fullscreen mode will fail with an assertion in - // system code, without [WindowDelegate windowDidFailToEnterFullScreen:] - // ever getting called. To pre-empt this we bail here. See bug 752294. - if (mUsesNativeFullScreen && aFullScreen && ![mWindow isVisible]) { - EnteredFullScreen(false); - return NS_OK; - } mInFullScreenTransition = true; - if (mUsesNativeFullScreen) { + if (ShouldToggleNativeFullscreen(aFullScreen)) { + // If we're using native fullscreen mode and our native window is invisible, + // our attempt to go into fullscreen mode will fail with an assertion in + // system code, without [WindowDelegate windowDidFailToEnterFullScreen:] + // ever getting called. To pre-empt this we bail here. See bug 752294. + if (aFullScreen && ![mWindow isVisible]) { + EnteredFullScreen(false); + return NS_OK; + } + MOZ_ASSERT(mInNativeFullScreenMode != aFullScreen, + "We shouldn't have been in native fullscreen."); // Calling toggleFullScreen will result in windowDid(FailTo)?(Enter|Exit)FullScreen // to be called from the OS. We will call EnteredFullScreen from those methods, - // where mFullScreen will be set and a sizemode event will be dispatched. + // where mInFullScreenMode will be set and a sizemode event will be dispatched. [mWindow toggleFullScreen:nil]; } else { NSDisableScreenUpdates(); @@ -1330,7 +1354,12 @@ NS_METHOD nsCocoaWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScre NSEnableScreenUpdates(); NS_ENSURE_SUCCESS(rv, rv); - EnteredFullScreen(aFullScreen); + EnteredFullScreen(aFullScreen, /* aNativeMode */ false); + } + + if (mInDOMFullscreenTransition) { + // Clear the flag about DOM fullscreen. + mInDOMFullscreenTransition = false; } return NS_OK; @@ -1365,10 +1394,8 @@ nsresult nsCocoaWindow::DoResize(double aX, double aY, NSToIntRound(height / scale)); // constrain to the screen that contains the largest area of the new rect - FitRectToVisibleAreaForScreen(newBounds, - aConstrainToCurrentScreen ? - [mWindow screen] : nullptr, - mUsesNativeFullScreen); + FitRectToVisibleAreaForScreen(newBounds, aConstrainToCurrentScreen ? + [mWindow screen] : nullptr); // convert requested bounds into Cocoa coordinate system NSRect newFrame = nsCocoaUtils::GeckoRectToCocoaRect(newBounds); @@ -1704,8 +1731,8 @@ nsCocoaWindow::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus) return NS_OK; } -// aFullScreen should be the window's mFullScreen. We don't have access to that -// from here, so we need to pass it in. mFullScreen should be the canonical +// aFullScreen should be the window's mInFullScreenMode. We don't have access to that +// from here, so we need to pass it in. mInFullScreenMode should be the canonical // indicator that a window is currently full screen and it makes sense to keep // all sizemode logic here. static nsSizeMode @@ -1750,7 +1777,7 @@ nsCocoaWindow::DispatchSizeModeEvent() return; } - nsSizeMode newMode = GetWindowSizeMode(mWindow, mFullScreen); + nsSizeMode newMode = GetWindowSizeMode(mWindow, mInFullScreenMode); // Don't dispatch a sizemode event if: // 1. the window is transitioning to fullscreen @@ -1966,16 +1993,16 @@ void nsCocoaWindow::SetShowsFullScreenButton(bool aShow) NS_OBJC_BEGIN_TRY_ABORT_BLOCK; if (!mWindow || ![mWindow respondsToSelector:@selector(toggleFullScreen:)] || - mUsesNativeFullScreen == aShow) { + mSupportsNativeFullScreen == aShow) { return; } // If the window is currently in fullscreen mode, then we're going to // transition out first, then set the collection behavior & toggle - // mUsesNativeFullScreen, then transtion back into fullscreen mode. This + // mSupportsNativeFullScreen, then transtion back into fullscreen mode. This // prevents us from getting into a conflicting state with MakeFullScreen - // where mUsesNativeFullScreen would lead us down the wrong path. - bool wasFullScreen = mFullScreen; + // where mSupportsNativeFullScreen would lead us down the wrong path. + bool wasFullScreen = mInFullScreenMode; if (wasFullScreen) { MakeFullScreen(false); @@ -1988,7 +2015,7 @@ void nsCocoaWindow::SetShowsFullScreenButton(bool aShow) newBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; } [mWindow setCollectionBehavior:newBehavior]; - mUsesNativeFullScreen = aShow; + mSupportsNativeFullScreen = aShow; if (wasFullScreen) { MakeFullScreen(true); @@ -2785,6 +2812,7 @@ static const NSString* kStateDrawsContentsIntoWindowFrameKey = @"drawsContentsIn static const NSString* kStateActiveTitlebarColorKey = @"activeTitlebarColor"; static const NSString* kStateInactiveTitlebarColorKey = @"inactiveTitlebarColor"; static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; +static const NSString* kStateCollectionBehavior = @"collectionBehavior"; - (void)importState:(NSDictionary*)aState { @@ -2793,6 +2821,7 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; [self setTitlebarColor:[aState objectForKey:kStateActiveTitlebarColorKey] forActiveWindow:YES]; [self setTitlebarColor:[aState objectForKey:kStateInactiveTitlebarColorKey] forActiveWindow:NO]; [self setShowsToolbarButton:[[aState objectForKey:kStateShowsToolbarButton] boolValue]]; + [self setCollectionBehavior:[[aState objectForKey:kStateCollectionBehavior] unsignedIntValue]]; } - (NSMutableDictionary*)exportState @@ -2811,6 +2840,8 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; } [state setObject:[NSNumber numberWithBool:[self showsToolbarButton]] forKey:kStateShowsToolbarButton]; + [state setObject:[NSNumber numberWithUnsignedInt: [self collectionBehavior]] + forKey:kStateCollectionBehavior]; return state; } @@ -3297,10 +3328,12 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; CGFloat topMargin = mUnifiedToolbarHeight - [self titlebarHeight]; [self setContentBorderThickness:topMargin forEdge:NSMaxYEdge]; - // Redraw the title bar. If we're inside painting, we'll do it right now, - // otherwise we'll just invalidate it. - BOOL needSyncRedraw = ([NSView focusView] != nil); - [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw]; + if (![self drawsContentsIntoWindowFrame]) { + // Redraw the title bar. If we're inside painting, we'll do it right now, + // otherwise we'll just invalidate it. + BOOL needSyncRedraw = ([NSView focusView] != nil); + [self setTitlebarNeedsDisplayInRect:[self titlebarRect] sync:needSyncRedraw]; + } } // Extending the content area into the title bar works by resizing the @@ -3346,7 +3379,9 @@ static const NSString* kStateShowsToolbarButton = @"showsToolbarButton"; - (NSPoint)windowButtonsPositionWithDefaultPosition:(NSPoint)aDefaultPosition { - if ([self drawsContentsIntoWindowFrame] && !([self styleMask] & NSFullScreenWindowMask)) { + NSInteger styleMask = [self styleMask]; + if ([self drawsContentsIntoWindowFrame] && + !(styleMask & NSFullScreenWindowMask) && (styleMask & NSTitledWindowMask)) { if (NSIsEmptyRect(mWindowButtonsRect)) { // Empty rect. Let's hide the buttons. // Position is in non-flipped window coordinates. Using frame's height diff --git a/widget/nsBaseWidget.h b/widget/nsBaseWidget.h index 5ef7006830..4ebb8a28aa 100644 --- a/widget/nsBaseWidget.h +++ b/widget/nsBaseWidget.h @@ -143,6 +143,7 @@ public: virtual void SetShowsFullScreenButton(bool aShow) override {} virtual void SetWindowAnimationType(WindowAnimationType aType) override {} NS_IMETHOD HideWindowChrome(bool aShouldHide) override; + virtual void PrepareForDOMFullscreenTransition() override {} NS_IMETHOD MakeFullScreen(bool aFullScreen, nsIScreen* aScreen = nullptr) override; virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr, LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE, diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h index 214d231a28..b50e6006aa 100644 --- a/widget/nsIWidget.h +++ b/widget/nsIWidget.h @@ -115,8 +115,8 @@ typedef void* nsNativeWidget; #define NS_NATIVE_PLUGIN_ID 105 #define NS_IWIDGET_IID \ -{ 0x316E4600, 0x15DB, 0x47AE, \ - { 0xBF, 0xE4, 0x5B, 0xCD, 0xFF, 0x80, 0x80, 0x83 } }; +{ 0x53376F57, 0xF081, 0x4949, \ + { 0xB5, 0x5E, 0x87, 0xEF, 0x6A, 0xE9, 0xE3, 0x5A } }; /* * Window shadow styles @@ -1542,6 +1542,19 @@ class nsIWidget : public nsISupports { */ NS_IMETHOD HideWindowChrome(bool aShouldHide) = 0; + /** + * Ask the widget to start the transition for entering or exiting + * DOM Fullscreen. + * + * XXX This method is currently not actually implemented by any + * widget. The only function of this method is to notify cocoa + * window that it should not use the native fullscreen mode. This + * method is reserved for bug 1160014 where a transition will be + * added for DOM fullscreen. Hence, this function is likely to + * be further changed then. + */ + virtual void PrepareForDOMFullscreenTransition() = 0; + /** * Put the toplevel window into or out of fullscreen mode. * If aTargetScreen is given, attempt to go fullscreen on that screen,