tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c4f2c793384af2f0a03ab8eef49040a67463bbe9
parent 6abddcb0a5076c3b888686ede6f4cf7d082460d3
Author: Sebastian Hengst <aryx.github@gmx-topmail.de>
Date:   Wed, 12 Nov 2025 00:48:45 +0100

Revert last merge (revisions f94783cb0489f47b723bdd744b1d2e006f5b9ecc to 6abddcb0a5076c3b888686ede6f4cf7d082460d3) to land them properly again

Reapply "Bug 1848958 - [wdspec] Update tests for dispatching mouse events via the parent process. r=jdescottes" for causing failures at pointer_dblclick.py.

This reverts commit 6abddcb0a5076c3b888686ede6f4cf7d082460d3.

Revert "Bug 1995691 - [wdspec] Add checks for overriding "Accept-Language" header with "emulation.setLocaleOverride". r=whimboo"

This reverts commit b45d8304247695bcef6ce4b9014650a75eaadc51.

Revert "Bug 1995691 - Override "Accept-Language" header when browsingContext.languageOverride is set. r=necko-reviewers,jesup"

This reverts commit b888cdb879bd83e8cf76635b6fead87851165697.

Revert "Bug 1999326 - Fix ancestor-search loop in GetFrameLineNum() to properly handle first-line frames if present. r=layout-reviewers,emilio"

This reverts commit 9c5b5b8d4e7726aa17811dfd3a25319d9dda28f6.

Revert "Bug 1999509 - Ensure PostTimerEvent will not release anything important while holding the monitor's lock. r=xpcom-reviewers,nika"

This reverts commit 8ba9c7901c57cf3b9340fb088c2e0819f4713d18.

Revert "Bug 1998951 - Fix state variable checks. r=android-reviewers,pollymce DONTBUILD"

This reverts commit b4d644adc46c3421e77c98a66ae7fb551da178e6.

Reapply "Bug 1999077 - Update opus to 7f097e0fbf31f0e1b679f863646b3746646ab2a0 r=kinetik" for causing Android bustages.

This reverts commit 23ba2afcaf93e1db2f36dd59082b570e7bba85dc.

Revert "Bug 1995935 - Remove ClockResolutionNs on MacOS. r=glandium"

This reverts commit 4187128adcb1ab6922cefad0da104d1ecff20675.

Revert "Bug 1998451: Improve performance of codegen tests. r=bvisness"

This reverts commit a6912ad9ed9bcebb345e77da124f54080d019e01.

Revert "Bug 1998452: Avoid REX prefix for And with unsigned 32-bit immediate. r=spidermonkey-reviewers,iain"

This reverts commit bdb1c980261cfddaae0e69035910538fa191d4cb.

Revert "Bug 1998161 - Part 4: Merge shifts for division by constants for x64. r=iain"

This reverts commit 7bf2612d5fc80eb3735ef2288e36a1d6da235732.

Revert "Bug 1998161 - Part 3: Remove register constraints for div/mod with constants on x64. r=spidermonkey-reviewers,iain"

This reverts commit cfd2a9855605d99afee434be297844f6ae095756.

Revert "Bug 1998161 - Part 2: Use register allocator to place lhs operand for div/mod into eax. r=spidermonkey-reviewers,iain"

This reverts commit d907853cc7c8b338b65102a9e4ced6c554b208ac.

Revert "Bug 1998161 - Part 1: Port arm64 changes to x86/x64. r=spidermonkey-reviewers,iain"

This reverts commit 7f9923c0f7d312e685493a26af4a2e3efd6cafce.

Revert "Bug 1999266 - Add test for speculative connect triggered by docshell mouseover r=necko-reviewers,kershaw"

This reverts commit 723332462872a2cdd2b01731dcc27187487e9ccb.

Revert "Bug 1999266 - Extract PREDICT_LINK from predictor r=necko-reviewers,jesup"

This reverts commit 9a87b84992f2dc8391f46992561ea6c1503e965f.

Revert "Bug 1999077 - Update opus to 7f097e0fbf31f0e1b679f863646b3746646ab2a0 r=kinetik"

This reverts commit 455a240e8dc7105da71feeab7e52ba91103f8f73.

Revert "Bug 1995254 - Always rely on QueryPerformanceCounter for Windows TimeStamp. r=glandium,profiler-reviewers,mstange"

This reverts commit f805d38885a74da57aca066a48c0b012e0015445.

Revert "Bug 1994942 - Fix typo when window.print()-ing a page with an iframe."

This reverts commit d034cec7a47fc8676422d69de34055267a45faf2.

Revert "Revert (Bug 1997393, Bug 1971438, Bug 1997185) for causing bc failures @ browser_sanitizeDialog_v2.js"

This reverts commit 0d3006309def9cbdf3a347da19b0b4eefffa2b52.

Reapply "Bug 1818649 - Make municipality field mandatory for JP addresses r=NeilDeakin,credential-management-reviewers" for causing junit failures @ AutocompleteTest

This reverts commit c15bb99271483bff4d91ebea4098fa0d7902a0ff.

Revert "Bug 1997393 - Add storybook stories and tests for disabled and hidden moz-option properties. r=hjones"

This reverts commit 5d51677ea1d70de0d1ea7e10472ac6f67dc58444.

Revert "Bug 1971438 - Support setting 'disabled' and 'hidden' properties via 'option' config. r=hjones"

This reverts commit 28f04acb49945d154e4df05880d809963a6ef630.

Revert "Bug 1997393 - Support hidden options in moz-select. r=hjones"

This reverts commit 7a1e6c562a426420b936818487062546aafb9965.

Revert "Bug 1997185 - Support disabled options in moz-select. r=hjones"

This reverts commit cb5b76689b42e04b679ded6d7d38dd023c0efc5c.

Revert "Bug 1971438 - Update tests for history preferences section. r=mconley"

This reverts commit 815dcbccd91adeedf31cf2e4fbe583e31a5a7f21.

Revert "Bug 1971438 - Convert History to config-based prefs. r=fluent-reviewers,mconley,bolsson"

This reverts commit d11399d4690058734d93a61d60666ae575d547c4.

Revert "Bug 1998949 [Wayland] Don't fire bounds recalculation from configure event on Wayland as we relly on mContainer GdkWindow size r=emilio"

This reverts commit 0210cc7dc1e959e11efddbe66de345d451486edc.

Revert "Bug 1998949 [Wayland] Propagate correct window coordinates constraint to layout r=emilio"

This reverts commit c92b980a448c8501bc8d28d2ed5c31fd15bc4bd0.

Revert "Bug 1636428 - Deprecate accent property from the operator dictionary r=emilio,fredw,layout-reviewers,flod"

This reverts commit fa485df7c4f75ed7b1dfdf8df43b421d4b49b186.

Revert "Bug 1848958 - [wdspec] Update tests for dispatching mouse events via the parent process. r=jdescottes"

This reverts commit 5f95d4ad4f0674f9055cfd528237271f054c92d0.

Revert "Bug 1848958 - [remote] Use async (widget) mouse events for async-event-dispatching jobs in CI. r=jgraham,jdescottes"

This reverts commit 4340c11d32351cf1652248563055e53a0255cab2.

Revert "Bug 1848958 - [remote] Add support for mouse widget events to Marionette and WebDriver BiDi. r=jdescottes"

This reverts commit bca3fa41d7544afc85473e98b31aeaf0c42d1af2.

Revert "Bug 1848958 - [remote] Ignore error from finalize action if browsing context no longer exists. r=jdescottes"

This reverts commit 303fbd932715686917bd365118632691b3351312.

Revert "Bug 1988590 - QM: Add optional cutoff access time to GetOriginInfosWithZeroUsage; r=asuth,dom-storage-reviewers,jari"

This reverts commit 4189da74314117c8447a6d4d8af59f3c1ee3a5f1.

Revert "Bug 1999192 - Remove obsolete reference to --enable-dtrace r=ahochheiden"

This reverts commit 64b3c904ad7ad1185723f1080a975d97f27283ae.

Revert "Bug 1998420 - Add a distribution.ini to the RPM package. r=releng-reviewers,jcristau"

This reverts commit a8567fd2be7c424b2dc5efe1f0c9938afb3af385.

Revert "Bug 1999289 - Remove `dom.ipc.forkserver.enable=true` from the debian distribution.ini. r=releng-reviewers,jld,bhearsum DONTBUILD"

This reverts commit 08bc746051fd58bc0670023904de907b122a9b3c.

Revert "Bug 1818649 - Make municipality field mandatory for JP addresses r=NeilDeakin,credential-management-reviewers"

This reverts commit b36af95b02289e62a7063fce6afa773bdfb42638.

Revert "Bug 1935981 - Improve "given-name" heuristic for Japan r=NeilDeakin,credential-management-reviewers"

This reverts commit be1d04c0ade2c012421930c633502ae468d57d44.

Revert "Bug 1997149 - Close KsecDD device handle in windows content process. r=handyman"

This reverts commit ff72b5f9bd38967a65819863adb7488c14916598.

Reapply "Bug 1995254 - Always rely on QueryPerformanceCounter for Windows TimeStamp. r=glandium,profiler-reviewers,mstange" for causing bustages at TimeStamp_windows.cpp.

This reverts commit ee3f35799ff58929360b98c252c454c6b1a2a798.

Revert "Bug 1997870 - Part 4: Perform the encoding and the compression off main thread. r=bthrall"

This reverts commit 53e770b01f1a855b61cc0a681aed10ee7afc8ae1.

Revert "Bug 1997870 - Part 3: Split EncodeBytecodeAndSave into two steps. r=bthrall"

This reverts commit 9d38131e7dde41cd6d4d432ed45a2bd763485e70.

Revert "Bug 1997870 - Part 2: Make BytecodeMimeTypeFor methods take const. r=bthrall"

This reverts commit 122bc2e9ef7375f608e003ad841578f7dfe30654.

Revert "Bug 1997870 - Part 1: Remove obsolete comment. r=bthrall"

This reverts commit 37508abe673bbb8c976c6cc77d43d7aae8a9dc14.

Revert "Bug 1931328 - Account for playback rate when setting frame timestamps in DecodedStream. r=padenot"

This reverts commit 5df6a99098033eac5a05134437d4393200c8652b.

Revert "Bug 1931328 - Remove an unused member in TestAudioDecoderInputTrack.cpp's mocked graph. r=padenot"

This reverts commit 953d9a7d100b92c2094dc296962d3f3c5e02f40e.

Revert "Bug 1931328 - Test the interpolated GetPosition in DecodedStream. r=padenot"

This reverts commit a8c2ffa3f7c13ce6533bc06c5bbe7ecb0bc7f90f.

Revert "Bug 1931328 - Allow for deterministic output time testing of DecodedStream. r=padenot"

This reverts commit 1a970ccb483397c434bd9f2a3d566d72b2444c82.

Revert "Bug 1931328 - Set up a test fixture and a trivial first gtest for DecodedStream. r=padenot"

This reverts commit 7ae22cf7c12da613f218c921f8956a22931ec0ca.

Revert "Bug 1931328 - Interpolate DecodedStream position based on current system time. r=padenot,webidl,smaug"

This reverts commit 6bc5f33ade36f71c360721f04c77744d662ace1b.

Revert "Bug 1931328 - Improve and simplify tests of AudioDecoderInputTrack::OnOutput. r=padenot"

This reverts commit 6f366e2d4f99e96991661219882560c14479ac2f.

Revert "Bug 1931328 - Add a WaitFor specialization for MediaEventSource<void>. r=padenot"

This reverts commit 38e71b4151a309933042a3e3d4dfae41782b5199.

Revert "Bug 1979050 - Part 2: test. r=arai"

This reverts commit 4fd7ef4e78ac8500530b1a7c594f55079a7853f8.

Revert "Bug 1979050 - Part 1: Patch. r=arai"

This reverts commit 2865881fa7e4d4eefb39837af6dc522db52d00d8.

Revert "Bug 1771789 - Update browser-chrome recording-device-events expectations. r=jib"

This reverts commit 0dc61042220487712e3a7cdb9b7278177b1d1b40.

Revert "Bug 1771789 - Disregard failure to send Stop in MERVS state machine. r=jib"

This reverts commit 903b2ec85840bfe79c003c8de14bc276e60a5ce0.

Revert "Bug 1771789 - Extend webrtc::VideoFrame lifetime a bit to avoid an extra allocation and copy in some cases. r=jib"

This reverts commit 5f30f72c4a2b57a6a830a82d292a9c440df60458.

Revert "Bug 1771789 - In CamerasParent use one ShmemPool per source. r=jib"

This reverts commit 1c7baf74eeb18f584cae392e5bc7ab19310f256c.

Revert "Bug 1771789 - Refactor video capture source sharing in CamerasParent. r=jib"

This reverts commit 4249cd3e42d4bc4895bcbe59ef37246b9863a358.

Revert "Bug 1771789 - In nsTArray, allow Comparators to compare elements to a type defined by the caller. r=xpcom-reviewers,nika"

This reverts commit ec13eb6d56fb851f5d33d28c8f36b19482d9a660.

Revert "Bug 1771789 - In PCameras rename captureId with streamId for consistency. r=jib"

This reverts commit ae854f4c5bad7f5ba59de895b058227d31d369e7.

Revert "Bug 1771789 - Clean up type mismatches and unused things in CamerasParent. r=jib"

This reverts commit 0e8d216b6ee7ae9ff406dc1d001e4f06e3ff94f9.

Revert "Bug 1771789 - Pass CubebInputStream the same CubebHandle used to enumerate its device id. r=padenot"

This reverts commit b9ccd5f6f0f980387771d6fbf348d86da78bd493.

Revert "Bug 1771789 - Test audio capture cloning independence. r=jib"

This reverts commit 325ecb91be29d9765a3484ebeefaa952fb8a4385.

Revert "Bug 1771789 - Test video capture cloning independence. r=jib"

This reverts commit 6d15e2d2f0f1ba847b8737ab3fd74a52def316e7.

Revert "Bug 1771789 - Break out some helpers from MST-resizeMode.https.html. r=jib"

This reverts commit b5d1a06a6d144d0274b08fea918527668bbb46d7.

Revert "Bug 1771789 - Use "real fake" camera in some mochitests as loopback device does not work with native cloning. r=jib"

This reverts commit ef2ef262e4ccc21be914a39da772e0e5faf6ee0c.

Revert "Bug 1771789 - Make AudioInputProcessing::NumberOfChannels always valid. r=padenot,webrtc-reviewers,mjf"

This reverts commit 783d400b492ad3d2537768ed75d57f9db457b1d3.

Revert "Bug 1771789 - Deep-clone track sources for correct settings synchronously. r=jib"

This reverts commit 815093e756972d470d46b2d39e3e79a956943962.

Revert "Bug 1771789 - Allow re-allocating video device without active camera permission. r=jib"

This reverts commit 90c8d1ab17d4678b5d9b0bf74ff4867457d4f883.

Revert "Bug 1771789 - Implement cloning for LocalTrackSource. r=jib"

This reverts commit e8a836995bcbb97b125aea03ca317c42a59c3013.

Revert "Bug 1771789 - Add DeviceState::mAllocated to avoid Stop and Deallocate of non-allocated sources. r=jib"

This reverts commit 2de57563a102ada9e8bbb5066edb8a21a084642b.

Revert "Bug 1771789 - Break out a sync Initialize from InitializeAsync. r=jib"

This reverts commit fbb2acf9acb11ba062ab3f25aed5446b9adb1c57.

Revert "Bug 1771789 - Add and use an API for MediaStreamTrackSource cloning. r=jib"

This reverts commit f15a6f8e7d44042dcc49daea18ebf1757ad6abee.

Revert "Bug 1771789 - Implement MediaStreamTrack::CloneInternal in a single place with templates. r=jib"

This reverts commit 25444573d2df2cddc360965214168c00b640262e.

Revert "Bug 1998071 - Deal with transform-origin consistently for textPath. r=longsonr,firefox-svg-reviewers"

This reverts commit 83871a1e560aa0aca7664d05635dc38d6306cc85.

Revert "Bug 1995254 - Always rely on QueryPerformanceCounter for Windows TimeStamp. r=glandium,profiler-reviewers,mstange"

This reverts commit bfa6a93a2fa7a222815b5f286f04cf10e974c957.

Revert "Bug 1992407 - Migrate the DatePicker to the material design and fix the colours. r=android-reviewers,aaronmt,calu"

This reverts commit b21ddce98f6ed9692b645de24eb0169f037d811f.

Reapply "Bug 1998949 [Wayland] Don't fire bounds recalculation from configure event as we relly on mContainer GdkWindow size r=emilio" for causing failures at browser_changePiPSrcInFullscreen.js.

This reverts commit 2b99e2c05a0624076ec6a6dbdc1f5ac877461247.

Revert "Bug 1994942 - Don't use views for subdocs. r=tnikkel,jwatt"

This reverts commit 381884b9d7dea0ddb4c77b5b364ef8364459d865.

Revert "Bug 1983111 - [Tab Management Phase 1] Make the status bar transparent when Tab Manager is scrolled. r=android-reviewers,calu"

This reverts commit 611a8170c1e6c3c82d6417012f0a95bf008185ef.

Revert "Bug 1990888 - Fix editing existing DoH provider once submitted r=android-reviewers,twhite"

This reverts commit 42f54f3111a785c9031fb94976d390540398d434.

Revert "No Bug - Bumping Mobile l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE"

This reverts commit 5ac9f353aea8cc7781a3a0aafbf42fa9405e7261.

Revert "No Bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE"

This reverts commit 0b0a29d6c2b324cf9c78bed3f560bed73b3c2db3.

Revert "Bug 1999071 - Allow random order around imported modules. r=bthrall"

This reverts commit 8c0b071d98305ce9faefce2e0489b34129e9a422.

Revert "Bug 1998610 - Set up Firefox Home settings section r=home-newtab-reviewers,fluent-reviewers,bolsson,reemhamz,akulyk,maxx"

This reverts commit f26cc44848f72a396e67a69144449dd48eb00303.

Revert "Bug 1998949 [Wayland] Don't fire bounds recalculation from configure event as we relly on mContainer GdkWindow size r=emilio"

This reverts commit 132b135bf961bea2acac204c414fc0dff0ff8090.

Diffstat:
Mbrowser/base/content/test/webrtc/browser_devices_get_user_media_paused.js | 6+-----
Mbrowser/components/preferences/home.inc.xhtml | 8--------
Mbrowser/components/preferences/home.js | 73-------------------------------------------------------------------------
Mbrowser/components/preferences/main.js | 29-----------------------------
Mbrowser/config/mozconfigs/allowlist | 3+++
Mbrowser/config/mozconfigs/macosx64-aarch64/devedition | 5+++++
Mbrowser/config/mozconfigs/macosx64-aarch64/nightly | 5+++++
Mbrowser/config/mozconfigs/macosx64/devedition | 5+++++
Mbrowser/config/mozconfigs/macosx64/nightly | 5+++++
Mbrowser/extensions/newtab/lib/AboutPreferences.sys.mjs | 7-------
Mbrowser/installer/linux/app/debian/distribution.ini | 1+
Dbrowser/installer/linux/app/rpm/distribution.ini | 7-------
Mbrowser/installer/linux/app/rpm/firefox.spec.j2 | 1-
Mbrowser/locales/en-US/browser/preferences/preferences.ftl | 11+----------
Mbrowser/locales/l10n-changesets.json | 226++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mdocshell/base/nsDocShell.cpp | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mdocshell/base/nsIDocumentViewer.idl | 6+++---
Mdom/base/Document.cpp | 9+--------
Mdom/base/Document.h | 3+--
Mdom/base/nsContentUtils.h | 1+
Mdom/base/nsDeprecatedOperationList.h | 3---
Mdom/base/nsFrameLoader.cpp | 39+++++++++++++++++++++++++++++++--------
Mdom/base/nsFrameLoader.h | 27++++++++++++++-------------
Mdom/base/test/browser_script_loader_js_cache_dyn_import.js | 184++++++++++++++++++++++---------------------------------------------------------
Mdom/base/test/browser_script_loader_js_cache_module.js | 102++++++++++++++++++++++++++++++-------------------------------------------------
Mdom/base/test/jsmodules/importmaps/chrome.toml | 7-------
Ddom/base/test/jsmodules/importmaps/module_1979050.mjs | 3---
Ddom/base/test/jsmodules/importmaps/scope1/module_1979050.mjs | 5-----
Ddom/base/test/jsmodules/importmaps/scope1/scope2/module_1979050.mjs | 5-----
Ddom/base/test/jsmodules/importmaps/test_1979050.html | 50--------------------------------------------------
Ddom/base/test/jsmodules/importmaps/test_1979050_2.html | 69---------------------------------------------------------------------
Mdom/base/use_counter_metrics.yaml | 102-------------------------------------------------------------------------------
Mdom/ipc/BrowserParent.cpp | 40++++++++++++++++++++++++++++------------
Mdom/locales/en-US/chrome/dom/dom.properties | 6------
Mdom/media/AudioStreamTrack.cpp | 5+++--
Mdom/media/AudioStreamTrack.h | 5+++--
Mdom/media/CubebInputStream.cpp | 9+++++----
Mdom/media/CubebInputStream.h | 1-
Mdom/media/MediaDecoderStateMachine.cpp | 6+++---
Mdom/media/MediaManager.cpp | 316+++++++++++++++----------------------------------------------------------------
Mdom/media/MediaManager.h | 8--------
Mdom/media/MediaStreamTrack.cpp | 9+++++++--
Mdom/media/MediaStreamTrack.h | 31++-----------------------------
Mdom/media/VideoStreamTrack.cpp | 5+++--
Mdom/media/VideoStreamTrack.h | 5+++--
Mdom/media/gtest/TestAudioDecoderInputTrack.cpp | 26+++++++++++++++++---------
Ddom/media/gtest/TestDecodedStream.cpp | 382-------------------------------------------------------------------------------
Mdom/media/gtest/moz.build | 1-
Mdom/media/mediasink/AudioDecoderInputTrack.cpp | 2+-
Mdom/media/mediasink/AudioDecoderInputTrack.h | 10++++------
Mdom/media/mediasink/DecodedStream.cpp | 117+++++++++++++++++++++++---------------------------------------------------------
Mdom/media/mediasink/DecodedStream.h | 21++++++---------------
Mdom/media/systemservices/CamerasChild.cpp | 97+++++++++++++++++++++++++++++++------------------------------------------------
Mdom/media/systemservices/CamerasChild.h | 21+++++----------------
Mdom/media/systemservices/CamerasParent.cpp | 943++++++++++++++++++++++++++++++-------------------------------------------------
Mdom/media/systemservices/CamerasParent.h | 166++++++++++++++++++-------------------------------------------------------------
Mdom/media/systemservices/PCameras.ipdl | 16++++++++--------
Mdom/media/systemservices/VideoEngine.cpp | 67++++++++++++++++++++-----------------------------------------------
Mdom/media/systemservices/VideoEngine.h | 16++++------------
Mdom/media/webrtc/MediaEngine.h | 7-------
Mdom/media/webrtc/MediaEngineFake.cpp | 28----------------------------
Mdom/media/webrtc/MediaEngineFake.h | 2--
Mdom/media/webrtc/MediaEngineRemoteVideoSource.cpp | 58+++++++++++-----------------------------------------------
Mdom/media/webrtc/MediaEngineRemoteVideoSource.h | 6+-----
Mdom/media/webrtc/MediaEngineWebRTC.cpp | 19-------------------
Mdom/media/webrtc/MediaEngineWebRTC.h | 2--
Mdom/media/webrtc/MediaEngineWebRTCAudio.cpp | 11-----------
Mdom/media/webrtc/MediaEngineWebRTCAudio.h | 14+++-----------
Mdom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html | 4----
Mdom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html | 4----
Mdom/midi/midir_impl/src/lib.rs | 12++++++++++++
Mdom/performance/Performance.cpp | 4++--
Mdom/quota/ActorsParent.cpp | 6++----
Mdom/quota/GroupInfoPair.h | 6+-----
Mdom/quota/GroupInfoPairImpl.h | 8+++-----
Mdom/quota/QuotaManager.h | 14+-------------
Mdom/script/ScriptLoader.cpp | 91++++++++++++++++++++++++++++++++++---------------------------------------------
Mdom/script/ScriptLoader.h | 21+++++----------------
Mdom/script/SharedScriptCache.cpp | 114+++++++++----------------------------------------------------------------------
Mdom/script/SharedScriptCache.h | 27---------------------------
Mdom/svg/SVGGeometryElement.cpp | 18+++++++++++++++---
Mdom/webidl/MediaDebugInfo.webidl | 1-
Mipc/glue/IPCMessageUtilsSpecializations.h | 24++++++++++++++++++++++++
Mjs/loader/ImportMap.cpp | 23++++-------------------
Mjs/src/jit-test/lib/adhoc-multiplatform-test.js | 28++++++++++++++++++++++++----
Mjs/src/jit-test/lib/codegen-arm64-test.js | 5++---
Mjs/src/jit-test/lib/codegen-test-common.js | 21+++++++--------------
Mjs/src/jit-test/lib/codegen-x64-test.js | 5++---
Mjs/src/jit-test/lib/codegen-x86-test.js | 5++---
Mjs/src/jit-test/tests/wasm/binop-x64-ion-codegen.js | 2+-
Mjs/src/jit/LIR.h | 11+++++++----
Mjs/src/jit/LIROps.yaml | 102++++++++++++++++---------------------------------------------------------------
Mjs/src/jit/PerfSpewer.cpp | 2+-
Mjs/src/jit/x64/CodeGenerator-x64.cpp | 115++++++++++++++++++++++++++++---------------------------------------------------
Mjs/src/jit/x64/LIR-x64.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/x64/Lowering-x64.cpp | 16++++++++--------
Mjs/src/jit/x64/MacroAssembler-x64-inl.h | 16+++-------------
Mjs/src/jit/x64/MacroAssembler-x64.cpp | 4++--
Mjs/src/jit/x86-shared/CodeGenerator-x86-shared.cpp | 560++++++++++++++++++++++++++++---------------------------------------------------
Mjs/src/jit/x86-shared/CodeGenerator-x86-shared.h | 5-----
Ajs/src/jit/x86-shared/LIR-x86-shared.h | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/x86-shared/Lowering-x86-shared.cpp | 121+++++++++++++++++++++++++++++--------------------------------------------------
Mjs/src/jit/x86/CodeGenerator-x86.cpp | 2+-
Mlayout/base/PresShell.cpp | 165++++++++++++++++++++++++++++++++++---------------------------------------------
Mlayout/base/PresShell.h | 7++-----
Mlayout/base/nsDocumentViewer.cpp | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mlayout/base/nsLayoutUtils.cpp | 89++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mlayout/base/nsLayoutUtils.h | 16+++++++++++++---
Mlayout/generic/FrameClasses.py | 2+-
Dlayout/generic/crashtests/1999326.html | 11-----------
Mlayout/generic/crashtests/crashtests.list | 1-
Mlayout/generic/nsSubDocumentFrame.cpp | 399+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mlayout/generic/nsSubDocumentFrame.h | 36++++++++++++------------------------
Mlayout/generic/nsTextFrame.cpp | 18+++++++++++-------
Mlayout/mathml/nsMathMLOperators.cpp | 10+++-------
Mlayout/mathml/nsMathMLmoFrame.cpp | 32+++++---------------------------
Mlayout/mathml/nsMathMLmunderoverFrame.cpp | 14--------------
Mlayout/printing/nsPrintJob.cpp | 75+++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mlayout/printing/nsPrintJob.h | 7++++---
Mlayout/xul/nsMenuPopupFrame.cpp | 3++-
Mmobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragment.kt | 293+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mmobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mmobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/WebControlsTest.kt | 14+++++++++++---
Mmobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt | 11++++++++---
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/doh/root/DohSettingsScreen.kt | 4+---
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/banner/TabsTrayBanner.kt | 190+++++++++++++++++++++++++++++++++----------------------------------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabpage/TabLayout.kt | 1+
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabstray/TabsTray.kt | 158++++++++++++++++++++++++++++++++-----------------------------------------------
Dmobile/android/fenix/app/src/main/res/color/time_picker_button_background_tint.xml | 7-------
Mmobile/android/fenix/app/src/main/res/values/styles.xml | 71+++++++++++++++++++++++++++++++----------------------------------------
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuStoreTest.kt | 6+++---
Mmobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt | 2+-
Mmobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt | 4+++-
Mmobile/locales/l10n-changesets.json | 196++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mmodules/libpref/init/StaticPrefList.yaml | 13-------------
Mmozglue/baseprofiler/core/platform.cpp | 7++++---
Mmozglue/misc/TimeStamp.h | 63++++++++++++++++++++++++++++++++++++++++++++-------------------
Mmozglue/misc/TimeStamp_darwin.cpp | 62++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mmozglue/misc/TimeStamp_windows.cpp | 527++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Amozglue/misc/TimeStamp_windows.h | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmozglue/misc/moz.build | 1+
Mnetwerk/base/Predictor.cpp | 38++++++++++++++++++++++++++++++++++++++
Mnetwerk/base/Predictor.h | 9+++++++++
Mnetwerk/base/nsINetworkPredictor.idl | 7+++++++
Mnetwerk/protocol/http/HttpBaseChannel.cpp | 10+---------
Mnetwerk/protocol/http/nsHttpHandler.cpp | 31+++++++++++--------------------
Mnetwerk/protocol/http/nsHttpHandler.h | 4++--
Mnetwerk/test/browser/browser.toml | 6------
Dnetwerk/test/browser/browser_accept_language_override.js | 152-------------------------------------------------------------------------------
Dnetwerk/test/browser/browser_link_hover_speculative_connection.js | 362-------------------------------------------------------------------------------
Dnetwerk/test/browser/file_link_hover.sjs | 28----------------------------
Dnetwerk/test/browser/request_accept_language.sjs | 7-------
Mnetwerk/test/unit/test_predictor.js | 23+++++++++++++++++++++++
Mpython/mozbuild/mozbuild/repackaging/rpm.py | 2+-
Msecurity/sandbox/win/src/sandboxbroker/sandboxBroker.cpp | 6------
Mtesting/gtest/mozilla/WaitFor.h | 24------------------------
Mtesting/web-platform/mozilla/meta/mathml/mathml-console-messages.html.ini | 2+-
Dtesting/web-platform/mozilla/meta/mediacapture-streams/MediaStreamTrack-independent-clones.https.html.ini | 32--------------------------------
Dtesting/web-platform/mozilla/meta/screen-capture/MediaStreamTrack-independent-clones.https.html.ini | 3---
Mtesting/web-platform/mozilla/tests/mathml/mathml-console-messages.html | 54++++++------------------------------------------------
Dtesting/web-platform/mozilla/tests/mediacapture-streams/MediaStreamTrack-independent-clones.https.html | 209-------------------------------------------------------------------------------
Mtesting/web-platform/mozilla/tests/mediacapture-streams/MediaStreamTrack-resizeMode.https.html | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Dtesting/web-platform/mozilla/tests/mediacapture-streams/settings-helper.js | 54------------------------------------------------------
Dtesting/web-platform/mozilla/tests/mediacapture-streams/video-test-helper.js | 38--------------------------------------
Dtesting/web-platform/mozilla/tests/screen-capture/MediaStreamTrack-independent-clones.https.html | 232-------------------------------------------------------------------------------
Mtesting/web-platform/mozilla/tests/webdriver/bidi/emulation/set_locale_override/contexts.py | 6+++---
Dtesting/web-platform/tests/svg/text/reftests/text-path-transformed-002-ref.html | 7-------
Dtesting/web-platform/tests/svg/text/reftests/text-path-transformed-002.html | 18------------------
Mtesting/web-platform/tests/webdriver/tests/bidi/emulation/conftest.py | 41+++--------------------------------------
Mtesting/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/contexts.py | 10+++++-----
Mtesting/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/locale.py | 2+-
Mtesting/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/user_contexts.py | 10+++++-----
Mtesting/web-platform/tests/webdriver/tests/bidi/network/set_extra_headers/conftest.py | 27+++++++++++++++++++++++++++
Mtesting/web-platform/tests/webdriver/tests/support/fixtures_bidi.py | 31++-----------------------------
Mtoolkit/components/formautofill/shared/HeuristicsRegExp.sys.mjs | 11++---------
Mtools/profiler/core/platform.cpp | 7++++---
Mtools/profiler/public/ETWTools.h | 16+++++++++++-----
Mview/nsView.cpp | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mview/nsView.h | 18+++++++++++++++---
Mview/nsViewManager.cpp | 61++++++++++++++++++++++++++++++++++++++++++-------------------
Mview/nsViewManager.h | 22+++++++++++++++++++---
Mwidget/PuppetWidget.cpp | 2+-
Mwidget/gtk/nsWindow.cpp | 6+-----
Mxpcom/ds/nsTArray.h | 34++++++++++++++++------------------
Mxpcom/tests/gtest/TestTArray2.cpp | 23+++++------------------
Mxpcom/threads/TimerThread.cpp | 24++++++++++++------------
186 files changed, 3961 insertions(+), 5961 deletions(-)

diff --git a/browser/base/content/test/webrtc/browser_devices_get_user_media_paused.js b/browser/base/content/test/webrtc/browser_devices_get_user_media_paused.js @@ -245,14 +245,10 @@ var gTests = [ // Clone audio and video, their state will be enabled await cloneTracks(true, true); - observerPromise = expectObserverCalled("recording-device-events", 2); - // Disable both audio and video. await setTrackEnabled(false, false); - await observerPromise; - - observerPromise = expectObserverCalled("recording-device-events"); + observerPromise = expectObserverCalled("recording-device-events", 2); // Stop the clones. This should disable the sharing indicators. await stopClonedTracks(true, true); diff --git a/browser/components/preferences/home.inc.xhtml b/browser/components/preferences/home.inc.xhtml @@ -92,7 +92,6 @@ <groupbox id="homeContentsGroup" data-category="paneHome" data-subcategory="contents" - data-srd-groupid="home" hidden="true"> <label><html:h2 data-l10n-id="home-prefs-content-header2" /></label> <description class="description-deemphasized" data-l10n-id="home-prefs-content-description2" /> @@ -113,11 +112,4 @@ <vbox id="highlights" /> </groupbox> - -<html:setting-group - groupid="home" - hidden="true" - data-category="paneHome" - data-subcategory="contents" /> - </html:template> diff --git a/browser/components/preferences/home.js b/browser/components/preferences/home.js @@ -38,77 +38,6 @@ const NEW_TAB_KEY = "newTabURL"; const BLANK_HOMEPAGE_URL = "chrome://browser/content/blanktab.html"; -// New Prefs UI: we need to check for this setting before registering prefs -// so that old-style prefs continue working -if (Services.prefs.getBoolPref("browser.settings-redesign.enabled")) { - Preferences.addAll([ - { id: "browser.newtabpage.activity-stream.showSearch", type: "bool" }, - { - id: "browser.newtabpage.activity-stream.system.showWeather", - type: "bool", - }, - { id: "browser.newtabpage.activity-stream.showWeather", type: "bool" }, - { - id: "browser.newtabpage.activity-stream.widgets.system.lists.enabled", - type: "bool", - }, - { - id: "browser.newtabpage.activity-stream.widgets.lists.enabled", - type: "bool", - }, - { - id: "browser.newtabpage.activity-stream.widgets.system.focusTimer.enabled", - type: "bool", - }, - { - id: "browser.newtabpage.activity-stream.widgets.focusTimer.enabled", - type: "bool", - }, - ]); - - // Search - Preferences.addSetting({ - id: "webSearch", - pref: "browser.newtabpage.activity-stream.showSearch", - }); - - // Weather - Preferences.addSetting({ - id: "showWeather", - pref: "browser.newtabpage.activity-stream.system.showWeather", - }); - Preferences.addSetting({ - id: "weather", - pref: "browser.newtabpage.activity-stream.showWeather", - deps: ["showWeather"], - visible: ({ showWeather }) => showWeather.value, - }); - - // Widgets: lists - Preferences.addSetting({ - id: "listsEnabled", - pref: "browser.newtabpage.activity-stream.widgets.system.lists.enabled", - }); - Preferences.addSetting({ - id: "lists", - pref: "browser.newtabpage.activity-stream.widgets.lists.enabled", - deps: ["listsEnabled"], - visible: ({ listsEnabled }) => listsEnabled.value, - }); - - // Widgets: timer - Preferences.addSetting({ - id: "timerEnabled", - pref: "browser.newtabpage.activity-stream.widgets.system.focusTimer.enabled", - }); - Preferences.addSetting({ - id: "timer", - pref: "browser.newtabpage.activity-stream.widgets.focusTimer.enabled", - deps: ["timerEnabled"], - visible: ({ timerEnabled }) => timerEnabled.value, - }); -} - var gHomePane = { HOME_MODE_FIREFOX_HOME: "0", HOME_MODE_BLANK: "1", @@ -737,8 +666,6 @@ var gHomePane = { }, init() { - initSettingGroup("home"); - // Event Listeners document .getElementById("homePageUrl") diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js @@ -1177,35 +1177,6 @@ let SETTINGS_CONFIG = { }, ], }, - home: { - inProgress: true, - headingLevel: 2, - l10nId: "home-prefs-content-header", - // Icons are not ready to be used yet. - // iconSrc: "chrome://browser/skin/home.svg", - items: [ - { - id: "webSearch", - l10nId: "home-prefs-search-header2", - control: "moz-toggle", - }, - { - id: "weather", - l10nId: "home-prefs-weather-header", - control: "moz-toggle", - }, - { - id: "lists", - l10nId: "home-prefs-lists-header", - control: "moz-toggle", - }, - { - id: "timer", - l10nId: "home-prefs-timer-header", - control: "moz-toggle", - }, - ], - }, zoom: { // This section is marked as in progress for testing purposes inProgress: true, diff --git a/browser/config/mozconfigs/allowlist b/browser/config/mozconfigs/allowlist @@ -14,6 +14,9 @@ for platform in all_platforms: allowlist['nightly']['macosx64'] += [ 'ac_add_options --enable-instruments', + 'ac_add_options --enable-dtrace', + 'if test `uname -s` != Linux; then', + 'fi', ] allowlist['nightly']['win64'] += [ diff --git a/browser/config/mozconfigs/macosx64-aarch64/devedition b/browser/config/mozconfigs/macosx64-aarch64/devedition @@ -5,6 +5,11 @@ MOZ_REQUIRE_SIGNING= ac_add_options --enable-instruments +# Cross-compiled builds fail when dtrace is enabled +if test `uname -s` != Linux; then + ac_add_options --enable-dtrace +fi + if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then ac_add_options --with-macbundlename-prefix=Firefox fi diff --git a/browser/config/mozconfigs/macosx64-aarch64/nightly b/browser/config/mozconfigs/macosx64-aarch64/nightly @@ -2,6 +2,11 @@ ac_add_options --enable-instruments +# Cross-compiled builds fail when dtrace is enabled +if test `uname -s` != Linux; then + ac_add_options --enable-dtrace +fi + ac_add_options --with-branding=browser/branding/nightly . "$topsrcdir/build/mozconfig.common.override" diff --git a/browser/config/mozconfigs/macosx64/devedition b/browser/config/mozconfigs/macosx64/devedition @@ -5,6 +5,11 @@ MOZ_REQUIRE_SIGNING= ac_add_options --enable-instruments +# Cross-compiled builds fail when dtrace is enabled +if test `uname -s` != Linux; then + ac_add_options --enable-dtrace +fi + if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then ac_add_options --with-macbundlename-prefix=Firefox fi diff --git a/browser/config/mozconfigs/macosx64/nightly b/browser/config/mozconfigs/macosx64/nightly @@ -2,6 +2,11 @@ ac_add_options --enable-instruments +# Cross-compiled builds fail when dtrace is enabled +if test `uname -s` != Linux; then + ac_add_options --enable-dtrace +fi + ac_add_options --with-branding=browser/branding/nightly . "$topsrcdir/build/mozconfig.common.override" diff --git a/browser/extensions/newtab/lib/AboutPreferences.sys.mjs b/browser/extensions/newtab/lib/AboutPreferences.sys.mjs @@ -205,13 +205,6 @@ export class AboutPreferences { * We have to potentially re-assign the `id` if it is `web-search`. * We should restore `id` back to a const after Fx146+. */ - - /* Do not render old-style settings if new settings UI is enabled - this is needed to avoid - * registering prefs twice and ensuing errors */ - if (Services.prefs.getBoolPref("browser.settings-redesign.enabled")) { - return; - } - let { id } = sectionData; const { pref: prefData, diff --git a/browser/installer/linux/app/debian/distribution.ini b/browser/installer/linux/app/debian/distribution.ini @@ -5,4 +5,5 @@ about=Mozilla Firefox Debian Package [Preferences] intl.locale.requested="" +dom.ipc.forkserver.enable=true browser.gnome-search-provider.enabled=true diff --git a/browser/installer/linux/app/rpm/distribution.ini b/browser/installer/linux/app/rpm/distribution.ini @@ -1,7 +0,0 @@ -[Global] -id=mozilla-rpm -version=1.0 -about=Mozilla Firefox RPM Package - -[Preferences] -intl.locale.requested="" diff --git a/browser/installer/linux/app/rpm/firefox.spec.j2 b/browser/installer/linux/app/rpm/firefox.spec.j2 @@ -27,7 +27,6 @@ Source{{ loop.index + 2 }}: {{ metadata.extension_id }}.xpi %{__install} -m 0644 %{SOURCE2} %{buildroot}%{_mandir}/man1/%{name}.1 %{__ln_s} %{mozappdir}/firefox %{buildroot}%{_bindir}/%{name} %{__mkdir_p} %{buildroot}/%{mozappdir}/distribution/extensions -%{__install} -m 0644 %{_sourcedir}/firefox/distribution/distribution.ini %{buildroot}%{mozappdir}/distribution/distribution.ini {%- for codename, metadata in LANGUAGES.items() %} %{__install} -m 0644 %{SOURCE{{ loop.index + 2 }}} %{buildroot}%{mozappdir}/distribution/extensions/{{ metadata.extension_id }}.xpi {%- endfor %} diff --git a/browser/locales/en-US/browser/preferences/preferences.ftl b/browser/locales/en-US/browser/preferences/preferences.ftl @@ -744,9 +744,8 @@ choose-bookmark = ## Home Section - Firefox Home Content Customization -home-prefs-content-header = - .label = { -firefox-home-brand-name } home-prefs-content-header2 = { -firefox-home-brand-name } Content +home-prefs-content-header3 = { -firefox-home-brand-name } home-prefs-content-description2 = Choose what content you want on your { -firefox-home-brand-name } screen. home-prefs-search-header = @@ -792,14 +791,6 @@ home-prefs-trending-search-header = .label = Trending searches home-prefs-trending-search-description = Popular and frequently searched topics -# Lists is a widget on New Tab, similar to a to-do widget -home-prefs-lists-header = - .label = Lists - -# Timer is a widget on New Tab, similar to the Pomodoro timer. -home-prefs-timer-header = - .label = Timer - # "Support" here means to help sustain or contribute to something, especially through funding or sponsorship. home-prefs-support-firefox-header = .label = Support { -brand-product-name } diff --git a/browser/locales/l10n-changesets.json b/browser/locales/l10n-changesets.json @@ -15,7 +15,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "af": { "pin": false, @@ -33,7 +33,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "an": { "pin": false, @@ -51,7 +51,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ar": { "pin": false, @@ -69,7 +69,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ast": { "pin": false, @@ -87,7 +87,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "az": { "pin": false, @@ -105,7 +105,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "be": { "pin": false, @@ -123,7 +123,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "bg": { "pin": false, @@ -141,7 +141,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "bn": { "pin": false, @@ -159,7 +159,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "bo": { "pin": false, @@ -177,7 +177,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "br": { "pin": false, @@ -195,7 +195,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "brx": { "pin": false, @@ -213,7 +213,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "bs": { "pin": false, @@ -231,7 +231,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ca": { "pin": false, @@ -249,7 +249,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ca-valencia": { "pin": false, @@ -267,7 +267,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "cak": { "pin": false, @@ -285,7 +285,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ckb": { "pin": false, @@ -303,7 +303,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "cs": { "pin": false, @@ -321,7 +321,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "cy": { "pin": false, @@ -339,7 +339,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "da": { "pin": false, @@ -357,7 +357,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "de": { "pin": false, @@ -375,7 +375,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "dsb": { "pin": false, @@ -393,7 +393,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "el": { "pin": false, @@ -411,7 +411,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "en-CA": { "pin": false, @@ -429,7 +429,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "en-GB": { "pin": false, @@ -447,7 +447,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "eo": { "pin": false, @@ -465,7 +465,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-AR": { "pin": false, @@ -483,7 +483,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-CL": { "pin": false, @@ -501,7 +501,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-ES": { "pin": false, @@ -519,7 +519,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-MX": { "pin": false, @@ -537,7 +537,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "et": { "pin": false, @@ -555,7 +555,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "eu": { "pin": false, @@ -573,7 +573,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fa": { "pin": false, @@ -591,7 +591,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ff": { "pin": false, @@ -609,7 +609,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fi": { "pin": false, @@ -627,7 +627,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fr": { "pin": false, @@ -645,7 +645,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fur": { "pin": false, @@ -663,7 +663,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fy-NL": { "pin": false, @@ -681,7 +681,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ga-IE": { "pin": false, @@ -699,7 +699,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gd": { "pin": false, @@ -717,7 +717,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gl": { "pin": false, @@ -735,7 +735,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gn": { "pin": false, @@ -753,7 +753,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gu-IN": { "pin": false, @@ -771,7 +771,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "he": { "pin": false, @@ -789,7 +789,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hi-IN": { "pin": false, @@ -807,7 +807,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hr": { "pin": false, @@ -825,7 +825,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hsb": { "pin": false, @@ -843,7 +843,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hu": { "pin": false, @@ -861,7 +861,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hy-AM": { "pin": false, @@ -879,7 +879,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hye": { "pin": false, @@ -897,7 +897,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ia": { "pin": false, @@ -915,7 +915,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "id": { "pin": false, @@ -933,7 +933,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "is": { "pin": false, @@ -951,7 +951,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "it": { "pin": false, @@ -969,7 +969,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ja": { "pin": false, @@ -985,7 +985,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ja-JP-mac": { "pin": false, @@ -993,7 +993,7 @@ "macosx64", "macosx64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ka": { "pin": false, @@ -1011,7 +1011,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "kab": { "pin": false, @@ -1029,7 +1029,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "kk": { "pin": false, @@ -1047,7 +1047,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "km": { "pin": false, @@ -1065,7 +1065,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "kn": { "pin": false, @@ -1083,7 +1083,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ko": { "pin": false, @@ -1101,7 +1101,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lij": { "pin": false, @@ -1119,7 +1119,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lo": { "pin": false, @@ -1137,7 +1137,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lt": { "pin": false, @@ -1155,7 +1155,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ltg": { "pin": false, @@ -1173,7 +1173,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lv": { "pin": false, @@ -1191,7 +1191,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "meh": { "pin": false, @@ -1209,7 +1209,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "mk": { "pin": false, @@ -1227,7 +1227,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ml": { "pin": false, @@ -1245,7 +1245,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "mr": { "pin": false, @@ -1263,7 +1263,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ms": { "pin": false, @@ -1281,7 +1281,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "my": { "pin": false, @@ -1299,7 +1299,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "nb-NO": { "pin": false, @@ -1317,7 +1317,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ne-NP": { "pin": false, @@ -1335,7 +1335,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "nl": { "pin": false, @@ -1353,7 +1353,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "nn-NO": { "pin": false, @@ -1371,7 +1371,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "oc": { "pin": false, @@ -1389,7 +1389,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pa-IN": { "pin": false, @@ -1407,7 +1407,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pl": { "pin": false, @@ -1425,7 +1425,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pt-BR": { "pin": false, @@ -1443,7 +1443,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pt-PT": { "pin": false, @@ -1461,7 +1461,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "rm": { "pin": false, @@ -1479,7 +1479,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ro": { "pin": false, @@ -1497,7 +1497,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ru": { "pin": false, @@ -1515,7 +1515,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sat": { "pin": false, @@ -1533,7 +1533,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sc": { "pin": false, @@ -1551,7 +1551,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "scn": { "pin": false, @@ -1569,7 +1569,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sco": { "pin": false, @@ -1587,7 +1587,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "si": { "pin": false, @@ -1605,7 +1605,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sk": { "pin": false, @@ -1623,7 +1623,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "skr": { "pin": false, @@ -1641,7 +1641,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sl": { "pin": false, @@ -1659,7 +1659,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "son": { "pin": false, @@ -1677,7 +1677,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sq": { "pin": false, @@ -1695,7 +1695,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sr": { "pin": false, @@ -1713,7 +1713,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sv-SE": { "pin": false, @@ -1731,7 +1731,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "szl": { "pin": false, @@ -1749,7 +1749,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ta": { "pin": false, @@ -1767,7 +1767,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "te": { "pin": false, @@ -1785,7 +1785,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "tg": { "pin": false, @@ -1803,7 +1803,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "th": { "pin": false, @@ -1821,7 +1821,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "tl": { "pin": false, @@ -1839,7 +1839,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "tr": { "pin": false, @@ -1857,7 +1857,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "trs": { "pin": false, @@ -1875,7 +1875,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "uk": { "pin": false, @@ -1893,7 +1893,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ur": { "pin": false, @@ -1911,7 +1911,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "uz": { "pin": false, @@ -1929,7 +1929,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "vi": { "pin": false, @@ -1947,7 +1947,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "wo": { "pin": false, @@ -1965,7 +1965,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "xh": { "pin": false, @@ -1983,7 +1983,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "zh-CN": { "pin": false, @@ -2001,7 +2001,7 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "zh-TW": { "pin": false, @@ -2019,6 +2019,6 @@ "win64-aarch64-devedition", "win64-devedition" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" } } \ No newline at end of file diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp @@ -7480,7 +7480,10 @@ nsresult nsDocShell::RestoreFromHistory() { // In cases where we use a transient about:blank viewer between loads, // we never show the transient viewer, so _its_ previous viewer is never - // destroyed. Destroy any such previous viewer now. + // unhooked from the view hierarchy. Destroy any such previous viewer now, + // before we grab the root view sibling, so that we don't grab a view + // that's about to go away. + if (mDocumentViewer) { // Make sure to hold a strong ref to previousViewer here while we // drop the reference to it from mDocumentViewer. @@ -7492,12 +7495,38 @@ nsresult nsDocShell::RestoreFromHistory() { } } - // Save the bounds of the root view's widget. + // Save off the root view's parent and sibling so that we can insert the + // new content viewer's root view at the same position. Also save the + // bounds of the root view's widget. + + nsView* rootViewSibling = nullptr; + nsView* rootViewParent = nullptr; LayoutDeviceIntRect newBounds(0, 0, 0, 0); PresShell* oldPresShell = GetPresShell(); if (oldPresShell) { - mDocumentViewer->GetBounds(newBounds); + nsViewManager* vm = oldPresShell->GetViewManager(); + if (vm) { + nsView* oldRootView = vm->GetRootView(); + + if (oldRootView) { + rootViewSibling = oldRootView->GetNextSibling(); + rootViewParent = oldRootView->GetParent(); + + mDocumentViewer->GetBounds(newBounds); + } + } + } + + nsCOMPtr<nsIContent> container; + RefPtr<Document> sibling; + if (rootViewParent && rootViewParent->GetParent()) { + nsIFrame* frame = rootViewParent->GetParent()->GetFrame(); + container = frame ? frame->GetContent() : nullptr; + } + if (rootViewSibling) { + nsIFrame* frame = rootViewSibling->GetFrame(); + sibling = frame ? frame->PresShell()->GetDocument() : nullptr; } // Transfer ownership to mDocumentViewer. By ensuring that either the @@ -7700,6 +7729,39 @@ nsresult nsDocShell::RestoreFromHistory() { nsViewManager* newVM = presShell ? presShell->GetViewManager() : nullptr; nsView* newRootView = newVM ? newVM->GetRootView() : nullptr; + // Insert the new root view at the correct location in the view tree. + if (container) { + nsSubDocumentFrame* subDocFrame = + do_QueryFrame(container->GetPrimaryFrame()); + rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr; + } else { + rootViewParent = nullptr; + } + if (sibling && sibling->GetPresShell() && + sibling->GetPresShell()->GetViewManager()) { + rootViewSibling = sibling->GetPresShell()->GetViewManager()->GetRootView(); + } else { + rootViewSibling = nullptr; + } + if (rootViewParent && newRootView && + newRootView->GetParent() != rootViewParent) { + nsViewManager* parentVM = rootViewParent->GetViewManager(); + if (parentVM) { + // InsertChild(parent, child, sib, true) inserts the child after + // sib in content order, which is before sib in view order. BUT + // when sib is null it inserts at the end of the the document + // order, i.e., first in view order. But when oldRootSibling is + // null, the old root as at the end of the view list --- last in + // content order --- and we want to call InsertChild(parent, child, + // nullptr, false) in that case. + parentVM->InsertChild(rootViewParent, newRootView, rootViewSibling, + rootViewSibling ? true : false); + + NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling, + "error in InsertChild"); + } + } + nsCOMPtr<nsPIDOMWindowInner> privWinInner = privWin->GetCurrentInnerWindow(); // If parent is suspended, increase suspension count. @@ -7736,6 +7798,15 @@ nsresult nsDocShell::RestoreFromHistory() { // presentation. If this is not the same size we showed it at last time, // then we need to resize the widget. + // XXXbryner This interacts poorly with Firefox's infobar. If the old + // presentation had the infobar visible, then we will resize the new + // presentation to that smaller size. However, firing the locationchanged + // event will hide the infobar, which will immediately resize the window + // back to the larger size. A future optimization might be to restore + // the presentation at the "wrong" size, then fire the locationchanged + // event and check whether the docshell's new size is the same as the + // cached viewer size (skipping the resize if they are equal). + if (newRootView) { if (!newBounds.IsEmpty() && !newBounds.ToUnknownRect().IsEqualEdges(oldBounds)) { @@ -7751,7 +7822,7 @@ nsresult nsDocShell::RestoreFromHistory() { // The FinishRestore call below can kill these, null them out so we don't // have invalid pointer lying around. - newRootView = nullptr; + newRootView = rootViewSibling = rootViewParent = nullptr; newVM = nullptr; // If the IsUnderHiddenEmbedderElement() state has been changed, we need to @@ -13751,21 +13822,8 @@ nsresult nsDocShell::OnOverLink(nsIContent* aContent, nsIURI* aURI, NS_ConvertUTF8toUTF16 uStr(spec); - // The speculative connect used to go through the predictor, but we don't - // need all that just to initiate a speculative connect. - if ((StaticPrefs::network_predictor_enable_hover_on_ssl() && - mCurrentURI->SchemeIs("https")) || - mCurrentURI->SchemeIs("http")) { - if (nsCOMPtr<nsISpeculativeConnect> specService = - mozilla::components::IO::Service()) { - // This would be a navigation, so if this is cross origin the speculative - // connection needs to have the origin of the URL not the current page. - nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal( - aURI, aContent->NodePrincipal()->OriginAttributesRef()); - - specService->SpeculativeConnect(aURI, principal, nullptr, false); - } - } + PredictorPredict(aURI, mCurrentURI, nsINetworkPredictor::PREDICT_LINK, + aContent->NodePrincipal()->OriginAttributesRef(), nullptr); rv = browserChrome->SetLinkStatus(uStr); return rv; diff --git a/docshell/base/nsIDocumentViewer.idl b/docshell/base/nsIDocumentViewer.idl @@ -18,7 +18,7 @@ webidl Node; class nsIWidget; class nsPresContext; -class nsSubDocumentFrame; +class nsView; class nsDOMNavigationTiming; namespace mozilla { class Encoding; @@ -35,7 +35,7 @@ class RemotePrintJobChild; [ptr] native nsIWidgetPtr(nsIWidget); [ref] native LayoutDeviceIntRectRef(mozilla::LayoutDeviceIntRect); [ptr] native nsPresContextPtr(nsPresContext); -[ptr] native nsSubDocumentFramePtr(nsSubDocumentFrame); +[ptr] native nsViewPtr(nsView); [ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming); [ptr] native Encoding(const mozilla::Encoding); [ptr] native PresShellPtr(mozilla::PresShell); @@ -251,7 +251,7 @@ interface nsIDocumentViewer : nsISupports * case, if mParentWidget is null then this document should not even * be displayed. */ - [noscript,notxpcom,nostdcall] nsSubDocumentFramePtr findContainerFrame(); + [noscript,notxpcom,nostdcall] nsViewPtr findContainerView(); /** * Set collector for navigation timing data (load, unload events). */ diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp @@ -7509,8 +7509,7 @@ static inline void AssertNoStaleServoDataIn(nsINode& aSubtreeRoot) { } already_AddRefed<PresShell> Document::CreatePresShell( - nsPresContext* aContext, nsViewManager* aViewManager, - nsSubDocumentFrame* aEmbedderFrame) { + nsPresContext* aContext, nsViewManager* aViewManager) { MOZ_DIAGNOSTIC_ASSERT(!mPresShell, "We have a presshell already!"); NS_ENSURE_FALSE(GetBFCacheEntry(), nullptr); @@ -7521,12 +7520,6 @@ already_AddRefed<PresShell> Document::CreatePresShell( // Note: we don't hold a ref to the shell (it holds a ref to us) mPresShell = presShell; - if (aEmbedderFrame) { - // It's important to do this as soon as possible so that - // GetRootPresContext() and so on do the right thing from the get go. - aEmbedderFrame->AddEmbeddingPresShell(presShell); - } - if (!mStyleSetFilled) { FillStyleSet(); } diff --git a/dom/base/Document.h b/dom/base/Document.h @@ -1175,8 +1175,7 @@ class Document : public nsINode, * presshell if the presshell should observe document mutations. */ MOZ_CAN_RUN_SCRIPT already_AddRefed<PresShell> CreatePresShell( - nsPresContext* aContext, nsViewManager* aViewManager, - nsSubDocumentFrame* aEmbedderFrame); + nsPresContext* aContext, nsViewManager* aViewManager); void DeletePresShell(); PresShell* GetPresShell() const { diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h @@ -118,6 +118,7 @@ class nsNodeInfoManager; class nsParser; class nsPIWindowRoot; class nsPresContext; +class nsView; class nsWrapperCache; enum class WindowMediatorFilter : uint8_t; diff --git a/dom/base/nsDeprecatedOperationList.h b/dom/base/nsDeprecatedOperationList.h @@ -47,9 +47,6 @@ DEPRECATED_OPERATION(InitMouseEvent) DEPRECATED_OPERATION(InitNSMouseEvent) DEPRECATED_OPERATION(MathML_DeprecatedMathSpaceValue2) DEPRECATED_OPERATION(MathML_DeprecatedMathVariant) -DEPRECATED_OPERATION(MathML_DeprecatedMoExplicitAccent) -DEPRECATED_OPERATION(MathML_DeprecatedMoverNonExplicitAccent) -DEPRECATED_OPERATION(MathML_DeprecatedMunderNonExplicitAccentunder) DEPRECATED_OPERATION(FormSubmissionUntrustedEvent) DEPRECATED_OPERATION(ElementSetCapture) DEPRECATED_OPERATION(ElementReleaseCapture) diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp @@ -117,6 +117,7 @@ #include "nsSubDocumentFrame.h" #include "nsThreadUtils.h" #include "nsUnicharUtils.h" +#include "nsView.h" #include "nsViewManager.h" #include "nsXPCOMPrivate.h" // for XUL_DLL #include "nsXULPopupManager.h" @@ -167,6 +168,7 @@ nsFrameLoader::nsFrameLoader(Element* aOwner, BrowsingContext* aBrowsingContext, bool aIsRemoteFrame, bool aNetworkCreated) : mPendingBrowsingContext(aBrowsingContext), mOwnerContent(aOwner), + mDetachedSubdocFrame(nullptr), mPendingSwitchID(0), mChildID(0), mRemoteType(NOT_REMOTE_TYPE), @@ -969,8 +971,13 @@ bool nsFrameLoader::Show(nsSubDocumentFrame* aFrame) { const bool marginsChanged = ds->UpdateFrameMargins(GetMarginAttributes(mOwnerContent)); + nsView* view = aFrame->EnsureInnerView(); + if (!view) { + return false; + } + // If we already have a pres shell (which can happen with <object> / <embed>) - // then hook it up to the frame. + // then hook it up in the view tree. if (PresShell* presShell = ds->GetPresShell()) { // Ensure root scroll frame is reflowed in case margins have changed. if (marginsChanged) { @@ -980,12 +987,24 @@ bool nsFrameLoader::Show(nsSubDocumentFrame* aFrame) { IntrinsicDirty::None, NS_FRAME_IS_DIRTY); } } - aFrame->EnsureEmbeddingPresShell(presShell); - MOZ_DIAGNOSTIC_ASSERT(presShell->GetViewManager()->GetRootView()); + nsView* childView = presShell->GetViewManager()->GetRootView(); + MOZ_DIAGNOSTIC_ASSERT(childView); + if (childView->GetParent() == view) { + // We were probably doing a docshell swap and succeeded before getting + // here, hooray, nothing else to do. + return true; + } + + // We did layout before due to <object> or <embed> and now we need to fix + // up our stuff and initialize our docshell below too. + MOZ_DIAGNOSTIC_ASSERT(!view->GetFirstChild()); + MOZ_DIAGNOSTIC_ASSERT(!childView->GetParent(), "Stale view?"); + nsSubDocumentFrame::InsertViewsInReverseOrder(childView, view); + nsSubDocumentFrame::EndSwapDocShellsForViews(view->GetFirstChild()); } RefPtr<nsDocShell> baseWindow = GetDocShell(); - baseWindow->InitWindow(nullptr, 0, 0, size.width, size.height); + baseWindow->InitWindow(view->GetWidget(), 0, 0, size.width, size.height); baseWindow->SetVisibility(true); NS_ENSURE_TRUE(GetDocShell(), false); @@ -3022,12 +3041,16 @@ nsFrameLoader::GetLazyLoadFrameResumptionState() { return sEmpty; } -void nsFrameLoader::SetDetachedSubdocs(WeakPresShellArray&& aDocs) { - mDetachedSubdocs = std::move(aDocs); +void nsFrameLoader::SetDetachedSubdocFrame(nsIFrame* aDetachedFrame) { + mDetachedSubdocFrame = aDetachedFrame; + mHadDetachedFrame = !!aDetachedFrame; } -auto nsFrameLoader::TakeDetachedSubdocs() -> WeakPresShellArray { - return std::move(mDetachedSubdocs); +nsIFrame* nsFrameLoader::GetDetachedSubdocFrame(bool* aOutIsSet) const { + if (aOutIsSet) { + *aOutIsSet = mHadDetachedFrame; + } + return mDetachedSubdocFrame.GetFrame(); } void nsFrameLoader::ApplySandboxFlags(uint32_t sandboxFlags) { diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h @@ -365,17 +365,18 @@ class nsFrameLoader final : public nsStubMutationObserver, mozilla::dom::Element* GetOwnerContent() { return mOwnerContent; } /** - * Stashes a list of detached pres shells on the frame loader. We do this when - * we're destroying the nsSubDocumentFrame. If the nsSubdocumentFrame is being - * reframed we'll restore the detached shells when they're recreated, - * otherwise we'll discard the old presentation and clear these. + * Stashes a detached nsIFrame on the frame loader. We do this when we're + * destroying the nsSubDocumentFrame. If the nsSubdocumentFrame is + * being reframed we'll restore the detached nsIFrame when it's recreated, + * otherwise we'll discard the old presentation and set the detached + * subdoc nsIFrame to null. */ - using WeakPresShellArray = nsTArray<nsWeakPtr>; - void SetDetachedSubdocs(WeakPresShellArray&&); - WeakPresShellArray TakeDetachedSubdocs(); - const WeakPresShellArray& GetDetachedSubdocs() const { - return mDetachedSubdocs; - } + void SetDetachedSubdocFrame(nsIFrame* aDetachedFrame); + + /** + * Retrieves the detached nsIFrame as set by SetDetachedSubdocFrame(). + */ + nsIFrame* GetDetachedSubdocFrame(bool* aOutIsSet = nullptr) const; /** * Applies a new set of sandbox flags. These are merged with the sandbox @@ -507,9 +508,9 @@ class nsFrameLoader final : public nsStubMutationObserver, // our <browser> element. RefPtr<mozilla::dom::Element> mOwnerContentStrong; - // Stores the detached pres shells of subdocuments. - // Used to restore the presentation after reframing. - WeakPresShellArray mDetachedSubdocs; + // Stores the root frame of the subdocument while the subdocument is being + // reframed. Used to restore the presentation after reframing. + WeakFrame mDetachedSubdocFrame; // When performing a process switch, this value is used rather than mURIToLoad // to identify the process-switching load which should be resumed in the diff --git a/dom/base/test/browser_script_loader_js_cache_dyn_import.js b/dom/base/test/browser_script_loader_js_cache_dyn_import.js @@ -21,26 +21,12 @@ add_task(async function testDiskCache_dynamicImport() { ev("load:source", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), ev("diskcache:disabled", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_dyn_imported1.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported1.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported2.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported2.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported3.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported3.mjs", - false - ), - ]), + ev("load:source", "file_js_cache_dyn_imported1.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported1.mjs", false), + ev("load:source", "file_js_cache_dyn_imported2.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported2.mjs", false), + ev("load:source", "file_js_cache_dyn_imported3.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported3.mjs", false), ], }, { @@ -49,26 +35,12 @@ add_task(async function testDiskCache_dynamicImport() { ev("load:source", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), ev("diskcache:disabled", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_dyn_imported1.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported1.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported2.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported2.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported3.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported3.mjs", - false - ), - ]), + ev("load:source", "file_js_cache_dyn_imported1.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported1.mjs", false), + ev("load:source", "file_js_cache_dyn_imported2.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported2.mjs", false), + ev("load:source", "file_js_cache_dyn_imported3.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported3.mjs", false), ], }, { @@ -77,26 +49,12 @@ add_task(async function testDiskCache_dynamicImport() { ev("load:source", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), ev("diskcache:disabled", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_dyn_imported1.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported1.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported2.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported2.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported3.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported3.mjs", - false - ), - ]), + ev("load:source", "file_js_cache_dyn_imported1.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported1.mjs", false), + ev("load:source", "file_js_cache_dyn_imported2.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported2.mjs", false), + ev("load:source", "file_js_cache_dyn_imported3.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported3.mjs", false), ], }, { @@ -105,32 +63,16 @@ add_task(async function testDiskCache_dynamicImport() { ev("load:source", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), ev("diskcache:register", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_dyn_imported1.mjs", false), - ev( - "diskcache:register", - "file_js_cache_dyn_imported1.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported2.mjs", false), - ev( - "diskcache:register", - "file_js_cache_dyn_imported2.mjs", - false - ), - ev("load:source", "file_js_cache_dyn_imported3.mjs", false), - ev( - "diskcache:register", - "file_js_cache_dyn_imported3.mjs", - false - ), - ]), + ev("load:source", "file_js_cache_dyn_imported1.mjs", false), + ev("diskcache:register", "file_js_cache_dyn_imported1.mjs", false), + ev("load:source", "file_js_cache_dyn_imported2.mjs", false), + ev("diskcache:register", "file_js_cache_dyn_imported2.mjs", false), + ev("load:source", "file_js_cache_dyn_imported3.mjs", false), + ev("diskcache:register", "file_js_cache_dyn_imported3.mjs", false), ev("diskcache:saved", "file_js_cache_dyn_importer.mjs", false), - unordered([ - ev("diskcache:saved", "file_js_cache_dyn_imported1.mjs", false), - ev("diskcache:saved", "file_js_cache_dyn_imported2.mjs", false), - ev("diskcache:saved", "file_js_cache_dyn_imported3.mjs", false), - ]), + ev("diskcache:saved", "file_js_cache_dyn_imported1.mjs", false), + ev("diskcache:saved", "file_js_cache_dyn_imported2.mjs", false), + ev("diskcache:saved", "file_js_cache_dyn_imported3.mjs", false), ], }, { @@ -139,26 +81,12 @@ add_task(async function testDiskCache_dynamicImport() { ev("load:diskcache", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), ev("diskcache:disabled", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:diskcache", "file_js_cache_dyn_imported1.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported1.mjs", - false - ), - ev("load:diskcache", "file_js_cache_dyn_imported2.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported2.mjs", - false - ), - ev("load:diskcache", "file_js_cache_dyn_imported3.mjs", false), - ev( - "diskcache:disabled", - "file_js_cache_dyn_imported3.mjs", - false - ), - ]), + ev("load:diskcache", "file_js_cache_dyn_imported1.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported1.mjs", false), + ev("load:diskcache", "file_js_cache_dyn_imported2.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported2.mjs", false), + ev("load:diskcache", "file_js_cache_dyn_imported3.mjs", false), + ev("diskcache:disabled", "file_js_cache_dyn_imported3.mjs", false), ], }, ], @@ -194,14 +122,12 @@ add_task(async function testMemoryCache_dynamicImport() { ev("load:source", "file_js_cache_dyn_importer.mjs"), ev("memorycache:saved", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_dyn_imported1.mjs", false), - ev("memorycache:saved", "file_js_cache_dyn_imported1.mjs", false), - ev("load:source", "file_js_cache_dyn_imported2.mjs", false), - ev("memorycache:saved", "file_js_cache_dyn_imported2.mjs", false), - ev("load:source", "file_js_cache_dyn_imported3.mjs", false), - ev("memorycache:saved", "file_js_cache_dyn_imported3.mjs", false), - ]), + ev("load:source", "file_js_cache_dyn_imported1.mjs", false), + ev("memorycache:saved", "file_js_cache_dyn_imported1.mjs", false), + ev("load:source", "file_js_cache_dyn_imported2.mjs", false), + ev("memorycache:saved", "file_js_cache_dyn_imported2.mjs", false), + ev("load:source", "file_js_cache_dyn_imported3.mjs", false), + ev("memorycache:saved", "file_js_cache_dyn_imported3.mjs", false), ev("diskcache:noschedule"), ], }, @@ -210,11 +136,9 @@ add_task(async function testMemoryCache_dynamicImport() { events: [ ev("load:memorycache", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), ev("diskcache:noschedule"), ], }, @@ -223,11 +147,9 @@ add_task(async function testMemoryCache_dynamicImport() { events: [ ev("load:memorycache", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), ev("diskcache:noschedule"), ], }, @@ -236,11 +158,9 @@ add_task(async function testMemoryCache_dynamicImport() { events: [ ev("load:memorycache", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), unordered([ ev("diskcache:saved", "file_js_cache_dyn_importer.mjs", false), ev("diskcache:saved", "file_js_cache_dyn_imported1.mjs", false), @@ -254,11 +174,9 @@ add_task(async function testMemoryCache_dynamicImport() { events: [ ev("load:memorycache", "file_js_cache_dyn_importer.mjs"), ev("evaluate:module", "file_js_cache_dyn_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_dyn_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_dyn_imported3.mjs", false), ev("diskcache:noschedule"), ], }, diff --git a/dom/base/test/browser_script_loader_js_cache_module.js b/dom/base/test/browser_script_loader_js_cache_module.js @@ -101,11 +101,9 @@ add_task(async function testDiskCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:source", "file_js_cache_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_imported1.mjs", false), - ev("load:source", "file_js_cache_imported2.mjs", false), - ev("load:source", "file_js_cache_imported3.mjs", false), - ]), + ev("load:source", "file_js_cache_imported1.mjs", false), + ev("load:source", "file_js_cache_imported2.mjs", false), + ev("load:source", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:disabled", "file_js_cache_importer.mjs"), // non-top-level modules that don't pass the condition @@ -116,11 +114,9 @@ add_task(async function testDiskCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:source", "file_js_cache_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_imported1.mjs", false), - ev("load:source", "file_js_cache_imported2.mjs", false), - ev("load:source", "file_js_cache_imported3.mjs", false), - ]), + ev("load:source", "file_js_cache_imported1.mjs", false), + ev("load:source", "file_js_cache_imported2.mjs", false), + ev("load:source", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:disabled", "file_js_cache_importer.mjs"), ], @@ -129,11 +125,9 @@ add_task(async function testDiskCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:source", "file_js_cache_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_imported1.mjs", false), - ev("load:source", "file_js_cache_imported2.mjs", false), - ev("load:source", "file_js_cache_imported3.mjs", false), - ]), + ev("load:source", "file_js_cache_imported1.mjs", false), + ev("load:source", "file_js_cache_imported2.mjs", false), + ev("load:source", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:disabled", "file_js_cache_importer.mjs"), ], @@ -142,35 +136,27 @@ add_task(async function testDiskCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:source", "file_js_cache_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_imported1.mjs", false), - ev("load:source", "file_js_cache_imported2.mjs", false), - ev("load:source", "file_js_cache_imported3.mjs", false), - ]), + ev("load:source", "file_js_cache_imported1.mjs", false), + ev("load:source", "file_js_cache_imported2.mjs", false), + ev("load:source", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:register", "file_js_cache_importer.mjs"), - unordered([ - ev("diskcache:register", "file_js_cache_imported1.mjs", false), - ev("diskcache:register", "file_js_cache_imported2.mjs", false), - ev("diskcache:register", "file_js_cache_imported3.mjs", false), - ]), + ev("diskcache:register", "file_js_cache_imported1.mjs", false), + ev("diskcache:register", "file_js_cache_imported2.mjs", false), + ev("diskcache:register", "file_js_cache_imported3.mjs", false), ev("diskcache:saved", "file_js_cache_importer.mjs", false), - unordered([ - ev("diskcache:saved", "file_js_cache_imported1.mjs", false), - ev("diskcache:saved", "file_js_cache_imported2.mjs", false), - ev("diskcache:saved", "file_js_cache_imported3.mjs", false), - ]), + ev("diskcache:saved", "file_js_cache_imported1.mjs", false), + ev("diskcache:saved", "file_js_cache_imported2.mjs", false), + ev("diskcache:saved", "file_js_cache_imported3.mjs", false), ], }, { file: "file_js_cache_importer.mjs", events: [ ev("load:diskcache", "file_js_cache_importer.mjs"), - unordered([ - ev("load:diskcache", "file_js_cache_imported1.mjs", false), - ev("load:diskcache", "file_js_cache_imported2.mjs", false), - ev("load:diskcache", "file_js_cache_imported3.mjs", false), - ]), + ev("load:diskcache", "file_js_cache_imported1.mjs", false), + ev("load:diskcache", "file_js_cache_imported2.mjs", false), + ev("load:diskcache", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:disabled", "file_js_cache_importer.mjs"), ], @@ -298,14 +284,12 @@ add_task(async function testMemoryCache_modules() { events: [ ev("load:source", "file_js_cache_importer.mjs"), ev("memorycache:saved", "file_js_cache_importer.mjs"), - unordered([ - ev("load:source", "file_js_cache_imported1.mjs", false), - ev("memorycache:saved", "file_js_cache_imported1.mjs", false), - ev("load:source", "file_js_cache_imported2.mjs", false), - ev("memorycache:saved", "file_js_cache_imported2.mjs", false), - ev("load:source", "file_js_cache_imported3.mjs", false), - ev("memorycache:saved", "file_js_cache_imported3.mjs", false), - ]), + ev("load:source", "file_js_cache_imported1.mjs", false), + ev("memorycache:saved", "file_js_cache_imported1.mjs", false), + ev("load:source", "file_js_cache_imported2.mjs", false), + ev("memorycache:saved", "file_js_cache_imported2.mjs", false), + ev("load:source", "file_js_cache_imported3.mjs", false), + ev("memorycache:saved", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:noschedule"), ], @@ -314,11 +298,9 @@ add_task(async function testMemoryCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:memorycache", "file_js_cache_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:noschedule"), ], @@ -327,11 +309,9 @@ add_task(async function testMemoryCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:memorycache", "file_js_cache_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:noschedule"), ], @@ -340,11 +320,9 @@ add_task(async function testMemoryCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:memorycache", "file_js_cache_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), // SharedScriptCache iterates over unordered hashmap while // saving. @@ -360,11 +338,9 @@ add_task(async function testMemoryCache_modules() { file: "file_js_cache_importer.mjs", events: [ ev("load:memorycache", "file_js_cache_importer.mjs"), - unordered([ - ev("load:memorycache", "file_js_cache_imported1.mjs", false), - ev("load:memorycache", "file_js_cache_imported2.mjs", false), - ev("load:memorycache", "file_js_cache_imported3.mjs", false), - ]), + ev("load:memorycache", "file_js_cache_imported1.mjs", false), + ev("load:memorycache", "file_js_cache_imported2.mjs", false), + ev("load:memorycache", "file_js_cache_imported3.mjs", false), ev("evaluate:module", "file_js_cache_importer.mjs"), ev("diskcache:noschedule"), ], diff --git a/dom/base/test/jsmodules/importmaps/chrome.toml b/dom/base/test/jsmodules/importmaps/chrome.toml @@ -2,25 +2,18 @@ support-files = [ "external_importMap.js", "insert_a_base_element.js", - "module_1979050.mjs", "module_simpleImportMap.mjs", "module_simpleImportMap_dir.mjs", "module_simpleImportMap_remap.mjs", "module_simpleImportMap_remap_https.mjs", "module_simpleExport.mjs", "module_sortedImportMap.mjs", - "scope1/module_1979050.mjs", "scope1/module_simpleExport.mjs", "scope1/module_simpleImportMap.mjs", - "scope1/scope2/module_1979050.mjs", "scope1/scope2/module_simpleExport.mjs", "scope1/scope2/module_simpleImportMap.mjs", ] -["test_1979050.html"] - -["test_1979050_2.html"] - ["test_dynamic_import_reject_importMap.html"] ["test_externalImportMap.html"] diff --git a/dom/base/test/jsmodules/importmaps/module_1979050.mjs b/dom/base/test/jsmodules/importmaps/module_1979050.mjs @@ -1,3 +0,0 @@ -import { x } from "./module_simpleExport.mjs"; - -result = x; diff --git a/dom/base/test/jsmodules/importmaps/scope1/module_1979050.mjs b/dom/base/test/jsmodules/importmaps/scope1/module_1979050.mjs @@ -1,5 +0,0 @@ -import { x } from "./module_simpleExport.mjs"; - -result2 = x; - -export let y = x; diff --git a/dom/base/test/jsmodules/importmaps/scope1/scope2/module_1979050.mjs b/dom/base/test/jsmodules/importmaps/scope1/scope2/module_1979050.mjs @@ -1,5 +0,0 @@ -import { x } from "./module_simpleExport.mjs"; - -result3 = x; - -export let z = x; diff --git a/dom/base/test/jsmodules/importmaps/test_1979050.html b/dom/base/test/jsmodules/importmaps/test_1979050.html @@ -1,50 +0,0 @@ -<!DOCTYPE html> -<meta charset=utf-8> -<title>Test bug 1979050</title> -<script> -Object.prototype.imports = { - "./module_simpleExport.mjs": "./scope1/module_simpleExport.mjs", -}; -Object.prototype.scopes = { - "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/": { - "./scope1/module_simpleExport.mjs": "/content/chrome/dom/base/test/jsmodules/importmaps/module_simpleExport.mjs" - } -}; -Object.prototype.integrity = { - "./scope1/scope2/module_simpleExport.mjs": "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" -}; - -</script> -<script type="importmap"> -{} -</script> - -<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> - -<script type="module" src="module_1979050.mjs"></script> -<script type="module" src="./scope1/module_1979050.mjs"></script> -<script type="module" src="./scope1/scope2/module_1979050.mjs"></script> -<script> - var result, result2, result3; - - SimpleTest.waitForExplicitFinish(); - - // eslint-disable-next-line no-unused-vars - function testLoaded() { - ok(result == 42, 'Check static imported value result: ' + result); - ok(result2 == 84, 'Check static imported value result2: ' + result2); - ok(result3 == 126, 'Check static imported value result3: ' + result3); - - import("./module_simpleExport.mjs").then((ns) => { - ok(ns.x == 42, 'Check dynamic imported value result: ' + ns.x); - return import("./scope1/module_1979050.mjs"); - }).then((ns) => { - ok(ns.y == 84, 'Check dynamic imported value ns.y: ' + ns.y); - return import("./scope1/scope2/module_1979050.mjs"); - }).then((ns) => { - ok(ns.z == 126, 'Check dynamic imported value ns.z: ' + ns.z); - SimpleTest.finish(); - }); - } -</script> -<body onload='testLoaded()'></body> diff --git a/dom/base/test/jsmodules/importmaps/test_1979050_2.html b/dom/base/test/jsmodules/importmaps/test_1979050_2.html @@ -1,69 +0,0 @@ -<!DOCTYPE html> -<meta charset=utf-8> -<title>Test bug 1979050</title> -<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> -<script> -Object.defineProperty(Object.prototype, "imports", - { - get() { - ok(false, "imports getter"); - return { - "./module_simpleExport.mjs": "./scope1/module_simpleExport.mjs", - }; - } - }); - -Object.defineProperty(Object.prototype, "scopes", - { - get() { - ok(false, "scopes getter"); - return { - "chrome://mochitests/content/chrome/dom/base/test/jsmodules/importmaps/scope1/": { - "./scope1/module_simpleExport.mjs": "/content/chrome/dom/base/test/jsmodules/importmaps/module_simpleExport.mjs" - } - }; - } - }); - -Object.defineProperty(Object.prototype, "integrity ", - { - get() { - ok(false, "scopes getter"); - return { - "./scope1/scope2/module_simpleExport.mjs": "sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC" - }; - } - }); - -</script> -<script type="importmap"> -{} -</script> - -<script type="module" src="module_1979050.mjs"></script> -<script type="module" src="./scope1/module_1979050.mjs"></script> -<script type="module" src="./scope1/scope2/module_1979050.mjs"></script> -<script> - var result, result2, result3; - - SimpleTest.waitForExplicitFinish(); - - // eslint-disable-next-line no-unused-vars - function testLoaded() { - ok(result == 42, 'Check static imported value result: ' + result); - ok(result2 == 84, 'Check static imported value result2: ' + result2); - ok(result3 == 126, 'Check static imported value result3: ' + result3); - - import("./module_simpleExport.mjs").then((ns) => { - ok(ns.x == 42, 'Check dynamic imported value result: ' + ns.x); - return import("./scope1/module_1979050.mjs"); - }).then((ns) => { - ok(ns.y == 84, 'Check dynamic imported value ns.y: ' + ns.y); - return import("./scope1/scope2/module_1979050.mjs"); - }).then((ns) => { - ok(ns.z == 126, 'Check dynamic imported value ns.z: ' + ns.z); - SimpleTest.finish(); - }); - } -</script> -<body onload='testLoaded()'></body> diff --git a/dom/base/use_counter_metrics.yaml b/dom/base/use_counter_metrics.yaml @@ -15642,57 +15642,6 @@ use.counter.deprecated_ops.page: send_in_pings: - use-counters - math_ml__deprecated_mo_explicit_accent: - type: counter - description: > - Whether a page used MathML_DeprecatedMoExplicitAccent. - Compare against `use.counter.top_level_content_documents_destroyed` - to calculate the rate. - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - notification_emails: - - dom-core@mozilla.com - - emilio@mozilla.com - expires: never - send_in_pings: - - use-counters - - math_ml__deprecated_mover_non_explicit_accent: - type: counter - description: > - Whether a page used MathML_DeprecatedMoverNonExplicitAccent. - Compare against `use.counter.top_level_content_documents_destroyed` - to calculate the rate. - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - notification_emails: - - dom-core@mozilla.com - - emilio@mozilla.com - expires: never - send_in_pings: - - use-counters - - math_ml__deprecated_munder_non_explicit_accentunder: - type: counter - description: > - Whether a page used MathML_DeprecatedMunderNonExplicitAccentunder. - Compare against `use.counter.top_level_content_documents_destroyed` - to calculate the rate. - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - notification_emails: - - dom-core@mozilla.com - - emilio@mozilla.com - expires: never - send_in_pings: - - use-counters - form_submission_untrusted_event: type: counter description: > @@ -16460,57 +16409,6 @@ use.counter.deprecated_ops.doc: send_in_pings: - use-counters - math_ml__deprecated_mo_explicit_accent: - type: counter - description: > - Whether a document used MathML_DeprecateMoExplicitAccent. - Compare against `use.counter.content_documents_destroyed` - to calculate the rate. - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - notification_emails: - - dom-core@mozilla.com - - emilio@mozilla.com - expires: never - send_in_pings: - - use-counters - - math_ml__deprecated_mover_non_explicit_accent: - type: counter - description: > - Whether a document used MathML_DeprecatedMoverNonExplicitAccent. - Compare against `use.counter.content_documents_destroyed` - to calculate the rate. - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - notification_emails: - - dom-core@mozilla.com - - emilio@mozilla.com - expires: never - send_in_pings: - - use-counters - - math_ml__deprecated_munder_non_explicit_accentunder: - type: counter - description: > - Whether a document used MathML_DeprecatedMunderNonExplicitAccentunder. - Compare against `use.counter.content_documents_destroyed` - to calculate the rate. - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1852098 - notification_emails: - - dom-core@mozilla.com - - emilio@mozilla.com - expires: never - send_in_pings: - - use-counters - form_submission_untrusted_event: type: counter description: > diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp @@ -1126,7 +1126,7 @@ void BrowserParent::UpdateDimensions(const LayoutDeviceIntRect& rect, LayoutDeviceIntPoint clientOffset = GetClientOffset(); LayoutDeviceIntPoint chromeOffset = !GetBrowserBridgeParent() - ? GetChildProcessOffset() + ? -GetChildProcessOffset() : LayoutDeviceIntPoint(); if (!mUpdatedDimensions || mDimensions != size || !mRect.IsEqualEdges(rect) || @@ -2717,7 +2717,7 @@ BrowserParent::GetChildToParentConversionMatrix() { if (mChildToParentConversionMatrix) { return *mChildToParentConversionMatrix; } - LayoutDevicePoint offset(GetChildProcessOffset()); + LayoutDevicePoint offset(-GetChildProcessOffset()); return LayoutDeviceToLayoutDeviceMatrix4x4::Translation(offset); } @@ -2741,20 +2741,34 @@ void BrowserParent::SetChildToParentConversionMatrix( LayoutDeviceIntPoint BrowserParent::GetChildProcessOffset() { // The "toplevel widget" in child processes is always at position // 0,0. Map the event coordinates to match that. + + LayoutDeviceIntPoint offset(0, 0); RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); if (!frameLoader) { - return {}; + return offset; } nsIFrame* targetFrame = frameLoader->GetPrimaryFrameOfOwningContent(); if (!targetFrame) { - return {}; + return offset; } nsCOMPtr<nsIWidget> widget = GetWidget(); if (!widget) { - return {}; + return offset; + } + + nsPresContext* presContext = targetFrame->PresContext(); + nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame(); + nsView* rootView = rootFrame ? rootFrame->GetView() : nullptr; + if (!rootView) { + return offset; } + // Note that we don't want to take into account transforms here: +#if 0 + nsPoint pt(0, 0); + nsLayoutUtils::TransformPoint(targetFrame, rootFrame, pt); +#endif // In practice, when transforms are applied to this frameLoader, we currently // get the wrong results whether we take transforms into account here or not. // But applying transforms here gives us the wrong results in all @@ -2766,13 +2780,15 @@ LayoutDeviceIntPoint BrowserParent::GetChildProcessOffset() { // What we actually need to do is apply the transforms to the coordinates of // any events we send to the child, and reverse them for any screen // coordinates that we retrieve from the child. - auto point = nsLayoutUtils::FrameToWidgetOffset(targetFrame, widget); - if (!point) { - return {}; - } - nsPresContext* pc = targetFrame->PresContext(); - return LayoutDeviceIntPoint::FromAppUnitsRounded(*point, - pc->AppUnitsPerDevPixel()); + + // TODO: Once we take into account transforms here, set viewportType + // correctly. For now we use Visual as this means we don't apply + // the layout-to-visual transform in TranslateViewToWidget(). + ViewportType viewportType = ViewportType::Visual; + + nsPoint pt = targetFrame->GetOffsetTo(rootFrame); + return -nsLayoutUtils::TranslateViewToWidget(presContext, rootView, pt, + viewportType, widget); } LayoutDeviceIntPoint BrowserParent::GetClientOffset() { diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties @@ -404,12 +404,6 @@ InitNSMouseEventWarning=initNSMouseEvent() is deprecated. Use the MouseEvent() c MathML_DeprecatedMathSpaceValue2Warning=MathML length value “%S” is deprecated and will be removed at a future date. # LOCALIZATION NOTE: Do not translate mathvariant or MathML. %S is the deprecated value of the mathvariant attribute. MathML_DeprecatedMathVariantWarning=“mathvariant='%S'” on MathML elements is deprecated and will be removed at a future date. -# LOCALIZATION NOTE: Do not translate accent and mo (it's the name of a MathML element). %1$S is either accent or accentunder. %2$S is the name of the parent frame. -MathML_DeprecatedMoExplicitAccentWarning=Setting the accent attribute on mo elements is deprecated. Use the “%1$S” attribute on “%2$S” instead. -# LOCALIZATION NOTE: Do not translate accent. %S is either mover or munderover. -MathML_DeprecatedMoverNonExplicitAccentWarning=Inferring the accent property from the core operator is deprecated. Consider adding an explicit accent attribute to “%S”. -# LOCALIZATION NOTE: Do not translate accentunder. %S is either munder or munderover. -MathML_DeprecatedMunderNonExplicitAccentunderWarning=Inferring the accentunder property from the core operator is deprecated. Consider adding an explicit accentunder attribute to “%S”. FormSubmissionUntrustedEventWarning=Form submission via untrusted submit event is deprecated and will be removed at a future date. WebShareAPI_Failed=The share operation has failed. diff --git a/dom/media/AudioStreamTrack.cpp b/dom/media/AudioStreamTrack.cpp @@ -40,8 +40,9 @@ void AudioStreamTrack::GetLabel(nsAString& aLabel, CallerType aCallerType) { MediaStreamTrack::GetLabel(aLabel, aCallerType); } -already_AddRefed<MediaStreamTrack> AudioStreamTrack::Clone() { - return MediaStreamTrack::CloneInternal<AudioStreamTrack>(); +already_AddRefed<MediaStreamTrack> AudioStreamTrack::CloneInternal() { + return do_AddRef(new AudioStreamTrack(mWindow, mInputTrack, mSource, + ReadyState(), Muted(), mConstraints)); } } // namespace mozilla::dom diff --git a/dom/media/AudioStreamTrack.h b/dom/media/AudioStreamTrack.h @@ -24,8 +24,6 @@ class AudioStreamTrack : public MediaStreamTrack { : MediaStreamTrack(aWindow, aInputTrack, aSource, aReadyState, aMuted, aConstraints) {} - already_AddRefed<MediaStreamTrack> Clone() override; - AudioStreamTrack* AsAudioStreamTrack() override { return this; } const AudioStreamTrack* AsAudioStreamTrack() const override { return this; } @@ -40,6 +38,9 @@ class AudioStreamTrack : public MediaStreamTrack { void GetKind(nsAString& aKind) override { aKind.AssignLiteral("audio"); } void GetLabel(nsAString& aLabel, CallerType aCallerType) override; + + protected: + already_AddRefed<MediaStreamTrack> CloneInternal() override; }; } // namespace mozilla::dom diff --git a/dom/media/CubebInputStream.cpp b/dom/media/CubebInputStream.cpp @@ -112,17 +112,18 @@ UniquePtr<CubebInputStream> CubebInputStream::Create(cubeb_devid aDeviceId, LOG("Create a cubeb stream %p successfully", inputStream.get()); - UniquePtr<CubebInputStream> stream(new CubebInputStream( - listener.forget(), handle.forget(), std::move(inputStream))); + UniquePtr<CubebInputStream> stream( + new CubebInputStream(listener.forget(), std::move(inputStream))); stream->Init(); return stream; } CubebInputStream::CubebInputStream( already_AddRefed<Listener>&& aListener, - already_AddRefed<CubebUtils::CubebHandle>&& aCubeb, UniquePtr<cubeb_stream, CubebDestroyPolicy>&& aStream) - : mListener(aListener), mCubeb(aCubeb), mStream(std::move(aStream)) { + : mListener(aListener), + mCubeb(CubebUtils::GetCubeb()), + mStream(std::move(aStream)) { MOZ_ASSERT(mListener); MOZ_ASSERT(mStream); } diff --git a/dom/media/CubebInputStream.h b/dom/media/CubebInputStream.h @@ -61,7 +61,6 @@ class CubebInputStream final { void operator()(cubeb_stream* aStream) const; }; CubebInputStream(already_AddRefed<Listener>&& aListener, - already_AddRefed<CubebUtils::CubebHandle>&& aCubeb, UniquePtr<cubeb_stream, CubebDestroyPolicy>&& aStream); void Init(); diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp @@ -3493,12 +3493,12 @@ void MediaDecoderStateMachine::AudioAudibleChanged(bool aAudible) { MediaSink* MediaDecoderStateMachine::CreateAudioSink() { if (mOutputCaptureState != MediaDecoder::OutputCaptureState::None) { DecodedStream* stream = new DecodedStream( - OwnerThread(), + this, mOutputCaptureState == MediaDecoder::OutputCaptureState::Capture ? mOutputDummyTrack.Ref() : nullptr, - mOutputTracks, CanonicalOutputPrincipal(), mVolume, mPlaybackRate, - mPreservesPitch, mAudioQueue, mVideoQueue); + mOutputTracks, mVolume, mPlaybackRate, mPreservesPitch, mAudioQueue, + mVideoQueue, mSinkDevice.Ref()); mAudibleListener.DisconnectIfExists(); mAudibleListener = stream->AudibleEvent().Connect( OwnerThread(), this, &MediaDecoderStateMachine::AudioAudibleChanged); diff --git a/dom/media/MediaManager.cpp b/dom/media/MediaManager.cpp @@ -210,11 +210,6 @@ struct DeviceState { MOZ_ASSERT(mTrackSource); } - // true if we have allocated mDevice. When not allocated, we may not stop or - // deallocate. - // MainThread only. - bool mAllocated = false; - // true if we have stopped mDevice, this is a terminal state. // MainThread only. bool mStopped = false; @@ -394,29 +389,13 @@ class DeviceListener : public SupportsWeakPtr { * Marks this listener as active and creates the internal device state. */ void Activate(RefPtr<LocalMediaDevice> aDevice, - RefPtr<LocalTrackSource> aTrackSource, bool aStartMuted, - bool aIsAllocated); + RefPtr<LocalTrackSource> aTrackSource, bool aStartMuted); /** * Posts a task to initialize and start the associated device. */ RefPtr<DeviceListenerPromise> InitializeAsync(); - private: - /** - * Initializes synchronously. Must be called on the media thread. - */ - nsresult Initialize(PrincipalHandle aPrincipal, LocalMediaDevice* aDevice, - MediaTrack* aTrack, bool aStartDevice); - - public: - /** - * Synchronously clones this device listener, setting up the device to match - * our current device state asynchronously. Settings, constraints and other - * main thread state starts applying immediately. - */ - already_AddRefed<DeviceListener> Clone() const; - /** * Posts a task to stop the device associated with this DeviceListener and * notifies the associated window listener that a track was stopped. @@ -487,10 +466,6 @@ class DeviceListener : public SupportsWeakPtr { return mDeviceState ? mDeviceState->mDevice.get() : nullptr; } - LocalTrackSource* GetTrackSource() const { - return mDeviceState ? mDeviceState->mTrackSource.get() : nullptr; - } - bool Activated() const { return static_cast<bool>(mDeviceState); } bool Stopped() const { return mStopped; } @@ -588,7 +563,7 @@ class GetUserMediaWindowListener { */ void Activate(RefPtr<DeviceListener> aListener, RefPtr<LocalMediaDevice> aDevice, - RefPtr<LocalTrackSource> aTrackSource, bool aIsAllocated) { + RefPtr<LocalTrackSource> aTrackSource) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); MOZ_ASSERT(!aListener->Activated()); @@ -606,8 +581,7 @@ class GetUserMediaWindowListener { } mInactiveListeners.RemoveElement(aListener); - aListener->Activate(std::move(aDevice), std::move(aTrackSource), muted, - aIsAllocated); + aListener->Activate(std::move(aDevice), std::move(aTrackSource), muted); mActiveListeners.AppendElement(std::move(aListener)); } @@ -834,7 +808,7 @@ class LocalTrackSource : public MediaStreamTrackSource { LocalTrackSource(nsIPrincipal* aPrincipal, const nsString& aLabel, const RefPtr<DeviceListener>& aListener, MediaSourceEnum aSource, MediaTrack* aTrack, - RefPtr<const PeerIdentity> aPeerIdentity, + RefPtr<PeerIdentity> aPeerIdentity, TrackingId aTrackingId = TrackingId()) : MediaStreamTrackSource(aPrincipal, aLabel, std::move(aTrackingId)), mSource(aSource), @@ -888,20 +862,6 @@ class LocalTrackSource : public MediaStreamTrackSource { } } - CloneResult Clone() override { - if (!mListener) { - return {}; - } - RefPtr listener = mListener->Clone(); - MOZ_ASSERT(listener); - if (!listener) { - return {}; - } - - return {.mSource = listener->GetTrackSource(), - .mInputTrack = listener->GetTrackSource()->mTrack}; - } - void Disable() override { if (mListener) { mListener->SetDeviceEnabled(false); @@ -1191,11 +1151,6 @@ const TrackingId& LocalMediaDevice::GetTrackingId() const { return mSource->GetTrackingId(); } -const dom::MediaTrackConstraints& LocalMediaDevice::Constraints() const { - MOZ_ASSERT(MediaManager::IsInMediaThread()); - return mConstraints; -} - // Threadsafe since mKind and mSource are const. NS_IMETHODIMP LocalMediaDevice::GetMediaSource(nsAString& aMediaSource) { @@ -1220,12 +1175,7 @@ nsresult LocalMediaDevice::Allocate(const MediaTrackConstraints& aConstraints, return NS_ERROR_FAILURE; } - nsresult rv = - Source()->Allocate(aConstraints, aPrefs, aWindowID, aOutBadConstraint); - if (NS_SUCCEEDED(rv)) { - mConstraints = aConstraints; - } - return rv; + return Source()->Allocate(aConstraints, aPrefs, aWindowID, aOutBadConstraint); } void LocalMediaDevice::SetTrack(const RefPtr<MediaTrack>& aTrack, @@ -1269,11 +1219,7 @@ nsresult LocalMediaDevice::Reconfigure( } } } - nsresult rv = Source()->Reconfigure(aConstraints, aPrefs, aOutBadConstraint); - if (NS_SUCCEEDED(rv)) { - mConstraints = aConstraints; - } - return rv; + return Source()->Reconfigure(aConstraints, aPrefs, aOutBadConstraint); } nsresult LocalMediaDevice::FocusOnSelectedSource() { @@ -1293,21 +1239,6 @@ nsresult LocalMediaDevice::Deallocate() { return mSource->Deallocate(); } -already_AddRefed<LocalMediaDevice> LocalMediaDevice::Clone() const { - MOZ_ASSERT(NS_IsMainThread()); - auto device = MakeRefPtr<LocalMediaDevice>(mRawDevice, mID, mGroupID, mName); - device->mSource = - mRawDevice->mEngine->CreateSourceFrom(mSource, device->mRawDevice); -#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED - // The source is normally created on the MediaManager thread. But for cloning, - // it ends up being created on main thread. Make sure its owning event target - // is set properly. - auto* src = device->Source(); - src->_mOwningThread = mSource->_mOwningThread; -#endif - return device.forget(); -} - MediaSourceEnum MediaDevice::GetMediaSource() const { return mMediaSource; } static const MediaTrackConstraints& GetInvariant( @@ -1835,13 +1766,11 @@ void GetUserMediaStreamTask::PrepareDOMStream() { // is freed when the page is invalidated (on navigation or close). if (mAudioDeviceListener) { mWindowListener->Activate(mAudioDeviceListener, mAudioDevice, - std::move(audioTrackSource), - /*aIsAllocated=*/true); + std::move(audioTrackSource)); } if (mVideoDeviceListener) { mWindowListener->Activate(mVideoDeviceListener, mVideoDevice, - std::move(videoTrackSource), - /*aIsAllocated=*/true); + std::move(videoTrackSource)); } // Dispatch to the media thread to ask it to start the sources, because that @@ -4326,7 +4255,7 @@ void DeviceListener::Register(GetUserMediaWindowListener* aListener) { void DeviceListener::Activate(RefPtr<LocalMediaDevice> aDevice, RefPtr<LocalTrackSource> aTrackSource, - bool aStartMuted, bool aIsAllocated) { + bool aStartMuted) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); LOG("DeviceListener %p activating %s device %p", this, @@ -4352,7 +4281,6 @@ void DeviceListener::Activate(RefPtr<LocalMediaDevice> aDevice, mDeviceState = MakeUnique<DeviceState>( std::move(aDevice), std::move(aTrackSource), offWhileDisabled); mDeviceState->mDeviceMuted = aStartMuted; - mDeviceState->mAllocated = aIsAllocated; if (aStartMuted) { mDeviceState->mTrackSource->Mute(); } @@ -4363,24 +4291,51 @@ DeviceListener::InitializeAsync() { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); MOZ_DIAGNOSTIC_ASSERT(!mStopped); - return InvokeAsync( - MediaManager::Get()->mMediaThread, __func__, - [this, self = RefPtr(this), principal = GetPrincipalHandle(), - device = mDeviceState->mDevice, + return MediaManager::Dispatch<DeviceListenerPromise>( + __func__, + [principal = GetPrincipalHandle(), device = mDeviceState->mDevice, track = mDeviceState->mTrackSource->mTrack, - deviceMuted = mDeviceState->mDeviceMuted] { - nsresult rv = Initialize(principal, device, track, - /*aStartDevice=*/!deviceMuted); - if (NS_SUCCEEDED(rv)) { - return GenericPromise::CreateAndResolve( - true, "DeviceListener::InitializeAsync success"); + deviceMuted = mDeviceState->mDeviceMuted]( + MozPromiseHolder<DeviceListenerPromise>& aHolder) { + auto kind = device->Kind(); + device->SetTrack(track, principal); + nsresult rv = deviceMuted ? NS_OK : device->Start(); + if (kind == MediaDeviceKind::Audioinput || + kind == MediaDeviceKind::Videoinput) { + if ((rv == NS_ERROR_NOT_AVAILABLE && + kind == MediaDeviceKind::Audioinput) || + (NS_FAILED(rv) && kind == MediaDeviceKind::Videoinput)) { + PR_Sleep(200); + rv = device->Start(); + } + if (rv == NS_ERROR_NOT_AVAILABLE && + kind == MediaDeviceKind::Audioinput) { + nsCString log; + log.AssignLiteral("Concurrent mic process limit."); + aHolder.Reject(MakeRefPtr<MediaMgrError>( + MediaMgrError::Name::NotReadableError, + std::move(log)), + __func__); + return; + } + } + if (NS_FAILED(rv)) { + nsCString log; + log.AppendPrintf("Starting %s failed", + dom::GetEnumString(kind).get()); + aHolder.Reject( + MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, + std::move(log)), + __func__); + return; } - return GenericPromise::CreateAndReject( - rv, "DeviceListener::InitializeAsync failure"); + LOG("started %s device %p", dom::GetEnumString(kind).get(), + device.get()); + aHolder.Resolve(true, __func__); }) ->Then( GetMainThreadSerialEventTarget(), __func__, - [self = RefPtr<DeviceListener>(this), this](bool) { + [self = RefPtr<DeviceListener>(this), this]() { if (mStopped) { // We were shut down during the async init return DeviceListenerPromise::CreateAndResolve(true, __func__); @@ -4395,25 +4350,10 @@ DeviceListener::InitializeAsync() { mDeviceState->mTrackEnabledTime = TimeStamp::Now(); return DeviceListenerPromise::CreateAndResolve(true, __func__); }, - [self = RefPtr<DeviceListener>(this), this](nsresult aRv) { - auto kind = mDeviceState->mDevice->Kind(); - RefPtr<MediaMgrError> err; - if (aRv == NS_ERROR_NOT_AVAILABLE && - kind == MediaDeviceKind::Audioinput) { - nsCString log; - log.AssignLiteral("Concurrent mic process limit."); - err = MakeRefPtr<MediaMgrError>( - MediaMgrError::Name::NotReadableError, std::move(log)); - } else if (NS_FAILED(aRv)) { - nsCString log; - log.AppendPrintf("Starting %s failed", - dom::GetEnumString(kind).get()); - err = MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError, - std::move(log)); - } - + [self = RefPtr<DeviceListener>(this), + this](const RefPtr<MediaMgrError>& aResult) { if (mStopped) { - return DeviceListenerPromise::CreateAndReject(err, __func__); + return DeviceListenerPromise::CreateAndReject(aResult, __func__); } MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mTrackEnabled); @@ -4421,150 +4361,10 @@ DeviceListener::InitializeAsync() { MOZ_DIAGNOSTIC_ASSERT(!mDeviceState->mStopped); Stop(); - - return DeviceListenerPromise::CreateAndReject(err, __func__); + return DeviceListenerPromise::CreateAndReject(aResult, __func__); }); } -nsresult DeviceListener::Initialize(PrincipalHandle aPrincipal, - LocalMediaDevice* aDevice, - MediaTrack* aTrack, bool aStartDevice) { - MOZ_ASSERT(MediaManager::IsInMediaThread()); - - auto kind = aDevice->Kind(); - aDevice->SetTrack(aTrack, aPrincipal); - nsresult rv = aStartDevice ? aDevice->Start() : NS_OK; - if (kind == MediaDeviceKind::Audioinput || - kind == MediaDeviceKind::Videoinput) { - if ((rv == NS_ERROR_NOT_AVAILABLE && kind == MediaDeviceKind::Audioinput) || - (NS_FAILED(rv) && kind == MediaDeviceKind::Videoinput)) { - PR_Sleep(200); - rv = aDevice->Start(); - } - } - LOG("started %s device %p", dom::GetEnumString(kind).get(), aDevice); - return rv; -} - -already_AddRefed<DeviceListener> DeviceListener::Clone() const { - MOZ_ASSERT(NS_IsMainThread()); - MediaManager* mgr = MediaManager::GetIfExists(); - if (!mgr) { - return nullptr; - } - if (!mWindowListener) { - return nullptr; - } - auto* thisDevice = GetDevice(); - if (!thisDevice) { - return nullptr; - } - - auto* thisTrackSource = GetTrackSource(); - if (!thisTrackSource) { - return nullptr; - } - - // See PrepareDOMStream for how a gUM/gDM track is created. - RefPtr<MediaTrack> track; - MediaTrackGraph* mtg = thisTrackSource->mTrack->Graph(); - if (const auto source = thisDevice->GetMediaSource(); - source == dom::MediaSourceEnum::Microphone) { -#ifdef MOZ_WEBRTC - if (thisDevice->IsFake()) { - track = mtg->CreateSourceTrack(MediaSegment::AUDIO); - } else { - track = AudioProcessingTrack::Create(mtg); - track->Suspend(); // Microphone source resumes in SetTrack - } -#else - track = mtg->CreateSourceTrack(MediaSegment::AUDIO); -#endif - } else if (source == dom::MediaSourceEnum::Camera || - source == dom::MediaSourceEnum::Screen || - source == dom::MediaSourceEnum::Window || - source == dom::MediaSourceEnum::Browser) { - track = mtg->CreateSourceTrack(MediaSegment::VIDEO); - } - - if (!track) { - return nullptr; - } - - RefPtr device = thisDevice->Clone(); - auto listener = MakeRefPtr<DeviceListener>(); - auto trackSource = MakeRefPtr<LocalTrackSource>( - thisTrackSource->GetPrincipal(), thisTrackSource->mLabel, listener, - thisTrackSource->mSource, track, thisTrackSource->mPeerIdentity, - thisTrackSource->mTrackingId); - - LOG("DeviceListener %p registering clone", this); - mWindowListener->Register(listener); - LOG("DeviceListener %p activating clone", this); - mWindowListener->Activate(listener, device, trackSource, - /*aIsAllocated=*/false); - - listener->mDeviceState->mDeviceEnabled = mDeviceState->mDeviceEnabled; - listener->mDeviceState->mDeviceMuted = mDeviceState->mDeviceMuted; - listener->mDeviceState->mTrackEnabled = mDeviceState->mTrackEnabled; - listener->mDeviceState->mTrackEnabledTime = TimeStamp::Now(); - - // We have to do an async operation here, even though Clone() is sync. - // This is fine because JS will not be able to trigger any operation to run - // async on the media thread. - LOG("DeviceListener %p allocating clone device %p async", this, device.get()); - InvokeAsync( - mgr->mMediaThread, __func__, - [thisDevice = RefPtr(thisDevice), device, prefs = mgr->mPrefs, - windowId = mWindowListener->WindowID(), listener, - principal = GetPrincipalHandle(), track, - startDevice = !listener->mDeviceState->mDeviceMuted && - listener->mDeviceState->mDeviceEnabled] { - const char* outBadConstraint{}; - nsresult rv = device->Source()->Allocate( - thisDevice->Constraints(), prefs, windowId, &outBadConstraint); - LOG("Allocated clone device %p. rv=%s", device.get(), - GetStaticErrorName(rv)); - if (NS_FAILED(rv)) { - return GenericPromise::CreateAndReject( - rv, "DeviceListener::Clone failure #1"); - } - rv = listener->Initialize(principal, device, track, startDevice); - if (NS_SUCCEEDED(rv)) { - return GenericPromise::CreateAndResolve( - true, "DeviceListener::Clone success"); - } - return GenericPromise::CreateAndReject( - rv, "DeviceListener::Clone failure #2"); - }) - ->Then(GetMainThreadSerialEventTarget(), __func__, - [listener, device, - trackSource](GenericPromise::ResolveOrRejectValue&& aValue) { - if (aValue.IsReject()) { - // Allocating/initializing failed. Stopping the device listener - // will destroy the MediaStreamTrackSource's MediaTrack, which - // will make the MediaStreamTrack's mTrack MediaTrack auto-end - // due to lack of inputs. This makes the MediaStreamTrack's - // readyState transition to "ended" as expected. - LOG("Allocating clone device %p failed. Stopping.", - device.get()); - listener->Stop(); - return; - } - listener->mDeviceState->mAllocated = true; - if (listener->mDeviceState->mStopped) { - MediaManager::Dispatch(NS_NewRunnableFunction( - "DeviceListener::Clone::Stop", - [device = listener->mDeviceState->mDevice]() { - device->Stop(); - device->Deallocate(); - })); - } - }); - - return listener.forget(); -} - void DeviceListener::Stop() { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); @@ -4586,12 +4386,10 @@ void DeviceListener::Stop() { mDeviceState->mTrackSource->Stop(); - if (mDeviceState->mAllocated) { - MediaManager::Dispatch(NewTaskFrom([device = mDeviceState->mDevice]() { - device->Stop(); - device->Deallocate(); - })); - } + MediaManager::Dispatch(NewTaskFrom([device = mDeviceState->mDevice]() { + device->Stop(); + device->Deallocate(); + })); mWindowListener->ChromeAffectingStateChanged(); } diff --git a/dom/media/MediaManager.h b/dom/media/MediaManager.h @@ -148,11 +148,6 @@ class LocalMediaDevice final : public nsIMediaDevice { nsresult Stop(); nsresult Deallocate(); - /** - * Clones the LocalMediaDevice and sets a cloned source. - */ - already_AddRefed<LocalMediaDevice> Clone() const; - void GetSettings(dom::MediaTrackSettings& aOutSettings); void GetCapabilities(dom::MediaTrackCapabilities& aOutCapabilities); MediaEngineSource* Source(); @@ -167,7 +162,6 @@ class LocalMediaDevice final : public nsIMediaDevice { dom::MediaDeviceKind Kind() const { return mRawDevice->mKind; } bool IsFake() const { return mRawDevice->mIsFake; } const nsString& RawID() { return mRawDevice->mRawID; } - const dom::MediaTrackConstraints& Constraints() const; private: virtual ~LocalMediaDevice() = default; @@ -190,8 +184,6 @@ class LocalMediaDevice final : public nsIMediaDevice { private: RefPtr<MediaEngineSource> mSource; - // Currently applied constraints. Media thread only. - dom::MediaTrackConstraints mConstraints; }; typedef nsRefPtrHashtable<nsUint64HashKey, GetUserMediaWindowListener> diff --git a/dom/media/MediaStreamTrack.cpp b/dom/media/MediaStreamTrack.cpp @@ -46,8 +46,6 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaStreamTrackSource) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END -auto MediaStreamTrackSource::Clone() -> CloneResult { return {}; } - auto MediaStreamTrackSource::ApplyConstraints( const dom::MediaTrackConstraints& aConstraints, CallerType aCallerType) -> RefPtr<ApplyConstraintsPromise> { @@ -541,6 +539,13 @@ void MediaStreamTrack::RemoveConsumer(MediaStreamTrackConsumer* aConsumer) { } } +already_AddRefed<MediaStreamTrack> MediaStreamTrack::Clone() { + RefPtr<MediaStreamTrack> newTrack = CloneInternal(); + newTrack->SetEnabled(Enabled()); + newTrack->SetMuted(Muted()); + return newTrack.forget(); +} + void MediaStreamTrack::SetReadyState(MediaStreamTrackState aState) { MOZ_ASSERT(!(mReadyState == MediaStreamTrackState::Ended && aState == MediaStreamTrackState::Live), diff --git a/dom/media/MediaStreamTrack.h b/dom/media/MediaStreamTrack.h @@ -128,18 +128,6 @@ class MediaStreamTrackSource : public nsISupports { */ virtual void Destroy() {} - struct CloneResult { - RefPtr<MediaStreamTrackSource> mSource; - RefPtr<mozilla::MediaTrack> mInputTrack; - }; - - /** - * Clone this MediaStreamTrackSource. Cloned sources allow independent track - * settings. Not supported by all source types. A source not supporting - * cloning returns nullptr. - */ - virtual CloneResult Clone(); - /** * Gets the source's MediaSourceEnum for usage by PeerConnections. */ @@ -481,7 +469,7 @@ class MediaStreamTrack : public DOMEventTargetHelper, public SupportsWeakPtr { already_AddRefed<Promise> ApplyConstraints( const dom::MediaTrackConstraints& aConstraints, CallerType aCallerType, ErrorResult& aRv); - virtual already_AddRefed<MediaStreamTrack> Clone() = 0; + already_AddRefed<MediaStreamTrack> Clone(); MediaStreamTrackState ReadyState() { return mReadyState; } IMPL_EVENT_HANDLER(mute) @@ -654,22 +642,7 @@ class MediaStreamTrack : public DOMEventTargetHelper, public SupportsWeakPtr { * Creates a new MediaStreamTrack with the same kind, input track, input * track ID and source as this MediaStreamTrack. */ - - template <typename TrackType> - already_AddRefed<MediaStreamTrack> CloneInternal() { - auto cloneRes = mSource->Clone(); - MOZ_ASSERT(!!cloneRes.mSource == !!cloneRes.mInputTrack); - if (!cloneRes.mSource || !cloneRes.mInputTrack) { - cloneRes.mSource = mSource; - cloneRes.mInputTrack = mInputTrack; - } - auto newTrack = - MakeRefPtr<TrackType>(mWindow, cloneRes.mInputTrack, cloneRes.mSource, - ReadyState(), Muted(), mConstraints); - newTrack->SetEnabled(Enabled()); - newTrack->SetMuted(Muted()); - return newTrack.forget(); - } + virtual already_AddRefed<MediaStreamTrack> CloneInternal() = 0; nsTArray<PrincipalChangeObserver<MediaStreamTrack>*> mPrincipalChangeObservers; diff --git a/dom/media/VideoStreamTrack.cpp b/dom/media/VideoStreamTrack.cpp @@ -74,8 +74,9 @@ void VideoStreamTrack::GetLabel(nsAString& aLabel, CallerType aCallerType) { MediaStreamTrack::GetLabel(aLabel, aCallerType); } -already_AddRefed<MediaStreamTrack> VideoStreamTrack::Clone() { - return MediaStreamTrack::CloneInternal<VideoStreamTrack>(); +already_AddRefed<MediaStreamTrack> VideoStreamTrack::CloneInternal() { + return do_AddRef(new VideoStreamTrack(mWindow, mInputTrack, mSource, + ReadyState(), Muted(), mConstraints)); } } // namespace mozilla::dom diff --git a/dom/media/VideoStreamTrack.h b/dom/media/VideoStreamTrack.h @@ -25,8 +25,6 @@ class VideoStreamTrack : public MediaStreamTrack { bool aMuted = false, const MediaTrackConstraints& aConstraints = MediaTrackConstraints()); - already_AddRefed<MediaStreamTrack> Clone() override; - void Destroy() override; VideoStreamTrack* AsVideoStreamTrack() override { return this; } @@ -47,6 +45,9 @@ class VideoStreamTrack : public MediaStreamTrack { void GetLabel(nsAString& aLabel, CallerType aCallerType) override; + protected: + already_AddRefed<MediaStreamTrack> CloneInternal() override; + private: nsTArray<RefPtr<VideoOutput>> mVideoOutputs; }; diff --git a/dom/media/gtest/TestAudioDecoderInputTrack.cpp b/dom/media/gtest/TestAudioDecoderInputTrack.cpp @@ -67,6 +67,8 @@ class MockTestGraph : public MediaTrackGraphImpl { protected: ~MockDriver() = default; }; + + bool mEnableFakeAppend = false; }; AudioData* CreateAudioDataFromInfo(uint32_t aFrames, const AudioInfo& aInfo) { @@ -392,21 +394,30 @@ TEST_F(TestAudioDecoderInputTrack, OutputAndEndEvent) { // Append an audio and EOS, the output event should notify the amount of // frames that is equal to the amount of audio we appended. RefPtr<AudioData> audio = CreateAudioData(10); - auto outputPromise = TakeN(mTrack->OnOutput(), 1); + MozPromiseHolder<GenericPromise> holder; + RefPtr<GenericPromise> p = holder.Ensure(__func__); + MediaEventListener outputListener = + mTrack->OnOutput().Connect(NS_GetCurrentThread(), [&](TrackTime aFrame) { + EXPECT_EQ(aFrame, audio->Frames()); + holder.Resolve(true, __func__); + }); mTrack->AppendData(audio, nullptr); mTrack->NotifyEndOfStream(); TrackTime start = 0; TrackTime end = 10; mTrack->ProcessInput(start, end, ProcessedMediaTrack::ALLOW_END); - auto output = WaitFor(outputPromise).unwrap()[0]; - EXPECT_EQ(std::get<int64_t>(output), audio->Frames()); + (void)WaitFor(p); // Track should end in this iteration, so the end event should be notified. - auto endPromise = TakeN(mTrack->OnEnd(), 1); + p = holder.Ensure(__func__); + MediaEventListener endListener = mTrack->OnEnd().Connect( + NS_GetCurrentThread(), [&]() { holder.Resolve(true, __func__); }); start = end; end += 10; mTrack->ProcessInput(start, end, ProcessedMediaTrack::ALLOW_END); - (void)WaitFor(endPromise); + (void)WaitFor(p); + outputListener.Disconnect(); + endListener.Disconnect(); } TEST_F(TestAudioDecoderInputTrack, PlaybackRateChange) { @@ -434,13 +445,10 @@ TEST_F(TestAudioDecoderInputTrack, PlaybackRateChange) { mTrack->NotifyEndOfStream(); // Playback rate is 2x, so we should only get 1/2x sample frames, another 1/2 - // should be silence. Output should not be rate-aware. - auto outputPromise = TakeN(mTrack->OnOutput(), 1); + // should be silence. TrackTime start = 0; TrackTime end = audio->Frames(); mTrack->ProcessInput(start, end, kNoFlags); - auto output = WaitFor(outputPromise).unwrap()[0]; - EXPECT_EQ(std::get<int64_t>(output), audio->Frames()); EXPECT_PRED_FORMAT2(ExpectSegmentNonSilence, start, audio->Frames() / 2); EXPECT_PRED_FORMAT2(ExpectSegmentSilence, start + audio->Frames() / 2, end); } diff --git a/dom/media/gtest/TestDecodedStream.cpp b/dom/media/gtest/TestDecodedStream.cpp @@ -1,382 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -#include "BlankDecoderModule.h" -#include "DecodedStream.h" -#include "ImageContainer.h" -#include "MediaData.h" -#include "MediaQueue.h" -#include "MediaTrackGraphImpl.h" -#include "MediaTrackListener.h" -#include "MockCubeb.h" -#include "VideoSegment.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "mozilla/gtest/WaitFor.h" -#include "nsJSEnvironment.h" - -using mozilla::layers::ImageContainer; -using mozilla::layers::ImageUsageType; -using mozilla::media::TimeUnit; -using testing::ElementsAre; -using testing::Test; - -namespace mozilla { -// Short-hand for DispatchToCurrentThread with a function. -#define DispatchFunction(f) \ - NS_DispatchToCurrentThread(NS_NewRunnableFunction(__func__, f)) - -enum MediaType { Audio = 1, Video = 2, AudioVideo = Audio | Video }; - -template <MediaType Type> -CopyableTArray<RefPtr<ProcessedMediaTrack>> CreateOutputTracks( - MediaTrackGraphImpl* aGraph) { - CopyableTArray<RefPtr<ProcessedMediaTrack>> outputTracks; - if constexpr (Type & Audio) { - outputTracks.AppendElement( - aGraph->CreateForwardedInputTrack(MediaSegment::AUDIO)); - } - if constexpr (Type & Video) { - outputTracks.AppendElement( - aGraph->CreateForwardedInputTrack(MediaSegment::VIDEO)); - } - return outputTracks; -} - -template <MediaType Type> -MediaInfo CreateMediaInfo() { - MediaInfo info; - info.mStartTime = TimeUnit::Zero(); - if constexpr (Type & Audio) { - info.EnableAudio(); - } - if constexpr (Type & Video) { - info.EnableVideo(); - } - return info; -} - -class OnFallbackListener : public MediaTrackListener { - const RefPtr<MediaTrack> mTrack; - Atomic<bool> mOnFallback{true}; - - public: - explicit OnFallbackListener(MediaTrack* aTrack) : mTrack(aTrack) {} - - void Reset() { mOnFallback = true; } - bool OnFallback() { return mOnFallback; } - - void NotifyOutput(MediaTrackGraph*, TrackTime) override { - if (auto* ad = - mTrack->GraphImpl()->CurrentDriver()->AsAudioCallbackDriver()) { - mOnFallback = ad->OnFallback(); - } - } -}; - -template <typename Segment> -class CapturingListener : public MediaTrackListener { - public: - Segment mSegment; - - void NotifyQueuedChanges(MediaTrackGraph* aGraph, TrackTime aTrackOffset, - const MediaSegment& aQueuedMedia) { - mSegment.AppendSlice(aQueuedMedia, 0, aQueuedMedia.GetDuration()); - } -}; - -class TestableDecodedStream : public DecodedStream { - public: - TestableDecodedStream( - AbstractThread* aOwnerThread, - nsMainThreadPtrHandle<SharedDummyTrack> aDummyTrack, - CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, - AbstractCanonical<PrincipalHandle>* aCanonicalOutputPrincipal, - double aVolume, double aPlaybackRate, bool aPreservesPitch, - MediaQueue<AudioData>& aAudioQueue, MediaQueue<VideoData>& aVideoQueue) - : DecodedStream(aOwnerThread, std::move(aDummyTrack), - std::move(aOutputTracks), aCanonicalOutputPrincipal, - aVolume, aPlaybackRate, aPreservesPitch, aAudioQueue, - aVideoQueue) {} - - using DecodedStream::GetPositionImpl; - using DecodedStream::LastOutputSystemTime; - using DecodedStream::LastVideoTimeStamp; -}; - -template <MediaType Type> -class TestDecodedStream : public Test { - public: - static constexpr TrackRate kRate = 48000; - static constexpr uint32_t kChannels = 2; - const RefPtr<MockCubeb> mMockCubeb; - RefPtr<SmartMockCubebStream> mMockCubebStream; - MediaQueue<AudioData> mAudioQueue; - MediaQueue<VideoData> mVideoQueue; - RefPtr<MediaTrackGraphImpl> mGraph; - nsMainThreadPtrHandle<SharedDummyTrack> mDummyTrack; - CopyableTArray<RefPtr<ProcessedMediaTrack>> mOutputTracks; - Canonical<PrincipalHandle> mCanonicalOutputPrincipal; - RefPtr<TestableDecodedStream> mDecodedStream; - - TestDecodedStream() - : mMockCubeb(MakeRefPtr<MockCubeb>(MockCubeb::RunningMode::Manual)), - mGraph(MediaTrackGraphImpl::GetInstance( - MediaTrackGraph::SYSTEM_THREAD_DRIVER, /*Window ID*/ 1, kRate, - nullptr, GetMainThreadSerialEventTarget())), - mDummyTrack(new nsMainThreadPtrHolder<SharedDummyTrack>( - __func__, new SharedDummyTrack( - mGraph->CreateSourceTrack(MediaSegment::AUDIO)))), - mOutputTracks(CreateOutputTracks<Type>(mGraph)), - mCanonicalOutputPrincipal( - AbstractThread::GetCurrent(), PRINCIPAL_HANDLE_NONE, - "TestDecodedStream::mCanonicalOutputPrincipal"), - mDecodedStream(MakeRefPtr<TestableDecodedStream>( - AbstractThread::GetCurrent(), mDummyTrack, mOutputTracks, - &mCanonicalOutputPrincipal, /* aVolume = */ 1.0, - /* aPlaybackRate = */ 1.0, - /* aPreservesPitch = */ true, mAudioQueue, mVideoQueue)) { - MOZ_ASSERT(NS_IsMainThread()); - }; - - void SetUp() override { - MOZ_ASSERT(NS_IsMainThread()); - CubebUtils::ForceSetCubebContext(mMockCubeb->AsCubebContext()); - - for (const auto& track : mOutputTracks) { - track->QueueSetAutoend(false); - } - - // Resume the dummy track because a suspended audio track will not use an - // AudioCallbackDriver. - mDummyTrack->mTrack->Resume(); - - RefPtr fallbackListener = new OnFallbackListener(mDummyTrack->mTrack); - mDummyTrack->mTrack->AddListener(fallbackListener); - - mMockCubebStream = WaitFor(mMockCubeb->StreamInitEvent()); - while (mMockCubebStream->State().isNothing()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - ASSERT_EQ(*mMockCubebStream->State(), CUBEB_STATE_STARTED); - // Wait for the AudioCallbackDriver to come into effect. - while (fallbackListener->OnFallback()) { - ASSERT_EQ(mMockCubebStream->ManualDataCallback(1), - MockCubebStream::KeepProcessing::Yes); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - } - - void TearDown() override { - MOZ_ASSERT(NS_IsMainThread()); - // Destroy all tracks so they're removed from the graph. - mDecodedStream->Shutdown(); - for (const auto& t : mOutputTracks) { - t->Destroy(); - } - mDummyTrack = nullptr; - // DecodedStream also has a ref to the dummy track. - mDecodedStream = nullptr; - - // Wait for the graph to shutdown. If all tracks are indeed removed, it will - // not switch to another driver. - MockCubebStream::KeepProcessing keepProcessing{}; - while ((keepProcessing = mMockCubebStream->ManualDataCallback(0)) == - MockCubebStream::KeepProcessing::Yes) { - NS_ProcessPendingEvents(nullptr); - } - ASSERT_EQ(keepProcessing, MockCubebStream::KeepProcessing::No); - - // Process the final track removal and run the stable state runnable. - NS_ProcessPendingEvents(nullptr); - // Process the shutdown runnable. - NS_ProcessPendingEvents(nullptr); - - // Graph should be shut down. - ASSERT_TRUE(mGraph->OnGraphThreadOrNotRunning()) - << "Not on graph thread so graph must still be running!"; - ASSERT_EQ(mGraph->LifecycleStateRef(), - MediaTrackGraphImpl::LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN) - << "The graph should be in its final state. Note it does not advance " - "the state any further on thread shutdown."; - CubebUtils::ForceSetCubebContext(nullptr); - - // mGraph should be the last or second last reference to the graph. The last - // reference may be the JS-based shutdown blocker, which will eventually be - // destroyed by CC and GC. - MediaTrackGraphImpl* graph{}; - mGraph.forget(&graph); - int32_t refcnt = static_cast<int32_t>(graph->Release()); - EXPECT_LE(refcnt, 1); - - // Attempt to release the last reference to the graph, to avoid its lifetime - // reaching into future tests. - nsJSContext::CycleCollectNow(CCReason::API); - nsJSContext::GarbageCollectNow(JS::GCReason::API); - NS_ProcessPendingEvents(nullptr); - } - - MediaInfo CreateMediaInfo() { return mozilla::CreateMediaInfo<Type>(); } - - void TestVideoTimestampsWithPlaybackRate(double aPlaybackRate); -}; - -using TestDecodedStreamA = TestDecodedStream<Audio>; -using TestDecodedStreamV = TestDecodedStream<Video>; -using TestDecodedStreamAV = TestDecodedStream<AudioVideo>; - -TEST_F(TestDecodedStreamAV, StartStop) { - mDecodedStream->Start(TimeUnit::Zero(), CreateMediaInfo()); - mDecodedStream->SetPlaying(true); - mDecodedStream->Stop(); -} - -TEST_F(TestDecodedStreamA, LastOutputSystemTime) { - auto start = AwakeTimeStamp::Now(); - BlankAudioDataCreator creator(2, kRate); - auto raw = MakeRefPtr<MediaRawData>(); - raw->mDuration = TimeUnit(kRate, kRate); - mAudioQueue.Push(RefPtr(creator.Create(raw))->As<AudioData>()); - - mDecodedStream->Start(TimeUnit::Zero(), CreateMediaInfo()); - mDecodedStream->SetPlaying(true); - NS_ProcessPendingEvents(nullptr); - mMockCubebStream->ManualDataCallback(0); - - auto before = AwakeTimeStamp::Now(); - // This runs the events on the graph thread, sampling the system clock. - mMockCubebStream->ManualDataCallback(512); - auto after = AwakeTimeStamp::Now(); - // This runs the event handlers on the MDSM thread, updating the timestamps. - NS_ProcessPendingEvents(nullptr); - EXPECT_GE(mDecodedStream->LastOutputSystemTime() - start, before - start); - EXPECT_LE(mDecodedStream->LastOutputSystemTime() - start, after - start); - - mDecodedStream->Stop(); -} - -TEST_F(TestDecodedStreamA, InterpolatedPosition) { - BlankAudioDataCreator creator(2, kRate); - auto raw = MakeRefPtr<MediaRawData>(); - raw->mDuration = TimeUnit(kRate, kRate); - mAudioQueue.Push(RefPtr(creator.Create(raw))->As<AudioData>()); - - mDecodedStream->Start(TimeUnit::Zero(), CreateMediaInfo()); - mDecodedStream->SetPlaying(true); - NS_ProcessPendingEvents(nullptr); - mMockCubebStream->ManualDataCallback(0); - - auto now = TimeStamp::Now(); - auto awakeNow = AwakeTimeStamp::Now(); - TimeStamp outNow; - TimeUnit pos = mDecodedStream->GetPositionImpl(now, awakeNow, &outNow); - EXPECT_EQ(now, outNow); - EXPECT_EQ(pos, TimeUnit::Zero()) << pos.ToMilliseconds(); - - mMockCubebStream->ManualDataCallback(512); - NS_ProcessPendingEvents(nullptr); - - now += TimeDuration::FromSeconds( - (mDecodedStream->LastOutputSystemTime() - awakeNow).ToSeconds()); - awakeNow = mDecodedStream->LastOutputSystemTime(); - pos = mDecodedStream->GetPositionImpl(now, awakeNow); - EXPECT_EQ(pos.ToMicroseconds(), TimeUnit(512, kRate).ToMicroseconds()); - - // Check that the position is interpolated based on wall clock time since last - // output notification. - now += TimeDuration::FromSeconds( - (mDecodedStream->LastOutputSystemTime() - awakeNow).ToSeconds()) + - TimeDuration::FromMilliseconds(10); - awakeNow = mDecodedStream->LastOutputSystemTime() + - AwakeTimeDuration::FromMilliseconds(10); - pos = mDecodedStream->GetPositionImpl(now, awakeNow); - EXPECT_EQ(pos.ToMicroseconds(), - (TimeUnit(512, kRate) + TimeUnit(10, 1000)).ToMicroseconds()); - - mDecodedStream->Stop(); -} - -template <MediaType Type> -void TestDecodedStream<Type>::TestVideoTimestampsWithPlaybackRate( - double aPlaybackRate) { - static_assert(Type == MediaType::Video); - - auto imageContainer = MakeRefPtr<ImageContainer>(ImageUsageType::Webrtc, - ImageContainer::SYNCHRONOUS); - // Capture the output into a dedicated segment, that the graph will not prune - // like it will for the output track's mSegment. - RefPtr capturingListener = new CapturingListener<VideoSegment>(); - mOutputTracks[0]->AddListener(capturingListener); - VideoSegment* segment = &capturingListener->mSegment; - - { - // Add 4 video frames a 100ms each. Later we'll check timestamps of 3. We - // add 4 here to make the 3rd frames duration deterministic. - BlankVideoDataCreator creator(640, 480, imageContainer); - TimeUnit t = TimeUnit::Zero(); - for (size_t i = 0; i < 4; ++i) { - constexpr TimeUnit kDuration = TimeUnit(kRate / 10, kRate); - auto raw = MakeRefPtr<MediaRawData>(); - raw->mTime = t; - raw->mDuration = kDuration; - t += kDuration; - mVideoQueue.Push(RefPtr(creator.Create(raw))->template As<VideoData>()); - } - } - - mDecodedStream->SetPlaybackRate(aPlaybackRate); - mDecodedStream->Start(TimeUnit::Zero(), CreateMediaInfo()); - mDecodedStream->SetPlaying(true); - NS_ProcessPendingEvents(nullptr); - mMockCubebStream->ManualDataCallback(0); - - // Advance time enough to extract all 3 video frames. - long duration = 0; - while (duration < static_cast<long>((static_cast<double>(kRate) / 10) * 3 / - aPlaybackRate)) { - constexpr long kChunk = 512; - mMockCubebStream->ManualDataCallback(kChunk); - NS_ProcessPendingEvents(nullptr); - duration += kChunk; - } - EXPECT_EQ(segment->GetDuration(), duration); - - // Calculate the expected timestamp of the first frame. At this point all - // frames in the VideoQueue have been sent, so LastVideoTimeStamp() matches - // the timestamp of frame 4. - const auto frameGap = - TimeDuration::FromMilliseconds(100).MultDouble(1 / aPlaybackRate); - TimeStamp videoStartOffset = - mDecodedStream->LastVideoTimeStamp() - frameGap * 3; - - // Check durations of the first 3 frames. - AutoTArray<TrackTime, 3> durations; - AutoTArray<TimeDuration, 3> timestamps; - for (VideoSegment::ConstChunkIterator i(*segment); - durations.Length() < 3 && !i.IsEnded(); i.Next()) { - durations.AppendElement(i->GetDuration()); - timestamps.AppendElement(i->mTimeStamp - videoStartOffset); - } - const TrackTime d = - static_cast<TrackTime>(static_cast<double>(kRate) / 10 / aPlaybackRate); - EXPECT_THAT(durations, ElementsAre(d, d, d)); - EXPECT_THAT(timestamps, - ElementsAre(frameGap * 0, frameGap * 1, frameGap * 2)); - - mOutputTracks[0]->RemoveListener(capturingListener); - mDecodedStream->Stop(); -} - -TEST_F(TestDecodedStreamV, VideoTimeStamps) { - TestVideoTimestampsWithPlaybackRate(1.0); -} -TEST_F(TestDecodedStreamV, VideoTimeStampsFaster) { - TestVideoTimestampsWithPlaybackRate(2.0); -} -TEST_F(TestDecodedStreamV, VideoTimeStampsSlower) { - TestVideoTimestampsWithPlaybackRate(0.5); -} -} // namespace mozilla diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build @@ -40,7 +40,6 @@ UNIFIED_SOURCES += [ "TestBufferReader.cpp", "TestCubebInputStream.cpp", "TestDataMutex.cpp", - "TestDecodedStream.cpp", "TestDeviceInputTrack.cpp", "TestDriftCompensation.cpp", "TestGMPUtils.cpp", diff --git a/dom/media/mediasink/AudioDecoderInputTrack.cpp b/dom/media/mediasink/AudioDecoderInputTrack.cpp @@ -597,7 +597,7 @@ void AudioDecoderInputTrack::NotifyInTheEndOfProcessInput( LOG("Notify, fill=%" PRId64 ", total written=%" PRId64 ", ended=%d", aFillDuration, mWrittenFrames, Ended()); if (aFillDuration > 0) { - mOnOutput.Notify(mWrittenFrames, TimeStamp::Now(), AwakeTimeStamp::Now()); + mOnOutput.Notify(mWrittenFrames); } if (Ended()) { mOnEnd.Notify(); diff --git a/dom/media/mediasink/AudioDecoderInputTrack.h b/dom/media/mediasink/AudioDecoderInputTrack.h @@ -12,6 +12,7 @@ #include "MediaTrackGraph.h" #include "TimeUnits.h" #include "mozilla/SPSCQueue.h" +#include "mozilla/StateMirroring.h" #include "mozilla/TimeStamp.h" #include "nsISerialEventTarget.h" @@ -100,9 +101,7 @@ class AudioDecoderInputTrack final : public ProcessedMediaTrack { void Close(); bool HasBatchedData() const; - MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& OnOutput() { - return mOnOutput; - } + MediaEventSource<int64_t>& OnOutput() { return mOnOutput; } MediaEventSource<void>& OnEnd() { return mOnEnd; } // Graph Thread API @@ -177,9 +176,8 @@ class AudioDecoderInputTrack final : public ProcessedMediaTrack { const RefPtr<nsISerialEventTarget> mDecoderThread; - // Notify the amount of audio frames which have been sent to the track, - // sampled by the awake system time (and non-awake, for now) they were sent. - MediaEventProducer<int64_t, TimeStamp, AwakeTimeStamp> mOnOutput; + // Notify the amount of audio frames which have been sent to the track. + MediaEventProducer<int64_t> mOnOutput; // Notify when the track is ended. MediaEventProducer<void> mOnEnd; diff --git a/dom/media/mediasink/DecodedStream.cpp b/dom/media/mediasink/DecodedStream.cpp @@ -7,17 +7,20 @@ #include "DecodedStream.h" #include "AudioDecoderInputTrack.h" +#include "AudioSegment.h" #include "MediaData.h" #include "MediaDecoderStateMachine.h" #include "MediaQueue.h" #include "MediaTrackGraph.h" #include "MediaTrackListener.h" +#include "SharedBuffer.h" #include "Tracing.h" #include "VideoSegment.h" #include "VideoUtils.h" #include "mozilla/AbstractThread.h" #include "mozilla/CheckedInt.h" #include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkerTypes.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/SyncRunnable.h" #include "mozilla/gfx/Point.h" @@ -101,11 +104,9 @@ class DecodedStreamGraphListener { void RegisterListeners() { if (mAudioTrack) { mOnAudioOutput = mAudioTrack->OnOutput().Connect( - mDecoderThread, [self = RefPtr<DecodedStreamGraphListener>(this)]( - TrackTime aTime, TimeStamp aSystemTime, - AwakeTimeStamp aAwakeSystemTime) { - self->NotifyOutput(MediaSegment::AUDIO, aTime, aSystemTime, - aAwakeSystemTime); + mDecoderThread, + [self = RefPtr<DecodedStreamGraphListener>(this)](TrackTime aTime) { + self->NotifyOutput(MediaSegment::AUDIO, aTime); }); mOnAudioEnd = mAudioTrack->OnEnd().Connect( mDecoderThread, [self = RefPtr<DecodedStreamGraphListener>(this)]() { @@ -146,8 +147,7 @@ class DecodedStreamGraphListener { mOnAudioEnd.DisconnectIfExists(); } - void NotifyOutput(MediaSegment::Type aType, TrackTime aCurrentTrackTime, - TimeStamp aSystemTime, AwakeTimeStamp aAwakeSystemTime) { + void NotifyOutput(MediaSegment::Type aType, TrackTime aCurrentTrackTime) { AssertOnDecoderThread(); if (aType == MediaSegment::AUDIO) { mAudioOutputFrames = aCurrentTrackTime; @@ -181,8 +181,7 @@ class DecodedStreamGraphListener { const MediaTrack* track = aType == MediaSegment::VIDEO ? static_cast<MediaTrack*>(mVideoTrack) : static_cast<MediaTrack*>(mAudioTrack); - mOnOutput.Notify(track->TrackTimeToMicroseconds(aCurrentTrackTime), - aSystemTime, aAwakeSystemTime); + mOnOutput.Notify(track->TrackTimeToMicroseconds(aCurrentTrackTime)); } void NotifyEnded(MediaSegment::Type aType) { @@ -238,9 +237,7 @@ class DecodedStreamGraphListener { return mAudioOutputFrames; } - MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& OnOutput() { - return mOnOutput; - } + MediaEventSource<int64_t>& OnOutput() { return mOnOutput; } private: ~DecodedStreamGraphListener() { @@ -255,7 +252,7 @@ class DecodedStreamGraphListener { const RefPtr<nsISerialEventTarget> mDecoderThread; // Accessible on any thread, but only notify on the decoder thread. - MediaEventProducer<int64_t, TimeStamp, AwakeTimeStamp> mOnOutput; + MediaEventProducer<int64_t> mOnOutput; RefPtr<SourceVideoTrackListener> mVideoTrackListener; @@ -302,12 +299,9 @@ void SourceVideoTrackListener::NotifyOutput(MediaTrackGraph* aGraph, mLastVideoOutputTime = aCurrentTrackTime; mDecoderThread->Dispatch(NS_NewRunnableFunction( "SourceVideoTrackListener::NotifyOutput", - [self = RefPtr<SourceVideoTrackListener>(this), aCurrentTrackTime, - systemTime = TimeStamp::Now(), - awakeSystemTime = AwakeTimeStamp::Now()]() { + [self = RefPtr<SourceVideoTrackListener>(this), aCurrentTrackTime]() { self->mGraphListener->NotifyOutput(MediaSegment::VIDEO, - aCurrentTrackTime, systemTime, - awakeSystemTime); + aCurrentTrackTime); })); } @@ -339,7 +333,7 @@ class DecodedStreamData final { float aPlaybackRate, float aVolume, bool aPreservesPitch, nsISerialEventTarget* aDecoderThread); ~DecodedStreamData(); - MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& OnOutput(); + MediaEventSource<int64_t>& OnOutput(); // This is used to mark track as closed and should be called before Forget(). // Decoder thread only. void Close(); @@ -449,8 +443,7 @@ DecodedStreamData::~DecodedStreamData() { } } -MediaEventSource<int64_t, TimeStamp, AwakeTimeStamp>& -DecodedStreamData::OnOutput() { +MediaEventSource<int64_t>& DecodedStreamData::OnOutput() { return mListener->OnOutput(); } @@ -474,20 +467,19 @@ void DecodedStreamData::GetDebugInfo(dom::DecodedStreamDataDebugInfo& aInfo) { } DecodedStream::DecodedStream( - AbstractThread* aOwnerThread, + MediaDecoderStateMachine* aStateMachine, nsMainThreadPtrHandle<SharedDummyTrack> aDummyTrack, - CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, - AbstractCanonical<PrincipalHandle>* aCanonicalOutputPrincipal, - double aVolume, double aPlaybackRate, bool aPreservesPitch, - MediaQueue<AudioData>& aAudioQueue, MediaQueue<VideoData>& aVideoQueue) - : mOwnerThread(aOwnerThread), + CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, double aVolume, + double aPlaybackRate, bool aPreservesPitch, + MediaQueue<AudioData>& aAudioQueue, MediaQueue<VideoData>& aVideoQueue, + RefPtr<AudioDeviceInfo> aAudioDevice) + : mOwnerThread(aStateMachine->OwnerThread()), mDummyTrack(std::move(aDummyTrack)), - mWatchManager(this, mOwnerThread), mPlaying(false, "DecodedStream::mPlaying"), - mPrincipalHandle(aOwnerThread, PRINCIPAL_HANDLE_NONE, + mPrincipalHandle(aStateMachine->OwnerThread(), PRINCIPAL_HANDLE_NONE, "DecodedStream::mPrincipalHandle (Mirror)"), - mCanonicalOutputPrincipal(aCanonicalOutputPrincipal), + mCanonicalOutputPrincipal(aStateMachine->CanonicalOutputPrincipal()), mOutputTracks(std::move(aOutputTracks)), mVolume(aVolume), mPlaybackRate(aPlaybackRate), @@ -516,7 +508,6 @@ nsresult DecodedStream::Start(const TimeUnit& aStartTime, const MediaInfo& aInfo) { AssertOwnerThread(); MOZ_ASSERT(mStartTime.isNothing(), "playback already started."); - MOZ_ASSERT(mLastReportedPosition.isNothing()); AUTO_PROFILER_LABEL(FUNCTION_SIGNATURE, MEDIA_PLAYBACK); if (profiler_thread_is_being_profiled_for_markers()) { @@ -529,7 +520,6 @@ nsresult DecodedStream::Start(const TimeUnit& aStartTime, mStartTime.emplace(aStartTime); mLastOutputTime = TimeUnit::Zero(); - mLastOutputSystemTime = Nothing(); mInfo = aInfo; mPlaying = true; mPrincipalHandle.Connect(mCanonicalOutputPrincipal); @@ -645,7 +635,6 @@ void DecodedStream::Stop() { ResetVideo(mPrincipalHandle); ResetAudio(); mStartTime.reset(); - mLastReportedPosition = Nothing(); mAudioEndedPromise = nullptr; mVideoEndedPromise = nullptr; @@ -994,10 +983,9 @@ void DecodedStream::SendVideo(const PrincipalHandle& aPrincipalHandle) { // video frame). E.g. if we have a video frame that is 30 sec long // and capture happens at 15 sec, we'll have to append a black frame // that is 15 sec long. - TimeStamp t = std::max(mData->mLastVideoTimeStamp, - currentTime + (lastEnd - currentPosition) - .ToTimeDuration() - .MultDouble(1 / mPlaybackRate)); + TimeStamp t = + std::max(mData->mLastVideoTimeStamp, + currentTime + (lastEnd - currentPosition).ToTimeDuration()); mData->WriteVideoToSegment(mData->mLastVideoImage, lastEnd, v->mTime, mData->mLastVideoImageDisplaySize, t, &output, aPrincipalHandle, mPlaybackRate); @@ -1009,10 +997,9 @@ void DecodedStream::SendVideo(const PrincipalHandle& aPrincipalHandle) { // before the last frame's end time for some videos. This only matters for // the track's lifetime in the MTG, as rendering is based on timestamps, // aka frame start times. - TimeStamp t = std::max(mData->mLastVideoTimeStamp, - currentTime + (lastEnd - currentPosition) - .ToTimeDuration() - .MultDouble(1 / mPlaybackRate)); + TimeStamp t = + std::max(mData->mLastVideoTimeStamp, + currentTime + (lastEnd - currentPosition).ToTimeDuration()); TimeUnit end = std::max( v->GetEndTime(), lastEnd + TimeUnit::FromMicroseconds( @@ -1113,47 +1100,16 @@ TimeUnit DecodedStream::GetEndTime(TrackType aType) const { TimeUnit DecodedStream::GetPosition(TimeStamp* aTimeStamp) { AssertOwnerThread(); TRACE("DecodedStream::GetPosition"); - return GetPositionImpl(TimeStamp::Now(), AwakeTimeStamp::Now(), aTimeStamp); -} - -TimeUnit DecodedStream::GetPositionImpl(TimeStamp aNow, - AwakeTimeStamp aAwakeNow, - TimeStamp* aTimeStamp) { - AssertOwnerThread(); // This is only called after MDSM starts playback. So mStartTime is // guaranteed to be something. MOZ_ASSERT(mStartTime.isSome()); if (aTimeStamp) { - *aTimeStamp = aNow; - } - AwakeTimeDuration timeSinceLastOutput; - if (mLastOutputSystemTime) { - MOZ_ASSERT(aAwakeNow >= *mLastOutputSystemTime); - timeSinceLastOutput = aAwakeNow - *mLastOutputSystemTime; - } - TimeUnit position = mStartTime.ref() + mLastOutputTime + - TimeUnit::FromSeconds(timeSinceLastOutput.ToSeconds()); - if (mLastReportedPosition && position < *mLastReportedPosition) { - // There's a theoretical risk of time going backwards because of the - // interpolation based on mLastOutputSystemTime. Prevent that here. - position = *mLastReportedPosition; - } - mLastReportedPosition = Some(position); - return position; -} - -AwakeTimeStamp DecodedStream::LastOutputSystemTime() const { - AssertOwnerThread(); - return *mLastOutputSystemTime; -} - -TimeStamp DecodedStream::LastVideoTimeStamp() const { - AssertOwnerThread(); - return mData->mLastVideoTimeStamp; + *aTimeStamp = TimeStamp::Now(); + } + return mStartTime.ref() + mLastOutputTime; } -void DecodedStream::NotifyOutput(int64_t aTime, TimeStamp aSystemTime, - AwakeTimeStamp aAwakeSystemTime) { +void DecodedStream::NotifyOutput(int64_t aTime) { AssertOwnerThread(); TimeUnit time = TimeUnit::FromMicroseconds(aTime); if (time == mLastOutputTime) { @@ -1161,10 +1117,7 @@ void DecodedStream::NotifyOutput(int64_t aTime, TimeStamp aSystemTime, } MOZ_ASSERT(mLastOutputTime < time); mLastOutputTime = time; - MOZ_ASSERT_IF(mLastOutputSystemTime, - *mLastOutputSystemTime < aAwakeSystemTime); - mLastOutputSystemTime = Some(aAwakeSystemTime); - auto currentTime = GetPositionImpl(aSystemTime, aAwakeSystemTime); + auto currentTime = GetPosition(); if (profiler_thread_is_being_profiled_for_markers()) { nsPrintfCString markerString("OutputTime=%" PRId64, @@ -1226,10 +1179,6 @@ void DecodedStream::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) { NS_ConvertUTF8toUTF16(nsPrintfCString("%p", this)); aInfo.mDecodedStream.mStartTime = startTime; aInfo.mDecodedStream.mLastOutputTime = mLastOutputTime.ToMicroseconds(); - aInfo.mDecodedStream.mLastReportedPosition = - mLastReportedPosition - .map([](const auto& aT) { return aT.ToMicroseconds(); }) - .valueOr(0); aInfo.mDecodedStream.mPlaying = mPlaying.Ref(); auto lastAudio = mAudioQueue.PeekBack(); aInfo.mDecodedStream.mLastAudio = diff --git a/dom/media/mediasink/DecodedStream.h b/dom/media/mediasink/DecodedStream.h @@ -10,14 +10,13 @@ #include "AudibilityMonitor.h" #include "MediaEventSource.h" #include "MediaInfo.h" +#include "MediaSegment.h" #include "MediaSink.h" #include "mozilla/AbstractThread.h" -#include "mozilla/AwakeTimeStamp.h" #include "mozilla/Maybe.h" #include "mozilla/MozPromise.h" #include "mozilla/RefPtr.h" #include "mozilla/StateMirroring.h" -#include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" namespace mozilla { @@ -29,19 +28,20 @@ class VideoData; struct PlaybackInfoInit; class ProcessedMediaTrack; struct SharedDummyTrack; +class TimeStamp; template <class T> class MediaQueue; class DecodedStream : public MediaSink { public: - DecodedStream(AbstractThread* aOwnerThread, + DecodedStream(MediaDecoderStateMachine* aStateMachine, nsMainThreadPtrHandle<SharedDummyTrack> aDummyTrack, CopyableTArray<RefPtr<ProcessedMediaTrack>> aOutputTracks, - AbstractCanonical<PrincipalHandle>* aCanonicalOutputPrincipal, double aVolume, double aPlaybackRate, bool aPreservesPitch, MediaQueue<AudioData>& aAudioQueue, - MediaQueue<VideoData>& aVideoQueue); + MediaQueue<VideoData>& aVideoQueue, + RefPtr<AudioDeviceInfo> aAudioDevice); RefPtr<EndedPromise> OnEnded(TrackType aType) override; media::TimeUnit GetEndTime(TrackType aType) const override; @@ -78,12 +78,6 @@ class DecodedStream : public MediaSink { protected: virtual ~DecodedStream(); - // A bit many clocks to sample, but what do you do... - media::TimeUnit GetPositionImpl(TimeStamp aNow, AwakeTimeStamp aAwakeNow, - TimeStamp* aTimeStamp = nullptr); - AwakeTimeStamp LastOutputSystemTime() const; - TimeStamp LastVideoTimeStamp() const; - private: void DestroyData(UniquePtr<DecodedStreamData>&& aData); void SendAudio(const PrincipalHandle& aPrincipalHandle); @@ -91,8 +85,7 @@ class DecodedStream : public MediaSink { void ResetAudio(); void ResetVideo(const PrincipalHandle& aPrincipalHandle); void SendData(); - void NotifyOutput(int64_t aTime, TimeStamp aSystemTime, - AwakeTimeStamp aAwakeSystemTime); + void NotifyOutput(int64_t aTime); void CheckIsDataAudible(const AudioData* aData); void AssertOwnerThread() const { @@ -135,8 +128,6 @@ class DecodedStream : public MediaSink { media::NullableTimeUnit mStartTime; media::TimeUnit mLastOutputTime; - Maybe<AwakeTimeStamp> mLastOutputSystemTime; - Maybe<media::TimeUnit> mLastReportedPosition; MediaInfo mInfo; // True when stream is producing audible sound, false when stream is silent. bool mIsAudioDataAudible = false; diff --git a/dom/media/systemservices/CamerasChild.cpp b/dom/media/systemservices/CamerasChild.cpp @@ -141,42 +141,35 @@ mozilla::ipc::IPCResult CamerasChild::RecvReplyNumberOfCapabilities( template <class T = int> class LockAndDispatch { public: - using Result = CamerasChild::DispatchToParentResult; - LockAndDispatch(CamerasChild* aCamerasChild, const char* aRequestingFunc, - nsIRunnable* aRunnable, T aFailureValue, T aIPCFailureValue, + nsIRunnable* aRunnable, T aFailureValue, const T& aSuccessValue) : mCamerasChild(aCamerasChild), mRequestingFunc(aRequestingFunc), mRunnable(aRunnable), mReplyLock(aCamerasChild->mReplyMonitor), mRequestLock(aCamerasChild->mRequestMutex), - mStatus(Result::SUCCESS), + mSuccess(true), mFailureValue(aFailureValue), - mIPCFailureValue(aIPCFailureValue), mSuccessValue(aSuccessValue) { Dispatch(); } T ReturnValue() const { - if (mStatus == Result::SUCCESS) { + if (mSuccess) { return mSuccessValue; - } - if (mStatus == Result::FAILURE) { + } else { return mFailureValue; } - MOZ_ASSERT(mStatus == Result::DISCONNECTED); - return mIPCFailureValue; } - bool Success() const { return mStatus == Result::SUCCESS; } - bool Disconnected() const { return mStatus == Result::DISCONNECTED; } + const bool& Success() const { return mSuccess; } private: void Dispatch() { - mStatus = mCamerasChild->DispatchToParent(mRunnable, mReplyLock); - if (mStatus != Result::SUCCESS) { + if (!mCamerasChild->DispatchToParent(mRunnable, mReplyLock)) { LOG(("Cameras dispatch for IPC failed in %s", mRequestingFunc)); + mSuccess = false; } } @@ -188,15 +181,13 @@ class LockAndDispatch { // the reply to be filled in, necessitating the additional mRequestLock/Mutex; MonitorAutoLock mReplyLock; MutexAutoLock mRequestLock; - CamerasChild::DispatchToParentResult mStatus; + bool mSuccess; const T mFailureValue; - const T mIPCFailureValue; const T& mSuccessValue; }; -auto CamerasChild::DispatchToParent(nsIRunnable* aRunnable, - MonitorAutoLock& aMonitor) - -> DispatchToParentResult { +bool CamerasChild::DispatchToParent(nsIRunnable* aRunnable, + MonitorAutoLock& aMonitor) { CamerasSingleton::Mutex().AssertCurrentThreadOwns(); mReplyMonitor.AssertCurrentThreadOwns(); CamerasSingleton::Thread()->Dispatch(aRunnable, NS_DISPATCH_NORMAL); @@ -206,12 +197,11 @@ auto CamerasChild::DispatchToParent(nsIRunnable* aRunnable, do { // If the parent has been shut down, then we won't receive a reply. if (!mIPCIsAlive) { - return DispatchToParentResult::DISCONNECTED; + return false; } aMonitor.Wait(); } while (!mReceivedReply); - return mReplySuccess ? DispatchToParentResult::SUCCESS - : DispatchToParentResult::FAILURE; + return mReplySuccess; } int CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine, @@ -223,7 +213,7 @@ int CamerasChild::NumberOfCapabilities(CaptureEngine aCapEngine, mozilla::NewRunnableMethod<CaptureEngine, nsCString>( "camera::PCamerasChild::SendNumberOfCapabilities", this, &CamerasChild::SendNumberOfCapabilities, aCapEngine, unique_id); - LockAndDispatch<> dispatcher(this, __func__, runnable, 0, 0, mReplyInteger); + LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture capability count: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } @@ -233,7 +223,7 @@ int CamerasChild::NumberOfCaptureDevices(CaptureEngine aCapEngine) { nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod<CaptureEngine>( "camera::PCamerasChild::SendNumberOfCaptureDevices", this, &CamerasChild::SendNumberOfCaptureDevices, aCapEngine); - LockAndDispatch<> dispatcher(this, __func__, runnable, 0, 0, mReplyInteger); + LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); LOG(("Capture Devices: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } @@ -254,8 +244,8 @@ int CamerasChild::EnsureInitialized(CaptureEngine aCapEngine) { nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod<CaptureEngine>( "camera::PCamerasChild::SendEnsureInitialized", this, &CamerasChild::SendEnsureInitialized, aCapEngine); - LockAndDispatch<> dispatcher(this, __func__, runnable, 0, 0, mReplyInteger); - LOG(("Initialized: %d", dispatcher.ReturnValue())); + LockAndDispatch<> dispatcher(this, __func__, runnable, 0, mReplyInteger); + LOG(("Capture Devices: %d", dispatcher.ReturnValue())); return dispatcher.ReturnValue(); } @@ -272,8 +262,7 @@ int CamerasChild::GetCaptureCapability( &CamerasChild::SendGetCaptureCapability, aCapEngine, unique_id, capability_number); mReplyCapability = capability; - LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError, - kSuccess); + LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); mReplyCapability = nullptr; return dispatcher.ReturnValue(); } @@ -303,8 +292,7 @@ int CamerasChild::GetCaptureDevice( mozilla::NewRunnableMethod<CaptureEngine, unsigned int>( "camera::PCamerasChild::SendGetCaptureDevice", this, &CamerasChild::SendGetCaptureDevice, aCapEngine, list_number); - LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError, - kSuccess); + LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); if (dispatcher.Success()) { base::strlcpy(device_nameUTF8, mReplyDeviceName.get(), device_nameUTF8Length); @@ -340,8 +328,7 @@ int CamerasChild::AllocateCapture(CaptureEngine aCapEngine, mozilla::NewRunnableMethod<CaptureEngine, nsCString, uint64_t>( "camera::PCamerasChild::SendAllocateCapture", this, &CamerasChild::SendAllocateCapture, aCapEngine, unique_id, aWindowID); - LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError, - mReplyInteger); + LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mReplyInteger); if (dispatcher.Success()) { LOG(("Capture Device allocated: %d", mReplyInteger)); } @@ -366,8 +353,7 @@ int CamerasChild::ReleaseCapture(CaptureEngine aCapEngine, mozilla::NewRunnableMethod<CaptureEngine, int>( "camera::PCamerasChild::SendReleaseCapture", this, &CamerasChild::SendReleaseCapture, aCapEngine, capture_id); - LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError, - kSuccess); + LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); return dispatcher.ReturnValue(); } @@ -415,8 +401,7 @@ int CamerasChild::StartCapture(CaptureEngine aCapEngine, const int capture_id, "camera::PCamerasChild::SendStartCapture", this, &CamerasChild::SendStartCapture, aCapEngine, capture_id, capCap, constraints, resize_mode); - LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError, - kSuccess); + LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); return dispatcher.ReturnValue(); } @@ -427,8 +412,7 @@ int CamerasChild::FocusOnSelectedSource(CaptureEngine aCapEngine, mozilla::NewRunnableMethod<CaptureEngine, int>( "camera::PCamerasChild::SendFocusOnSelectedSource", this, &CamerasChild::SendFocusOnSelectedSource, aCapEngine, aCaptureId); - LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError, - kSuccess); + LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); return dispatcher.ReturnValue(); } @@ -438,9 +422,8 @@ int CamerasChild::StopCapture(CaptureEngine aCapEngine, const int capture_id) { mozilla::NewRunnableMethod<CaptureEngine, int>( "camera::PCamerasChild::SendStopCapture", this, &CamerasChild::SendStopCapture, aCapEngine, capture_id); - LockAndDispatch<> dispatcher(this, __func__, runnable, kError, kIpcError, - kSuccess); - if (dispatcher.Success() || dispatcher.Disconnected()) { + LockAndDispatch<> dispatcher(this, __func__, runnable, -1, mZero); + if (dispatcher.Success()) { RemoveCallback(capture_id); } return dispatcher.ReturnValue(); @@ -493,32 +476,27 @@ void Shutdown(void) { CamerasSingleton::Thread() = nullptr; } -mozilla::ipc::IPCResult CamerasChild::RecvCaptureEnded( - nsTArray<int>&& aCaptureIds) { +mozilla::ipc::IPCResult CamerasChild::RecvCaptureEnded(const int& capId) { MutexAutoLock lock(mCallbackMutex); - for (int capId : aCaptureIds) { - if (auto* cb = Callback(capId)) { - cb->OnCaptureEnded(); - } else { - LOG(("CaptureEnded called with dead callback")); - } + if (Callback(capId)) { + Callback(capId)->OnCaptureEnded(); + } else { + LOG(("CaptureEnded called with dead callback")); } return IPC_OK(); } mozilla::ipc::IPCResult CamerasChild::RecvDeliverFrame( - const int& aCaptureId, nsTArray<int>&& aStreamIds, - mozilla::ipc::Shmem&& aShmem, const VideoFrameProperties& aProps) { + const int& capId, mozilla::ipc::Shmem&& shmem, + const VideoFrameProperties& prop) { MutexAutoLock lock(mCallbackMutex); - for (const int& streamId : aStreamIds) { - if (auto* cb = Callback(streamId)) { - unsigned char* image = aShmem.get<unsigned char>(); - cb->DeliverFrame(image, aProps); - } else { - LOG(("DeliverFrame called with dead callback")); - } + if (Callback(capId)) { + unsigned char* image = shmem.get<unsigned char>(); + Callback(capId)->DeliverFrame(image, prop); + } else { + LOG(("DeliverFrame called with dead callback")); } - SendReleaseFrame(aCaptureId, std::move(aShmem)); + SendReleaseFrame(std::move(shmem)); return IPC_OK(); } @@ -543,6 +521,7 @@ CamerasChild::CamerasChild() mReplyMonitor("mozilla::cameras::CamerasChild::mReplyMonitor"), mReceivedReply(false), mReplySuccess(false), + mZero(0), mReplyInteger(0), mReplyScary(false) { LOG(("CamerasChild: %p", this)); diff --git a/dom/media/systemservices/CamerasChild.h b/dom/media/systemservices/CamerasChild.h @@ -45,10 +45,6 @@ class CamerasChild; template <class T> class LockAndDispatch; -static constexpr int kSuccess = 0; -static constexpr int kError = -1; -static constexpr int kIpcError = -2; - // We emulate the sync webrtc.org API with the help of singleton // CamerasSingleton, which manages a pointer to an IPC object, a thread // where IPC operations should run on, and a mutex. @@ -150,12 +146,10 @@ class CamerasChild final : public PCamerasChild { // IPC messages recevied, received on the PBackground thread // these are the actual callbacks with data - mozilla::ipc::IPCResult RecvCaptureEnded( - nsTArray<int>&& aCaptureIds) override; + mozilla::ipc::IPCResult RecvCaptureEnded(const int&) override; mozilla::ipc::IPCResult RecvDeliverFrame( - const int& aCaptureId, nsTArray<int>&& aStreamIds, - mozilla::ipc::Shmem&& aShmem, - const VideoFrameProperties& aProps) override; + const int&, mozilla::ipc::Shmem&&, + const VideoFrameProperties& prop) override; mozilla::ipc::IPCResult RecvDeviceChange() override; @@ -227,13 +221,7 @@ class CamerasChild final : public PCamerasChild { ~CamerasChild(); // Dispatch a Runnable to the PCamerasParent, by executing it on the // decidecated Cameras IPC/PBackground thread. - enum class DispatchToParentResult : int8_t { - SUCCESS = 0, - FAILURE = -1, - DISCONNECTED = -2, - }; - DispatchToParentResult DispatchToParent(nsIRunnable* aRunnable, - MonitorAutoLock& aMonitor); + bool DispatchToParent(nsIRunnable* aRunnable, MonitorAutoLock& aMonitor); void AddCallback(int capture_id, FrameRelay* render); void RemoveCallback(int capture_id); @@ -260,6 +248,7 @@ class CamerasChild final : public PCamerasChild { bool mReceivedReply; // Async responses data contents; bool mReplySuccess; + const int mZero; int mReplyInteger; webrtc::VideoCaptureCapability* mReplyCapability = nullptr; nsCString mReplyDeviceName; diff --git a/dom/media/systemservices/CamerasParent.cpp b/dom/media/systemservices/CamerasParent.cpp @@ -57,6 +57,10 @@ using dom::VideoResizeModeEnum; using media::ShutdownBlockingTicket; namespace camera { +MOZ_RUNINIT std::map<uint32_t, const char*> sDeviceUniqueIDs; +MOZ_RUNINIT std::map<uint32_t, webrtc::VideoCaptureCapability> + sAllRequestedCapabilities; + uint32_t ResolutionFeasibilityDistance(int32_t candidate, int32_t requested) { // The purpose of this function is to find a smallest resolution // which is larger than all requested capabilities. @@ -115,12 +119,6 @@ static StaticRefPtr<nsIThread> sVideoCaptureThread; // objects. Created on IPC background thread, destroyed on main thread on // shutdown. Outlives the CamerasParent instances. static StaticRefPtr<VideoCaptureFactory> sVideoCaptureFactory; -// All live capturers across all CamerasParent instances. The array and its -// members are only modified on the video capture thread. The outermost refcount -// is IPC background thread only. -static StaticRefPtr< - media::Refcountable<nsTArray<std::unique_ptr<AggregateCapturer>>>> - sCapturers; static void ClearCameraDeviceInfo() { ipc::AssertIsOnBackgroundThread(); @@ -198,9 +196,6 @@ MakeAndAddRefVideoCaptureThreadAndSingletons() { sEngines = MakeRefPtr<VideoEngineArray>(); sEngines->AppendElements(CaptureEngine::MaxEngine); - - sCapturers = MakeRefPtr< - media::Refcountable<nsTArray<std::unique_ptr<AggregateCapturer>>>>(); } ++sNumCamerasParents; @@ -219,10 +214,8 @@ static void ReleaseVideoCaptureThreadAndSingletons() { // No other CamerasParent instances alive. Clean up. LOG("Shutting down VideoEngines and the VideoCapture thread"); - MOZ_ALWAYS_SUCCEEDS(sVideoCaptureThread->Dispatch(NS_NewRunnableFunction( - __func__, [engines = RefPtr(sEngines.forget()), - capturers = RefPtr(sCapturers.forget())] { - MOZ_ASSERT(capturers->IsEmpty(), "No capturers expected on shutdown"); + MOZ_ALWAYS_SUCCEEDS(sVideoCaptureThread->Dispatch( + NS_NewRunnableFunction(__func__, [engines = RefPtr(sEngines.forget())] { for (RefPtr<VideoEngine>& engine : *engines) { if (engine) { VideoEngine::Delete(engine); @@ -258,34 +251,43 @@ void CamerasParent::OnDeviceChange() { class DeliverFrameRunnable : public mozilla::Runnable { public: - // Constructor for when no ShmemBuffer (of the right size) was available, so - // keep the frame around until we can allocate one on PBackground (in Run). DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine, - int aCaptureId, nsTArray<int>&& aStreamIds, - const TrackingId& aTrackingId, + uint32_t aStreamId, const TrackingId& aTrackingId, const webrtc::VideoFrame& aFrame, const VideoFrameProperties& aProperties) : Runnable("camera::DeliverFrameRunnable"), mParent(aParent), mCapEngine(aEngine), - mCaptureId(aCaptureId), - mStreamIds(std::move(aStreamIds)), + mStreamId(aStreamId), mTrackingId(aTrackingId), - mBuffer(aFrame), - mProperties(aProperties) {} + mProperties(aProperties), + mResult(0) { + // No ShmemBuffer (of the right size) was available, so make an + // extra buffer here. We have no idea when we are going to run and + // it will be potentially long after the webrtc frame callback has + // returned, so the copy needs to be no later than here. + // We will need to copy this back into a Shmem later on so we prefer + // using ShmemBuffers to avoid the extra copy. + PerformanceRecorder<CopyVideoStage> rec( + "CamerasParent::VideoFrameToAltBuffer"_ns, aTrackingId, aFrame.width(), + aFrame.height()); + mAlternateBuffer.reset(new unsigned char[aProperties.bufferSize()]); + VideoFrameUtils::CopyVideoFrameBuffers(mAlternateBuffer.get(), + aProperties.bufferSize(), aFrame); + rec.Record(); + } DeliverFrameRunnable(CamerasParent* aParent, CaptureEngine aEngine, - int aCaptureId, nsTArray<int>&& aStreamIds, - const TrackingId& aTrackingId, ShmemBuffer aBuffer, - VideoFrameProperties& aProperties) + uint32_t aStreamId, const TrackingId& aTrackingId, + ShmemBuffer aBuffer, VideoFrameProperties& aProperties) : Runnable("camera::DeliverFrameRunnable"), mParent(aParent), mCapEngine(aEngine), - mCaptureId(aCaptureId), - mStreamIds(std::move(aStreamIds)), + mStreamId(aStreamId), mTrackingId(aTrackingId), mBuffer(std::move(aBuffer)), - mProperties(aProperties) {} + mProperties(aProperties), + mResult(0) {}; NS_IMETHOD Run() override { // runs on BackgroundEventTarget @@ -293,43 +295,45 @@ class DeliverFrameRunnable : public mozilla::Runnable { mParent->mPBackgroundEventTarget); if (mParent->IsShuttingDown()) { // Communication channel is being torn down + mResult = 0; return NS_OK; } - mParent->DeliverFrameOverIPC(mCapEngine, mCaptureId, mStreamIds, - mTrackingId, std::move(mBuffer), mProperties); + if (!mParent->DeliverFrameOverIPC(mCapEngine, mStreamId, mTrackingId, + std::move(mBuffer), + mAlternateBuffer.get(), mProperties)) { + mResult = -1; + } else { + mResult = 0; + } return NS_OK; } + int GetResult() { return mResult; } + private: const RefPtr<CamerasParent> mParent; const CaptureEngine mCapEngine; - const int mCaptureId; - const nsTArray<int> mStreamIds; + const uint32_t mStreamId; const TrackingId mTrackingId; - Variant<ShmemBuffer, webrtc::VideoFrame> mBuffer; + ShmemBuffer mBuffer; + UniquePtr<unsigned char[]> mAlternateBuffer; const VideoFrameProperties mProperties; + int mResult; }; -int CamerasParent::DeliverFrameOverIPC( - CaptureEngine aCapEngine, int aCaptureId, const Span<const int>& aStreamIds, - const TrackingId& aTrackingId, - Variant<ShmemBuffer, webrtc::VideoFrame>&& aBuffer, - const VideoFrameProperties& aProps) { +int CamerasParent::DeliverFrameOverIPC(CaptureEngine aCapEngine, + uint32_t aStreamId, + const TrackingId& aTrackingId, + ShmemBuffer aBuffer, + unsigned char* aAltBuffer, + const VideoFrameProperties& aProps) { // No ShmemBuffers were available, so construct one now of the right size // and copy into it. That is an extra copy, but we expect this to be // the exceptional case, because we just assured the next call *will* have a // buffer of the right size. - if (!aBuffer.is<ShmemBuffer>()) { + if (aAltBuffer != nullptr) { // Get a shared memory buffer from the pool, at least size big - ShmemBuffer shMemBuff; - { - auto guard = mShmemPools.Lock(); - auto it = guard->find(aCaptureId); - if (it != guard->end()) { - auto& [_, pool] = *it; - shMemBuff = pool.Get(this, aProps.bufferSize()); - } - } + ShmemBuffer shMemBuff = mShmemPool.Get(this, aProps.bufferSize()); if (!shMemBuff.Valid()) { LOG("No usable Video shmem in DeliverFrame (out of buffers?)"); @@ -340,20 +344,18 @@ int CamerasParent::DeliverFrameOverIPC( PerformanceRecorder<CopyVideoStage> rec( "CamerasParent::AltBufferToShmem"_ns, aTrackingId, aProps.width(), aProps.height()); - VideoFrameUtils::CopyVideoFrameBuffers(shMemBuff, - aBuffer.as<webrtc::VideoFrame>()); + // get() and Size() check for proper alignment of the segment + memcpy(shMemBuff.GetBytes(), aAltBuffer, aProps.bufferSize()); rec.Record(); - if (!SendDeliverFrame(aCaptureId, aStreamIds, std::move(shMemBuff.Get()), - aProps)) { + if (!SendDeliverFrame(aStreamId, std::move(shMemBuff.Get()), aProps)) { return -1; } } else { - MOZ_ASSERT(aBuffer.as<ShmemBuffer>().Valid()); + MOZ_ASSERT(aBuffer.Valid()); // ShmemBuffer was available, we're all good. A single copy happened // in the original webrtc callback. - if (!SendDeliverFrame(aCaptureId, aStreamIds, - std::move(aBuffer.as<ShmemBuffer>().Get()), aProps)) { + if (!SendDeliverFrame(aStreamId, std::move(aBuffer.Get()), aProps)) { return -1; } } @@ -361,275 +363,57 @@ int CamerasParent::DeliverFrameOverIPC( return 0; } -bool CamerasParent::IsWindowCapturing(uint64_t aWindowId, - const nsACString& aUniqueId) const { - MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); - for (const auto& capturer : *mCapturers) { - if (capturer->mUniqueId != aUniqueId) { - continue; - } - auto streamsGuard = capturer->mStreams.ConstLock(); - for (const auto& stream : *streamsGuard) { - if (stream->mWindowId == aWindowId) { - return true; - } - } - } - return false; -} - -ShmemBuffer CamerasParent::GetBuffer(int aCaptureId, size_t aSize) { - auto guard = mShmemPools.Lock(); - auto it = guard->find(aCaptureId); - if (it == guard->end()) { - return ShmemBuffer(); - } - auto& [_, pool] = *it; - return pool.GetIfAvailable(aSize); -} - -/*static*/ -std::unique_ptr<AggregateCapturer> AggregateCapturer::Create( - nsISerialEventTarget* aVideoCaptureThread, CaptureEngine aCapEng, - VideoEngine* aEngine, const nsCString& aUniqueId, uint64_t aWindowId, - nsTArray<webrtc::VideoCaptureCapability>&& aCapabilities, - CamerasParent* aParent) { - MOZ_ASSERT(aVideoCaptureThread->IsOnCurrentThread()); - int captureId = aEngine->CreateVideoCapture(aUniqueId.get(), aWindowId); - auto capturer = WrapUnique( - new AggregateCapturer(aVideoCaptureThread, aCapEng, aEngine, aUniqueId, - captureId, std::move(aCapabilities))); - capturer->AddStream(aParent, captureId, aWindowId); - aEngine->WithEntry(captureId, [&](VideoEngine::CaptureEntry& aEntry) -> void { - aEntry.VideoCapture()->SetTrackingId(capturer->mTrackingId.mUniqueInProcId); - aEntry.VideoCapture()->RegisterCaptureDataCallback(capturer.get()); - if (auto* event = aEntry.CaptureEndedEvent()) { - capturer->mCaptureEndedListener = - event->Connect(aVideoCaptureThread, capturer.get(), - &AggregateCapturer::OnCaptureEnded); - } - }); - return capturer; -} - -AggregateCapturer::AggregateCapturer( - nsISerialEventTarget* aVideoCaptureThread, CaptureEngine aCapEng, - VideoEngine* aEngine, const nsCString& aUniqueId, int aCaptureId, - nsTArray<webrtc::VideoCaptureCapability>&& aCapabilities) - : mVideoCaptureThread(aVideoCaptureThread), - mCapEngine(aCapEng), - mEngine(aEngine), - mUniqueId(aUniqueId), - mCaptureId(aCaptureId), - mTrackingId(CaptureEngineToTrackingSourceStr(aCapEng), mCaptureId), - mCapabilities(std::move(aCapabilities)), - mStreams("CallbackHelper::mStreams") { - MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); -} - -AggregateCapturer::~AggregateCapturer() { - MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); -#ifdef DEBUG - { - auto streamsGuard = mStreams.Lock(); - MOZ_ASSERT(streamsGuard->IsEmpty()); - } -#endif - mCaptureEndedListener.DisconnectIfExists(); - mEngine->WithEntry(mCaptureId, [&](VideoEngine::CaptureEntry& aEntry) { - if (auto* cap = aEntry.VideoCapture().get()) { - cap->DeRegisterCaptureDataCallback(this); - cap->StopCaptureIfAllClientsClose(); - } - }); - MOZ_ALWAYS_FALSE(mEngine->ReleaseVideoCapture(mCaptureId)); -} - -void AggregateCapturer::AddStream(CamerasParent* aParent, int aStreamId, - uint64_t aWindowId) { - auto streamsGuard = mStreams.Lock(); -#ifdef DEBUG - for (const auto& stream : *streamsGuard) { - MOZ_ASSERT(stream->mId != aStreamId); - } -#endif - streamsGuard->AppendElement( - new Stream{.mParent = aParent, .mId = aStreamId, .mWindowId = aWindowId}); -} - -auto AggregateCapturer::RemoveStream(int aStreamId) -> RemoveStreamResult { - auto streamsGuard = mStreams.Lock(); - size_t idx = streamsGuard->IndexOf( - aStreamId, 0, - [](const auto& aElem, const auto& aId) { return aElem->mId - aId; }); - if (idx == streamsGuard->NoIndex) { - return {.mNumRemainingStreams = 0, .mNumRemainingStreamsForParent = 0}; - } - CamerasParent* parent = streamsGuard->ElementAt(idx)->mParent; - streamsGuard->RemoveElementAt(idx); - size_t remainingForParent = 0; - for (const auto& s : *streamsGuard) { - if (s->mParent == parent) { - remainingForParent += 1; - } - } - return {.mNumRemainingStreams = streamsGuard->Length(), - .mNumRemainingStreamsForParent = remainingForParent}; -} - -auto AggregateCapturer::RemoveStreamsFor(CamerasParent* aParent) - -> RemoveStreamResult { - auto streamsGuard = mStreams.Lock(); - streamsGuard->RemoveElementsBy( - [&](const auto& aElem) { return aElem->mParent == aParent; }); - return {.mNumRemainingStreams = streamsGuard->Length(), - .mNumRemainingStreamsForParent = 0}; -} - -Maybe<int> AggregateCapturer::CaptureIdFor(int aStreamId) { - auto streamsGuard = mStreams.Lock(); - for (auto& stream : *streamsGuard) { - if (stream->mId == aStreamId) { - return Some(mCaptureId); - } - } - return Nothing(); +ShmemBuffer CamerasParent::GetBuffer(size_t aSize) { + return mShmemPool.GetIfAvailable(aSize); } -void AggregateCapturer::SetConfigurationFor( - int aStreamId, const webrtc::VideoCaptureCapability& aCapability, +void CallbackHelper::SetConfiguration( + const webrtc::VideoCaptureCapability& aCapability, const NormalizedConstraints& aConstraints, - const dom::VideoResizeModeEnum& aResizeMode, bool aStarted) { - auto streamsGuard = mStreams.Lock(); - for (auto& stream : *streamsGuard) { - if (stream->mId == aStreamId) { - stream->mConfiguration = { - .mCapability = aCapability, - .mConstraints = aConstraints, - .mResizeMode = aResizeMode, - }; - stream->mStarted = aStarted; - break; - } - } -} - -webrtc::VideoCaptureCapability AggregateCapturer::CombinedCapability() { - Maybe<webrtc::VideoCaptureCapability> combinedCap; - CamerasParent* someParent{}; - const auto streamsGuard = mStreams.ConstLock(); - for (const auto& stream : *streamsGuard) { - if (!stream->mStarted) { - continue; - } - if (!someParent) { - someParent = stream->mParent; - } - const auto& cap = stream->mConfiguration.mCapability; - if (!combinedCap) { - combinedCap = Some(cap); - continue; - } - auto combinedRes = combinedCap->width * combinedCap->height; - combinedCap->maxFPS = std::max(combinedCap->maxFPS, cap.maxFPS); - if (mCapEngine == CaptureEngine::CameraEngine) { - auto newCombinedRes = cap.width * cap.height; - if (newCombinedRes > combinedRes) { - combinedCap->videoType = cap.videoType; - } - combinedCap->width = std::max(combinedCap->width, cap.width); - combinedCap->height = std::max(combinedCap->height, cap.height); - } - } - if (mCapEngine == CameraEngine) { - const webrtc::VideoCaptureCapability* minDistanceCapability{}; - uint64_t minDistance = UINT64_MAX; - - for (const auto& candidateCapability : mCapabilities) { - if (candidateCapability.videoType != combinedCap->videoType) { - continue; - } - // The first priority is finding a suitable resolution. - // So here we raise the weight of width and height - uint64_t distance = - uint64_t(ResolutionFeasibilityDistance(candidateCapability.width, - combinedCap->width)) + - uint64_t(ResolutionFeasibilityDistance(candidateCapability.height, - combinedCap->height)) + - uint64_t(FeasibilityDistance(candidateCapability.maxFPS, - combinedCap->maxFPS)); - if (distance < minDistance) { - minDistanceCapability = &candidateCapability; - minDistance = distance; - } - } - if (minDistanceCapability) { - combinedCap = Some(*minDistanceCapability); - } - } - return combinedCap.extract(); + const dom::VideoResizeModeEnum& aResizeMode) { + auto c = mConfiguration.Lock(); + c.ref() = Configuration{ + .mCapability = aCapability, + .mConstraints = aConstraints, + .mResizeMode = aResizeMode, + }; } -void AggregateCapturer::OnCaptureEnded() { - MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); - std::multimap<CamerasParent*, int> parentsAndIds; - { - auto streamsGuard = mStreams.Lock(); - for (const auto& stream : *streamsGuard) { - parentsAndIds.insert({stream->mParent, stream->mId}); - } - } +void CallbackHelper::OnCaptureEnded() { + nsIEventTarget* target = mParent->GetBackgroundEventTarget(); - for (auto it = parentsAndIds.begin(); it != parentsAndIds.end();) { - const auto& parent = it->first; - auto nextParentIt = parentsAndIds.upper_bound(parent); - AutoTArray<int, 4> ids; - while (it != nextParentIt) { - const auto& [_, id] = *it; - ids.AppendElement(id); - ++it; - } - nsIEventTarget* target = parent->GetBackgroundEventTarget(); - MOZ_ALWAYS_SUCCEEDS(target->Dispatch(NS_NewRunnableFunction( - __func__, [parent = RefPtr(parent), ids = std::move(ids)] { - (void)parent->SendCaptureEnded(ids); - }))); - } + MOZ_ALWAYS_SUCCEEDS(target->Dispatch(NS_NewRunnableFunction( + __func__, [parent = RefPtr(mParent), id = mStreamId] { + (void)parent->SendCaptureEnded(id); + }))); } -void AggregateCapturer::OnFrame(const webrtc::VideoFrame& aVideoFrame) { - std::multimap<CamerasParent*, int> parentsAndIds; +void CallbackHelper::OnFrame(const webrtc::VideoFrame& aVideoFrame) { { // Proactively drop frames that would not get processed anyway. - auto streamsGuard = mStreams.Lock(); - - for (auto& stream : *streamsGuard) { - auto& c = stream->mConfiguration; - const double maxFramerate = static_cast<double>( - c.mCapability.maxFPS > 0 ? c.mCapability.maxFPS : 120); - const double desiredFramerate = - c.mResizeMode == VideoResizeModeEnum::Crop_and_scale - ? c.mConstraints.mFrameRate.Get(maxFramerate) - : maxFramerate; - const double targetFramerate = std::clamp(desiredFramerate, 0.01, 120.); - - // Allow 5% higher fps than configured as frame time sampling is timing - // dependent. - const auto minInterval = - media::TimeUnit(1000, static_cast<int64_t>(1050 * targetFramerate)); - const auto frameTime = - media::TimeUnit::FromMicroseconds(aVideoFrame.timestamp_us()); - const auto frameInterval = frameTime - stream->mLastFrameTime; - if (frameInterval < minInterval) { - continue; - } - stream->mLastFrameTime = frameTime; - LOG_VERBOSE("CamerasParent::%s parent=%p, id=%d.", __func__, - stream->mParent, stream->mId); - parentsAndIds.insert({stream->mParent, stream->mId}); + auto c = mConfiguration.Lock(); + + const double maxFramerate = static_cast<double>( + c->mCapability.maxFPS > 0 ? c->mCapability.maxFPS : 120); + const double desiredFramerate = + c->mResizeMode == VideoResizeModeEnum::Crop_and_scale + ? c->mConstraints.mFrameRate.Get(maxFramerate) + : maxFramerate; + const double targetFramerate = std::clamp(desiredFramerate, 0.01, 120.); + + // Allow 5% higher fps than configured as frame time sampling is timing + // dependent. + const auto minInterval = + media::TimeUnit(1000, static_cast<int64_t>(1050 * targetFramerate)); + const auto frameTime = + media::TimeUnit::FromMicroseconds(aVideoFrame.timestamp_us()); + const auto frameInterval = frameTime - mLastFrameTime; + if (frameInterval < minInterval) { + return; } + mLastFrameTime = frameTime; } - + LOG_VERBOSE("CamerasParent(%p)::%s", mParent, __func__); if (profiler_thread_is_being_profiled_for_markers()) { PROFILER_MARKER_UNTYPED( nsPrintfCString("CaptureVideoFrame %dx%d %s %s", aVideoFrame.width(), @@ -639,68 +423,43 @@ void AggregateCapturer::OnFrame(const webrtc::VideoFrame& aVideoFrame) { mTrackingId.ToString().get()), MEDIA_RT); } - + RefPtr<DeliverFrameRunnable> runnable = nullptr; // Get frame properties camera::VideoFrameProperties properties; VideoFrameUtils::InitFrameBufferProperties(aVideoFrame, properties); - - for (auto it = parentsAndIds.begin(); it != parentsAndIds.end();) { - const auto& parent = it->first; - auto nextParentIt = parentsAndIds.upper_bound(parent); - AutoTArray<int, 4> ids; - while (it != nextParentIt) { - const auto& [_, id] = *it; - ids.AppendElement(id); - ++it; - } - - LOG_VERBOSE("CamerasParent(%p)::%s", parent, __func__); - RefPtr<DeliverFrameRunnable> runnable = nullptr; - // Get a shared memory buffer to copy the frame data into - ShmemBuffer shMemBuffer = - parent->GetBuffer(mCaptureId, properties.bufferSize()); - if (!shMemBuffer.Valid()) { - // Either we ran out of buffers or they're not the right size yet - LOG("Correctly sized Video shmem not available in DeliverFrame"); - // We will do the copy into a(n extra) temporary buffer inside - // the DeliverFrameRunnable constructor. - } else { - // Shared memory buffers of the right size are available, do the copy - // here. - PerformanceRecorder<CopyVideoStage> rec( - "CamerasParent::VideoFrameToShmem"_ns, mTrackingId, - aVideoFrame.width(), aVideoFrame.height()); - VideoFrameUtils::CopyVideoFrameBuffers( - shMemBuffer.GetBytes(), properties.bufferSize(), aVideoFrame); - rec.Record(); - runnable = new DeliverFrameRunnable(parent, mCapEngine, mCaptureId, - std::move(ids), mTrackingId, - std::move(shMemBuffer), properties); - } - if (!runnable) { - runnable = new DeliverFrameRunnable(parent, mCapEngine, mCaptureId, - std::move(ids), mTrackingId, - aVideoFrame, properties); - } - nsIEventTarget* target = parent->GetBackgroundEventTarget(); - target->Dispatch(runnable, NS_DISPATCH_NORMAL); + // Get a shared memory buffer to copy the frame data into + ShmemBuffer shMemBuffer = mParent->GetBuffer(properties.bufferSize()); + if (!shMemBuffer.Valid()) { + // Either we ran out of buffers or they're not the right size yet + LOG("Correctly sized Video shmem not available in DeliverFrame"); + // We will do the copy into a(n extra) temporary buffer inside + // the DeliverFrameRunnable constructor. + } else { + // Shared memory buffers of the right size are available, do the copy here. + PerformanceRecorder<CopyVideoStage> rec( + "CamerasParent::VideoFrameToShmem"_ns, mTrackingId, aVideoFrame.width(), + aVideoFrame.height()); + VideoFrameUtils::CopyVideoFrameBuffers( + shMemBuffer.GetBytes(), properties.bufferSize(), aVideoFrame); + rec.Record(); + runnable = + new DeliverFrameRunnable(mParent, mCapEngine, mStreamId, mTrackingId, + std::move(shMemBuffer), properties); } + if (!runnable) { + runnable = new DeliverFrameRunnable(mParent, mCapEngine, mStreamId, + mTrackingId, aVideoFrame, properties); + } + MOZ_ASSERT(mParent); + nsIEventTarget* target = mParent->GetBackgroundEventTarget(); + MOZ_ASSERT(target != nullptr); + target->Dispatch(runnable, NS_DISPATCH_NORMAL); } -ipc::IPCResult CamerasParent::RecvReleaseFrame(const int& aCaptureId, - ipc::Shmem&& aShmem) { +ipc::IPCResult CamerasParent::RecvReleaseFrame(ipc::Shmem&& aShmem) { MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread()); - auto guard = mShmemPools.Lock(); - auto it = guard->find(aCaptureId); - if (it == guard->end()) { - MOZ_ASSERT_UNREACHABLE( - "Releasing shmem but pool is already gone. Shmem must have been " - "deallocated."); - return IPC_FAIL(this, "Shmem was already deallocated"); - } - auto& [_, pool] = *it; - pool.Put(ShmemBuffer(aShmem)); + mShmemPool.Put(ShmemBuffer(aShmem)); return IPC_OK(); } @@ -708,16 +467,13 @@ void CamerasParent::CloseEngines() { MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); LOG_FUNCTION(); - // Stop the capturers. - for (const auto& capturer : Reversed(*mCapturers)) { - auto removed = capturer->RemoveStreamsFor(this); - if (removed.mNumRemainingStreams == 0) { - auto capEngine = capturer->mCapEngine; - auto captureId = capturer->mCaptureId; - size_t idx = mCapturers->LastIndexOf(capturer); - mCapturers->RemoveElementAtUnsafe(idx); - LOG("Forcing shutdown of engine %d, capturer %d", capEngine, captureId); - } + // Stop the callers + while (!mCallbacks.IsEmpty()) { + auto capEngine = mCallbacks[0]->mCapEngine; + auto streamNum = mCallbacks[0]->mStreamId; + LOG("Forcing shutdown of engine %d, capturer %d", capEngine, streamNum); + StopCapture(capEngine, streamNum); + (void)ReleaseCapture(capEngine, streamNum); } mDeviceChangeEventListener.DisconnectIfExists(); @@ -919,21 +675,30 @@ ipc::IPCResult CamerasParent::RecvGetCaptureCapability( InvokeAsync(mVideoCaptureThread, __func__, [this, self = RefPtr(this), id = nsCString(aUniqueId), aCapEngine, aIndex] { - nsTArray<webrtc::VideoCaptureCapability> const* capabilities = - EnsureCapabilitiesPopulated(aCapEngine, id); webrtc::VideoCaptureCapability webrtcCaps; - if (!capabilities) { - return Promise::CreateAndReject( - -1, "CamerasParent::RecvGetCaptureCapability"); + int error = -1; + if (auto devInfo = GetDeviceInfo(aCapEngine)) { + error = devInfo->GetCapability(id.get(), aIndex, webrtcCaps); + } + + if (!error && aCapEngine == CameraEngine) { + auto iter = mAllCandidateCapabilities.find(id); + if (iter == mAllCandidateCapabilities.end()) { + std::map<uint32_t, webrtc::VideoCaptureCapability> + candidateCapabilities; + candidateCapabilities.emplace(aIndex, webrtcCaps); + mAllCandidateCapabilities.emplace(id, + candidateCapabilities); + } else { + (iter->second).emplace(aIndex, webrtcCaps); + } } - if (aIndex < 0 || - static_cast<size_t>(aIndex) >= capabilities->Length()) { + if (error) { return Promise::CreateAndReject( - -2, "CamerasParent::RecvGetCaptureCapability"); + error, "CamerasParent::RecvGetCaptureCapability"); } return Promise::CreateAndResolve( - capabilities->ElementAt(aIndex), - "CamerasParent::RecvGetCaptureCapability"); + webrtcCaps, "CamerasParent::RecvGetCaptureCapability"); }) ->Then( mPBackgroundEventTarget, __func__, @@ -1089,66 +854,46 @@ ipc::IPCResult CamerasParent::RecvAllocateCapture( using Promise1 = MozPromise<bool, bool, true>; using Data = std::tuple<int, int>; using Promise2 = MozPromise<Data, bool, true>; - InvokeAsync( - GetMainThreadSerialEventTarget(), __func__, - [aWindowID] { - // Verify whether the claimed origin has received permission - // to use the camera, either persistently or this session (one - // shot). - bool allowed = HasCameraPermission(aWindowID); - if (!allowed && Preferences::GetBool( - "media.navigator.permission.disabled", false)) { - // Developer preference for turning off permission check. - allowed = true; - LOG("No permission but checks are disabled"); - } - if (!allowed) { - LOG("No camera permission for this origin"); - } - return Promise1::CreateAndResolve(allowed, - "CamerasParent::RecvAllocateCapture"); - }) - ->Then( - mVideoCaptureThread, __func__, - [this, self = RefPtr(this), aCapEngine, aWindowID, - unique_id = nsCString(aUniqueIdUTF8)]( - Promise1::ResolveOrRejectValue&& aValue) { - VideoEngine* engine = EnsureInitialized(aCapEngine); - if (!engine) { - return Promise2::CreateAndResolve( - std::make_tuple(-1, -1), - "CamerasParent::RecvAllocateCapture no engine"); - } - bool allowed = aValue.ResolveValue(); - if (!allowed && IsWindowCapturing(aWindowID, unique_id)) { - allowed = true; - LOG("No permission but window is already capturing this device"); - } - if (!allowed) { - return Promise2::CreateAndResolve( - std::make_tuple(-1, -1), - "CamerasParent::RecvAllocateCapture"); - } - - nsTArray<webrtc::VideoCaptureCapability> capabilities; - if (const auto* caps = - EnsureCapabilitiesPopulated(aCapEngine, unique_id)) { - capabilities.AppendElements(*caps); - } - - auto created = GetOrCreateCapturer(aCapEngine, aWindowID, unique_id, - std::move(capabilities)); - int error = -1; - engine->WithEntry(created.mCapturer->mCaptureId, - [&](VideoEngine::CaptureEntry& cap) { - if (cap.VideoCapture()) { - error = 0; - } - }); - return Promise2::CreateAndResolve( - std::make_tuple(created.mStreamId, error), - "CamerasParent::RecvAllocateCapture"); - }) + InvokeAsync(GetMainThreadSerialEventTarget(), __func__, + [aWindowID] { + // Verify whether the claimed origin has received permission + // to use the camera, either persistently or this session (one + // shot). + bool allowed = HasCameraPermission(aWindowID); + if (!allowed) { + // Developer preference for turning off permission check. + if (Preferences::GetBool( + "media.navigator.permission.disabled", false)) { + allowed = true; + LOG("No permission but checks are disabled"); + } else { + LOG("No camera permission for this origin"); + } + } + return Promise1::CreateAndResolve( + allowed, "CamerasParent::RecvAllocateCapture"); + }) + ->Then(mVideoCaptureThread, __func__, + [this, self = RefPtr(this), aCapEngine, + unique_id = nsCString(aUniqueIdUTF8)]( + Promise1::ResolveOrRejectValue&& aValue) { + bool allowed = aValue.ResolveValue(); + int captureId = -1; + int error = -1; + if (allowed && EnsureInitialized(aCapEngine)) { + VideoEngine* engine = mEngines->ElementAt(aCapEngine); + captureId = engine->CreateVideoCapture(unique_id.get()); + engine->WithEntry(captureId, + [&error](VideoEngine::CaptureEntry& cap) { + if (cap.VideoCapture()) { + error = 0; + } + }); + } + return Promise2::CreateAndResolve( + std::make_tuple(captureId, error), + "CamerasParent::RecvAllocateCapture"); + }) ->Then( mPBackgroundEventTarget, __func__, [this, self = RefPtr(this)](Promise2::ResolveOrRejectValue&& aValue) { @@ -1170,24 +915,34 @@ ipc::IPCResult CamerasParent::RecvAllocateCapture( return IPC_OK(); } +int CamerasParent::ReleaseCapture(const CaptureEngine& aCapEngine, + int aCaptureId) { + MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); + int error = -1; + if (auto* engine = EnsureInitialized(aCapEngine)) { + error = engine->ReleaseVideoCapture(aCaptureId); + } + return error; +} + ipc::IPCResult CamerasParent::RecvReleaseCapture( - const CaptureEngine& aCapEngine, const int& aStreamId) { + const CaptureEngine& aCapEngine, const int& aCaptureId) { MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread()); MOZ_ASSERT(!mDestroyed); LOG_FUNCTION(); - LOG("RecvReleaseCapture stream nr %d", aStreamId); + LOG("RecvReleaseCamera device nr %d", aCaptureId); using Promise = MozPromise<int, bool, true>; InvokeAsync(mVideoCaptureThread, __func__, - [this, self = RefPtr(this), aCapEngine, aStreamId] { + [this, self = RefPtr(this), aCapEngine, aCaptureId] { return Promise::CreateAndResolve( - ReleaseStream(aCapEngine, aStreamId), + ReleaseCapture(aCapEngine, aCaptureId), "CamerasParent::RecvReleaseCapture"); }) ->Then(mPBackgroundEventTarget, __func__, [this, self = RefPtr(this), - aStreamId](Promise::ResolveOrRejectValue&& aValue) { + aCaptureId](Promise::ResolveOrRejectValue&& aValue) { int error = aValue.ResolveValue(); if (mDestroyed) { @@ -1197,19 +952,19 @@ ipc::IPCResult CamerasParent::RecvReleaseCapture( if (error != 0) { (void)SendReplyFailure(); - LOG("RecvReleaseCapture: Failed to free stream nr %d", - aStreamId); + LOG("RecvReleaseCapture: Failed to free device nr %d", + aCaptureId); return; } (void)SendReplySuccess(); - LOG("Freed stream nr %d", aStreamId); + LOG("Freed device nr %d", aCaptureId); }); return IPC_OK(); } ipc::IPCResult CamerasParent::RecvStartCapture( - const CaptureEngine& aCapEngine, const int& aStreamId, + const CaptureEngine& aCapEngine, const int& aCaptureId, const VideoCaptureCapability& aIpcCaps, const NormalizedConstraints& aConstraints, const dom::VideoResizeModeEnum& aResizeMode) { @@ -1221,24 +976,18 @@ ipc::IPCResult CamerasParent::RecvStartCapture( using Promise = MozPromise<int, bool, true>; InvokeAsync( mVideoCaptureThread, __func__, - [this, self = RefPtr(this), aCapEngine, aStreamId, aIpcCaps, aConstraints, - aResizeMode] { + [this, self = RefPtr(this), aCapEngine, aCaptureId, aIpcCaps, + aConstraints, aResizeMode] { LOG_FUNCTION(); + int error = -1; if (!EnsureInitialized(aCapEngine)) { - return Promise::CreateAndResolve(-1, + return Promise::CreateAndResolve(error, "CamerasParent::RecvStartCapture"); } - AggregateCapturer* cbh = GetCapturer(aCapEngine, aStreamId); - if (!cbh) { - return Promise::CreateAndResolve(-1, - "CamerasParent::RecvStartCapture"); - } - - int error = -1; mEngines->ElementAt(aCapEngine) - ->WithEntry(cbh->mCaptureId, [&](VideoEngine::CaptureEntry& cap) { + ->WithEntry(aCaptureId, [&](VideoEngine::CaptureEntry& cap) { webrtc::VideoCaptureCapability capability; capability.width = aIpcCaps.width(); capability.height = aIpcCaps.height(); @@ -1247,15 +996,123 @@ ipc::IPCResult CamerasParent::RecvStartCapture( static_cast<webrtc::VideoType>(aIpcCaps.videoType()); capability.interlaced = aIpcCaps.interlaced(); - if (cbh) { - cbh->SetConfigurationFor(aStreamId, capability, aConstraints, - aResizeMode, /*aStarted=*/true); - error = - cap.VideoCapture()->StartCapture(cbh->CombinedCapability()); - if (error) { - cbh->SetConfigurationFor(aStreamId, capability, aConstraints, - aResizeMode, /*aStarted=*/false); + if (sDeviceUniqueIDs.find(aCaptureId) == sDeviceUniqueIDs.end()) { + sDeviceUniqueIDs.emplace( + aCaptureId, cap.VideoCapture()->CurrentDeviceName()); + sAllRequestedCapabilities.emplace(aCaptureId, capability); + } else { + // Starting capture for an id that already exists. Update its + // requested capability. + MOZ_DIAGNOSTIC_ASSERT( + strcmp(sDeviceUniqueIDs[aCaptureId], + cap.VideoCapture()->CurrentDeviceName()) == 0); + MOZ_DIAGNOSTIC_ASSERT( + sAllRequestedCapabilities.find(aCaptureId) != + sAllRequestedCapabilities.end()); + sAllRequestedCapabilities[aCaptureId] = capability; + } + + if (aCapEngine == CameraEngine) { + for (const auto& it : sDeviceUniqueIDs) { + if (strcmp(it.second, + cap.VideoCapture()->CurrentDeviceName()) == 0) { + capability.width = + std::max(capability.width, + sAllRequestedCapabilities[it.first].width); + capability.height = + std::max(capability.height, + sAllRequestedCapabilities[it.first].height); + capability.maxFPS = + std::max(capability.maxFPS, + sAllRequestedCapabilities[it.first].maxFPS); + } + } + + auto candidateCapabilities = mAllCandidateCapabilities.find( + nsCString(cap.VideoCapture()->CurrentDeviceName())); + if ((candidateCapabilities != + mAllCandidateCapabilities.end()) && + (!candidateCapabilities->second.empty())) { + int32_t minIdx = -1; + uint64_t minDistance = UINT64_MAX; + + for (auto& candidateCapability : + candidateCapabilities->second) { + if (candidateCapability.second.videoType != + capability.videoType) { + continue; + } + // The first priority is finding a suitable resolution. + // So here we raise the weight of width and height + uint64_t distance = uint64_t(ResolutionFeasibilityDistance( + candidateCapability.second.width, + capability.width)) + + uint64_t(ResolutionFeasibilityDistance( + candidateCapability.second.height, + capability.height)) + + uint64_t(FeasibilityDistance( + candidateCapability.second.maxFPS, + capability.maxFPS)); + if (distance < minDistance) { + minIdx = static_cast<int32_t>(candidateCapability.first); + minDistance = distance; + } + } + MOZ_ASSERT(minIdx != -1); + capability = candidateCapabilities->second[minIdx]; } + } else if (aCapEngine == ScreenEngine || + aCapEngine == BrowserEngine || + aCapEngine == WinEngine) { + for (const auto& it : sDeviceUniqueIDs) { + if (strcmp(it.second, + cap.VideoCapture()->CurrentDeviceName()) == 0) { + capability.maxFPS = + std::max(capability.maxFPS, + sAllRequestedCapabilities[it.first].maxFPS); + } + } + } + + CallbackHelper* cbh = nullptr; + for (auto& cb : mCallbacks) { + if (cb->mCapEngine == aCapEngine && + cb->mStreamId == (uint32_t)aCaptureId) { + cbh = cb.get(); + break; + } + } + bool cbhCreated = !cbh; + if (!cbh) { + cbh = mCallbacks + .AppendElement(MakeUnique<CallbackHelper>( + static_cast<CaptureEngine>(aCapEngine), + aCaptureId, this)) + ->get(); + cap.VideoCapture()->SetTrackingId( + cbh->mTrackingId.mUniqueInProcId); + } + + cbh->SetConfiguration(capability, aConstraints, aResizeMode); + error = cap.VideoCapture()->StartCapture(capability); + + if (!error) { + if (cbhCreated) { + cap.VideoCapture()->RegisterCaptureDataCallback( + static_cast< + webrtc::VideoSinkInterface<webrtc::VideoFrame>*>( + cbh)); + if (auto* event = cap.CaptureEndedEvent(); + event && !cbh->mConnectedToCaptureEnded) { + cbh->mCaptureEndedListener = + event->Connect(mVideoCaptureThread, cbh, + &CallbackHelper::OnCaptureEnded); + cbh->mConnectedToCaptureEnded = true; + } + } + } else { + sDeviceUniqueIDs.erase(aCaptureId); + sAllRequestedCapabilities.erase(aCaptureId); } }); @@ -1284,7 +1141,7 @@ ipc::IPCResult CamerasParent::RecvStartCapture( } ipc::IPCResult CamerasParent::RecvFocusOnSelectedSource( - const CaptureEngine& aCapEngine, const int& aStreamId) { + const CaptureEngine& aCapEngine, const int& aCaptureId) { MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread()); MOZ_ASSERT(!mDestroyed); @@ -1292,17 +1149,11 @@ ipc::IPCResult CamerasParent::RecvFocusOnSelectedSource( using Promise = MozPromise<bool, bool, true>; InvokeAsync(mVideoCaptureThread, __func__, - [this, self = RefPtr(this), aCapEngine, aStreamId] { + [this, self = RefPtr(this), aCapEngine, aCaptureId] { bool result = false; - auto* capturer = GetCapturer(aCapEngine, aStreamId); - if (!capturer) { - return Promise::CreateAndResolve( - result, "CamerasParent::RecvFocusOnSelectedSource"); - } if (auto* engine = EnsureInitialized(aCapEngine)) { engine->WithEntry( - capturer->mCaptureId, - [&](VideoEngine::CaptureEntry& cap) { + aCaptureId, [&](VideoEngine::CaptureEntry& cap) { if (cap.VideoCapture()) { result = cap.VideoCapture()->FocusOnSelectedSource(); } @@ -1331,109 +1182,45 @@ ipc::IPCResult CamerasParent::RecvFocusOnSelectedSource( return IPC_OK(); } -auto CamerasParent::GetOrCreateCapturer( - CaptureEngine aEngine, uint64_t aWindowId, const nsCString& aUniqueId, - nsTArray<webrtc::VideoCaptureCapability>&& aCapabilities) - -> GetOrCreateCapturerResult { +void CamerasParent::StopCapture(const CaptureEngine& aCapEngine, + int aCaptureId) { MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); - VideoEngine* engine = EnsureInitialized(aEngine); - const auto ensureShmemPool = [&](int aCaptureId) { - auto guard = mShmemPools.Lock(); - constexpr size_t kMaxShmemBuffers = 1; - guard->try_emplace(aCaptureId, kMaxShmemBuffers); - }; - for (auto& capturer : *mCapturers) { - if (capturer->mCapEngine != aEngine) { - continue; - } - if (capturer->mUniqueId.Equals(aUniqueId)) { - int streamId = engine->GenerateId(); - ensureShmemPool(capturer->mCaptureId); - capturer->AddStream(this, streamId, aWindowId); - return {.mCapturer = capturer.get(), .mStreamId = streamId}; - } - } - NotNull capturer = mCapturers->AppendElement( - AggregateCapturer::Create(mVideoCaptureThread, aEngine, engine, aUniqueId, - aWindowId, std::move(aCapabilities), this)); - ensureShmemPool(capturer->get()->mCaptureId); - return {.mCapturer = capturer->get(), - .mStreamId = capturer->get()->mCaptureId}; -} - -AggregateCapturer* CamerasParent::GetCapturer(CaptureEngine aEngine, - int aStreamId) { - MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); - for (auto& capturer : *mCapturers) { - if (capturer->mCapEngine != aEngine) { - continue; - } - Maybe captureId = capturer->CaptureIdFor(aStreamId); - if (captureId) { - return capturer.get(); - } - } - return nullptr; -} - -int CamerasParent::ReleaseStream(CaptureEngine aEngine, int aStreamId) { - MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); - auto* capturer = GetCapturer(aEngine, aStreamId); - if (!capturer) { - return -1; - } - auto removed = capturer->RemoveStream(aStreamId); - if (removed.mNumRemainingStreams == 0) { - mCapturers->RemoveElement(capturer); - } - return 0; -} - -nsTArray<webrtc::VideoCaptureCapability> const* -CamerasParent::EnsureCapabilitiesPopulated(CaptureEngine aEngine, - const nsCString& aUniqueId) { - MOZ_ASSERT(mVideoCaptureThread->IsOnCurrentThread()); - if (auto iter = mAllCandidateCapabilities.find(aUniqueId); - iter != mAllCandidateCapabilities.end()) { - return &iter->second; - } - auto devInfo = GetDeviceInfo(aEngine); - if (!devInfo) { - return nullptr; - } - const int num = devInfo->NumberOfCapabilities(aUniqueId.get()); - if (num <= 0) { - return nullptr; - } - nsTArray<webrtc::VideoCaptureCapability> capabilities(num); - for (int i = 0; i < num; ++i) { - webrtc::VideoCaptureCapability capability; - if (devInfo->GetCapability(aUniqueId.get(), i, capability)) { - return nullptr; + if (auto* engine = EnsureInitialized(aCapEngine)) { + // we're removing elements, iterate backwards + for (size_t i = mCallbacks.Length(); i > 0; i--) { + if (mCallbacks[i - 1]->mCapEngine == aCapEngine && + mCallbacks[i - 1]->mStreamId == (uint32_t)aCaptureId) { + CallbackHelper* cbh = mCallbacks[i - 1].get(); + engine->WithEntry(aCaptureId, [cbh, &aCaptureId]( + VideoEngine::CaptureEntry& cap) { + if (cap.VideoCapture()) { + cap.VideoCapture()->DeRegisterCaptureDataCallback( + static_cast<webrtc::VideoSinkInterface<webrtc::VideoFrame>*>( + cbh)); + cap.VideoCapture()->StopCaptureIfAllClientsClose(); + + sDeviceUniqueIDs.erase(aCaptureId); + sAllRequestedCapabilities.erase(aCaptureId); + } + }); + cbh->mCaptureEndedListener.DisconnectIfExists(); + mCallbacks.RemoveElementAt(i - 1); + break; + } } - capabilities.AppendElement(capability); } - const auto& [iter, _] = - mAllCandidateCapabilities.emplace(aUniqueId, std::move(capabilities)); - return &iter->second; } ipc::IPCResult CamerasParent::RecvStopCapture(const CaptureEngine& aCapEngine, - const int& aStreamId) { + const int& aCaptureId) { MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread()); MOZ_ASSERT(!mDestroyed); LOG_FUNCTION(); nsresult rv = mVideoCaptureThread->Dispatch(NS_NewRunnableFunction( - __func__, [this, self = RefPtr(this), aCapEngine, aStreamId] { - auto* capturer = GetCapturer(aCapEngine, aStreamId); - if (capturer) { - capturer->SetConfigurationFor( - aStreamId, webrtc::VideoCaptureCapability{}, - NormalizedConstraints{}, dom::VideoResizeModeEnum::None, - /*aStarted=*/false); - } + __func__, [this, self = RefPtr(this), aCapEngine, aCaptureId] { + StopCapture(aCapEngine, aCaptureId); })); if (mDestroyed) { @@ -1459,12 +1246,7 @@ void CamerasParent::ActorDestroy(ActorDestroyReason aWhy) { LOG_FUNCTION(); // Release shared memory now, it's our last chance - { - auto guard = mShmemPools.Lock(); - for (auto& [captureId, pool] : *guard) { - pool.Cleanup(this); - } - } + mShmemPool.Cleanup(this); // We don't want to receive callbacks or anything if we can't // forward them anymore anyway. mDestroyed = true; @@ -1495,9 +1277,8 @@ CamerasParent::CamerasParent() ? MakeAndAddRefVideoCaptureThreadAndSingletons() : nullptr), mEngines(sEngines), - mCapturers(sCapturers), mVideoCaptureFactory(EnsureVideoCaptureFactory()), - mShmemPools("CamerasParent::mShmemPools"), + mShmemPool(CaptureEngine::MaxEngine), mPBackgroundEventTarget(GetCurrentSerialEventTarget()), mDestroyed(false) { MOZ_ASSERT(mPBackgroundEventTarget != nullptr, diff --git a/dom/media/systemservices/CamerasParent.h b/dom/media/systemservices/CamerasParent.h @@ -28,56 +28,26 @@ namespace mozilla::camera { class CamerasParent; class VideoEngine; -// Class that manages sharing of VideoCaptureImpl instances on top of -// VideoEngine. Sharing is needed as access to most sources is exclusive -// system-wide. -// -// There is at most one AggregateCapturer instance per unique source, as defined -// by its unique capture ID. -// -// There can be multiple requests for a stream from a source, as defined by -// unique stream IDs. -// -// Stream IDs and capture IDs use the same ID space. With capture happening in -// the parent process, application-wide uniqueness is guaranteed. -// -// When multiple stream requests have been made for a source, even across -// multiple CamerasParent instances, this class distributes a single frame to -// each CamerasParent instance that has requested a stream. Distribution to the -// various stream requests happens in CamerasChild::RecvDeliverFrame. -// -// This class similarly handles capture-ended events, and distributes them to -// the correct CamerasParent instances, with distribution to streams happening -// in CamerasChild::RecvCaptureEnded. -class AggregateCapturer final - : public webrtc::VideoSinkInterface<webrtc::VideoFrame> { +class CallbackHelper : public webrtc::VideoSinkInterface<webrtc::VideoFrame> { public: - static std::unique_ptr<AggregateCapturer> Create( - nsISerialEventTarget* aVideoCaptureThread, CaptureEngine aCapEng, - VideoEngine* aEngine, const nsCString& aUniqueId, uint64_t aWindowId, - nsTArray<webrtc::VideoCaptureCapability>&& aCapabilities, - CamerasParent* aParent); - - ~AggregateCapturer(); - - void AddStream(CamerasParent* aParent, int aStreamId, uint64_t aWindowId); - struct RemoveStreamResult { - size_t mNumRemainingStreams; - size_t mNumRemainingStreamsForParent; - }; - RemoveStreamResult RemoveStream(int aStreamId); - RemoveStreamResult RemoveStreamsFor(CamerasParent* aParent); - Maybe<int> CaptureIdFor(int aStreamId); - void SetConfigurationFor(int aStreamId, - const webrtc::VideoCaptureCapability& aCapability, - const NormalizedConstraints& aConstraints, - const dom::VideoResizeModeEnum& aResizeMode, - bool aStarted); - webrtc::VideoCaptureCapability CombinedCapability(); + CallbackHelper(CaptureEngine aCapEng, uint32_t aStreamId, + CamerasParent* aParent) + : mCapEngine(aCapEng), + mStreamId(aStreamId), + mTrackingId(CaptureEngineToTrackingSourceStr(aCapEng), aStreamId), + mParent(aParent), + mConfiguration("CallbackHelper::mConfiguration") {}; + + void SetConfiguration(const webrtc::VideoCaptureCapability& aCapability, + const NormalizedConstraints& aConstraints, + const dom::VideoResizeModeEnum& aResizeMode); void OnCaptureEnded(); void OnFrame(const webrtc::VideoFrame& aVideoFrame) override; + friend CamerasParent; + + private: struct Configuration { webrtc::VideoCaptureCapability mCapability; NormalizedConstraints mConstraints; @@ -85,55 +55,15 @@ class AggregateCapturer final // defaults factored in. dom::VideoResizeModeEnum mResizeMode{}; }; - // Representation of a stream request for the source of this AggregateCapturer - // instance. - struct Stream { - // The CamerasParent instance that requested this stream. mParent is - // responsible for the lifetime of this stream. - CamerasParent* const mParent; - // The id that identifies this stream. This is unique within the application - // session, in the same set of IDs as AggregateCapturer::mCaptureId. - const int mId{-1}; - // The id of the window where the request for this stream originated. - const uint64_t mWindowId{}; - // The configuration applied to this stream. - Configuration mConfiguration; - // Whether the stream has been started and not stopped. As opposed to - // allocated and not deallocated, which controls the presence of this stream - // altogether. - bool mStarted{false}; - // The timestamp of the last frame sent to mParent for this stream. - media::TimeUnit mLastFrameTime{media::TimeUnit::FromNegativeInfinity()}; - }; - // The video capture thread is where all access to this class must happen. - const nsCOMPtr<nsISerialEventTarget> mVideoCaptureThread; - // The identifier for which VideoEngine instance we are using, i.e. which type - // of source we're associated with. const CaptureEngine mCapEngine; - // The (singleton from sEngines) VideoEngine instance that mCaptureId is valid - // in. - const RefPtr<VideoEngine> mEngine; - // The unique ID string of the associated device. - const nsCString mUniqueId; - // The id that identifies the capturer instance of the associated source - // device in VideoEngine. - const int mCaptureId; - // Tracking ID of the capturer for profiler markers. + const uint32_t mStreamId; const TrackingId mTrackingId; - // The (immutable) list of capabilities offered by the associated source - // device. - const nsTArray<webrtc::VideoCaptureCapability> mCapabilities; - // The list of streams that have been requested from all CamerasParent - // instances for the associated source device. - DataMutex<nsTArray<std::unique_ptr<Stream>>> mStreams; - - private: - AggregateCapturer(nsISerialEventTarget* aVideoCaptureThread, - CaptureEngine aCapEng, VideoEngine* aEngine, - const nsCString& aUniqueId, int aCaptureId, - nsTArray<webrtc::VideoCaptureCapability>&& aCapabilities); - + CamerasParent* const mParent; MediaEventListener mCaptureEndedListener; + bool mConnectedToCaptureEnded = false; + DataMutex<Configuration> mConfiguration; + // Capture thread only. + media::TimeUnit mLastFrameTime = media::TimeUnit::FromNegativeInfinity(); }; class DeliverFrameRunnable; @@ -173,7 +103,7 @@ class CamerasParent final : public PCamerasParent { const CaptureEngine& aCapEngine, const nsACString& aUniqueIdUTF8, const uint64_t& aWindowID) override; mozilla::ipc::IPCResult RecvReleaseCapture(const CaptureEngine& aCapEngine, - const int& aStreamId) override; + const int& aCaptureId) override; mozilla::ipc::IPCResult RecvNumberOfCaptureDevices( const CaptureEngine& aCapEngine) override; mozilla::ipc::IPCResult RecvNumberOfCapabilities( @@ -184,21 +114,20 @@ class CamerasParent final : public PCamerasParent { mozilla::ipc::IPCResult RecvGetCaptureDevice( const CaptureEngine& aCapEngine, const int& aDeviceIndex) override; mozilla::ipc::IPCResult RecvStartCapture( - const CaptureEngine& aCapEngine, const int& aStreamId, + const CaptureEngine& aCapEngine, const int& aCaptureId, const VideoCaptureCapability& aIpcCaps, const NormalizedConstraints& aConstraints, const dom::VideoResizeModeEnum& aResizeMode) override; mozilla::ipc::IPCResult RecvFocusOnSelectedSource( - const CaptureEngine& aCapEngine, const int& aStreamId) override; + const CaptureEngine& aCapEngine, const int& aCaptureId) override; mozilla::ipc::IPCResult RecvStopCapture(const CaptureEngine& aCapEngine, - const int& aStreamId) override; + const int& aCaptureId) override; mozilla::ipc::IPCResult RecvReleaseFrame( - const int& aCaptureId, mozilla::ipc::Shmem&& aShmem) override; + mozilla::ipc::Shmem&& aShmem) override; void ActorDestroy(ActorDestroyReason aWhy) override; mozilla::ipc::IPCResult RecvEnsureInitialized( const CaptureEngine& aCapEngine) override; - bool IsWindowCapturing(uint64_t aWindowId, const nsACString& aUniqueId) const; nsIEventTarget* GetBackgroundEventTarget() { return mPBackgroundEventTarget; }; @@ -207,13 +136,12 @@ class CamerasParent final : public PCamerasParent { MOZ_ASSERT(mPBackgroundEventTarget->IsOnCurrentThread()); return mDestroyed; }; - ShmemBuffer GetBuffer(int aCaptureId, size_t aSize); + ShmemBuffer GetBuffer(size_t aSize); // helper to forward to the PBackground thread - int DeliverFrameOverIPC(CaptureEngine aCapEngine, int aCaptureId, - const Span<const int>& aStreamId, - const TrackingId& aTrackingId, - Variant<ShmemBuffer, webrtc::VideoFrame>&& aBuffer, + int DeliverFrameOverIPC(CaptureEngine aCapEngine, uint32_t aStreamId, + const TrackingId& aTrackingId, ShmemBuffer aBuffer, + unsigned char* aAltBuffer, const VideoFrameProperties& aProps); CamerasParent(); @@ -221,18 +149,9 @@ class CamerasParent final : public PCamerasParent { private: virtual ~CamerasParent(); - struct GetOrCreateCapturerResult { - AggregateCapturer* mCapturer{}; - int mStreamId{}; - }; - GetOrCreateCapturerResult GetOrCreateCapturer( - CaptureEngine aEngine, uint64_t aWindowId, const nsCString& aUniqueId, - nsTArray<webrtc::VideoCaptureCapability>&& aCapabilities); - AggregateCapturer* GetCapturer(CaptureEngine aEngine, int aStreamId); - int ReleaseStream(CaptureEngine aEngine, int aStreamId); - - nsTArray<webrtc::VideoCaptureCapability> const* EnsureCapabilitiesPopulated( - CaptureEngine aEngine, const nsCString& aUniqueId); + // We use these helpers for shutdown and for the respective IPC commands. + void StopCapture(const CaptureEngine& aCapEngine, int aCaptureId); + int ReleaseCapture(const CaptureEngine& aCapEngine, int aCaptureId); void OnDeviceChange(); @@ -249,6 +168,7 @@ class CamerasParent final : public PCamerasParent { void OnShutdown(); + nsTArray<UniquePtr<CallbackHelper>> mCallbacks; // If existent, blocks xpcom shutdown while alive. // Note that this makes a reference cycle that gets broken in ActorDestroy(). const UniquePtr<media::ShutdownBlockingTicket> mShutdownBlocker; @@ -261,24 +181,12 @@ class CamerasParent final : public PCamerasParent { // Reference to same VideoEngineArray as sEngines. Video capture thread only. const RefPtr<VideoEngineArray> mEngines; - // Reference to same array of AggregateCapturers as sCapturers. There is one - // AggregateCapturer per allocated video source. It tracks the mapping from - // source to streamIds and CamerasParent instances. Video capture thread only. - const RefPtr< - media::Refcountable<nsTArray<std::unique_ptr<AggregateCapturer>>>> - mCapturers; - // Reference to same VideoCaptureFactory as sVideoCaptureFactory. Video // capture thread only. const RefPtr<VideoCaptureFactory> mVideoCaptureFactory; - // Image buffers. One pool per CamerasParent instance and capture id (i.e. - // unique source). Multiple CamerasParent instances capturing the same source - // need distinct ShmemPools as ShmemBuffers are tied to the IPC channel. - // Access is on the PBackground thread for mutations and - // allocating shmem buffers, and on the callback thread (varies by capture - // backend) for querying an existing pool for an available buffer. - DataMutex<std::map<int, ShmemPool>> mShmemPools; + // image buffers + ShmemPool mShmemPool; // PBackgroundParent thread const nsCOMPtr<nsISerialEventTarget> mPBackgroundEventTarget; @@ -286,7 +194,7 @@ class CamerasParent final : public PCamerasParent { // Set to true in ActorDestroy. PBackground only. bool mDestroyed; - std::map<nsCString, nsTArray<webrtc::VideoCaptureCapability>> + std::map<nsCString, std::map<uint32_t, webrtc::VideoCaptureCapability>> mAllCandidateCapabilities; // Listener for the camera VideoEngine::DeviceChangeEvent(). Video capture diff --git a/dom/media/systemservices/PCameras.ipdl b/dom/media/systemservices/PCameras.ipdl @@ -61,13 +61,13 @@ async protocol PCameras manager PBackground; child: - async CaptureEnded(int[] streamIds); + async CaptureEnded(int streamId); // transfers ownership of |buffer| from parent to child - async DeliverFrame(int captureId, int[] streamIds, Shmem buffer, VideoFrameProperties props); + async DeliverFrame(int streamId, Shmem buffer, VideoFrameProperties props); async DeviceChange(); async ReplyNumberOfCaptureDevices(int deviceCount); async ReplyNumberOfCapabilities(int capabilityCount); - async ReplyAllocateCapture(int streamId); + async ReplyAllocateCapture(int captureId); async ReplyGetCaptureCapability(VideoCaptureCapability cap); async ReplyGetCaptureDevice(nsCString device_name, nsCString device_id, bool scary); async ReplyFailure(); @@ -84,15 +84,15 @@ parent: async AllocateCapture(CaptureEngine engine, nsCString unique_idUTF8, uint64_t windowID); - async ReleaseCapture(CaptureEngine engine, int streamId); - async StartCapture(CaptureEngine engine, int streamId, + async ReleaseCapture(CaptureEngine engine, int captureId); + async StartCapture(CaptureEngine engine, int captureId, VideoCaptureCapability capability, NormalizedConstraints constraints, VideoResizeModeEnum resizeMode); - async FocusOnSelectedSource(CaptureEngine engine, int streamId); - async StopCapture(CaptureEngine engine, int streamId); + async FocusOnSelectedSource(CaptureEngine engine, int captureId); + async StopCapture(CaptureEngine engine, int captureId); // transfers frame back - async ReleaseFrame(int captureId, Shmem s); + async ReleaseFrame(Shmem s); // setup camera engine async EnsureInitialized(CaptureEngine engine); diff --git a/dom/media/systemservices/VideoEngine.cpp b/dom/media/systemservices/VideoEngine.cpp @@ -46,21 +46,19 @@ int VideoEngine::SetAndroidObjects() { } #endif -int32_t VideoEngine::CreateVideoCapture(const char* aDeviceUniqueIdUTF8, - uint64_t aWindowID) { +int32_t VideoEngine::CreateVideoCapture(const char* aDeviceUniqueIdUTF8) { LOG(("%s", __PRETTY_FUNCTION__)); MOZ_ASSERT(aDeviceUniqueIdUTF8); int32_t id = GenerateId(); LOG(("CaptureDeviceType=%s id=%d", EnumValueToString(mCaptureDevType), id)); - for (auto& it : mSharedCapturers) { + for (auto& it : mCaps) { if (it.second.VideoCapture() && it.second.VideoCapture()->CurrentDeviceName() && strcmp(it.second.VideoCapture()->CurrentDeviceName(), aDeviceUniqueIdUTF8) == 0) { - mIdToCapturerMap.emplace(id, CaptureHandle{.mCaptureEntryNum = it.first, - .mWindowID = aWindowID}); + mIdMap.emplace(id, it.first); return id; } } @@ -73,9 +71,8 @@ int32_t VideoEngine::CreateVideoCapture(const char* aDeviceUniqueIdUTF8, entry = CaptureEntry(id, std::move(capturer.mCapturer), capturer.mDesktopImpl); - mSharedCapturers.emplace(id, std::move(entry)); - mIdToCapturerMap.emplace( - id, CaptureHandle{.mCaptureEntryNum = id, .mWindowID = aWindowID}); + mCaps.emplace(id, std::move(entry)); + mIdMap.emplace(id, id); return id; } @@ -84,15 +81,14 @@ int VideoEngine::ReleaseVideoCapture(const int32_t aId) { #ifdef DEBUG { - auto it = mIdToCapturerMap.find(aId); - MOZ_ASSERT(it != mIdToCapturerMap.end()); + auto it = mIdMap.find(aId); + MOZ_ASSERT(it != mIdMap.end()); (void)it; } #endif - for (auto& it : mIdToCapturerMap) { - if (it.first != aId && - it.second.mCaptureEntryNum == mIdToCapturerMap[aId].mCaptureEntryNum) { + for (auto& it : mIdMap) { + if (it.first != aId && it.second == mIdMap[aId]) { // There are other tracks still using this hardware. found = true; } @@ -105,13 +101,13 @@ int VideoEngine::ReleaseVideoCapture(const int32_t aId) { }); MOZ_ASSERT(found); if (found) { - auto it = mSharedCapturers.find(mIdToCapturerMap[aId].mCaptureEntryNum); - MOZ_ASSERT(it != mSharedCapturers.end()); - mSharedCapturers.erase(it); + auto it = mCaps.find(mIdMap[aId]); + MOZ_ASSERT(it != mCaps.end()); + mCaps.erase(it); } } - mIdToCapturerMap.erase(aId); + mIdMap.erase(aId); return found ? 0 : (-1); } @@ -220,44 +216,21 @@ bool VideoEngine::WithEntry( const std::function<void(CaptureEntry& entry)>&& fn) { #ifdef DEBUG { - auto it = mIdToCapturerMap.find(entryCapnum); - MOZ_ASSERT(it != mIdToCapturerMap.end()); + auto it = mIdMap.find(entryCapnum); + MOZ_ASSERT(it != mIdMap.end()); (void)it; } #endif - auto it = - mSharedCapturers.find(mIdToCapturerMap[entryCapnum].mCaptureEntryNum); - MOZ_ASSERT(it != mSharedCapturers.end()); - if (it == mSharedCapturers.end()) { + auto it = mCaps.find(mIdMap[entryCapnum]); + MOZ_ASSERT(it != mCaps.end()); + if (it == mCaps.end()) { return false; } fn(it->second); return true; } -bool VideoEngine::IsWindowCapturing(uint64_t aWindowID, - const nsCString& aUniqueIdUTF8) { - Maybe<int32_t> sharedId; - for (auto& [id, entry] : mSharedCapturers) { - if (entry.VideoCapture() && entry.VideoCapture()->CurrentDeviceName() && - strcmp(entry.VideoCapture()->CurrentDeviceName(), - aUniqueIdUTF8.get()) == 0) { - sharedId = Some(id); - break; - } - } - if (!sharedId) { - return false; - } - for (auto& [id, handle] : mIdToCapturerMap) { - if (handle.mCaptureEntryNum == *sharedId && handle.mWindowID == aWindowID) { - return true; - } - } - return false; -} - int32_t VideoEngine::GenerateId() { // XXX Something better than this (a map perhaps, or a simple boolean TArray, // given the number in-use is O(1) normally!) @@ -280,8 +253,8 @@ VideoEngine::VideoEngine(const CaptureDeviceType& aCaptureDeviceType, } VideoEngine::~VideoEngine() { - MOZ_ASSERT(mSharedCapturers.empty()); - MOZ_ASSERT(mIdToCapturerMap.empty()); + MOZ_ASSERT(mCaps.empty()); + MOZ_ASSERT(mIdMap.empty()); } } // namespace mozilla::camera diff --git a/dom/media/systemservices/VideoEngine.h b/dom/media/systemservices/VideoEngine.h @@ -44,11 +44,9 @@ class VideoEngine : public webrtc::VideoInputFeedBack { #if defined(ANDROID) static int SetAndroidObjects(); #endif - int32_t GenerateId(); /** Returns a non-negative capture identifier or -1 on failure. */ - int32_t CreateVideoCapture(const char* aDeviceUniqueIdUTF8, - uint64_t aWindowID); + int32_t CreateVideoCapture(const char* aDeviceUniqueIdUTF8); int ReleaseVideoCapture(const int32_t aId); @@ -89,17 +87,10 @@ class VideoEngine : public webrtc::VideoInputFeedBack { friend class VideoEngine; }; - struct CaptureHandle { - int32_t mCaptureEntryNum{}; - uint64_t mWindowID{}; - }; - // Returns true iff an entry for capnum exists bool WithEntry(const int32_t entryCapnum, const std::function<void(CaptureEntry& entry)>&& fn); - bool IsWindowCapturing(uint64_t aWindowID, const nsCString& aUniqueIdUTF8); - void OnDeviceChange() override; MediaEventSource<void>& DeviceChangeEvent() { return mDeviceChangeEvent; } @@ -111,11 +102,12 @@ class VideoEngine : public webrtc::VideoInputFeedBack { const CaptureDeviceType mCaptureDevType; const RefPtr<VideoCaptureFactory> mVideoCaptureFactory; std::shared_ptr<webrtc::VideoCaptureModule::DeviceInfo> mDeviceInfo; - std::map<int32_t, CaptureEntry> mSharedCapturers; - std::map<int32_t, CaptureHandle> mIdToCapturerMap; + std::map<int32_t, CaptureEntry> mCaps; + std::map<int32_t, int32_t> mIdMap; MediaEventProducer<void> mDeviceChangeEvent; // The validity period for non-camera capture device infos` webrtc::Timestamp mExpiryTime = webrtc::Timestamp::Micros(0); + int32_t GenerateId(); }; } // namespace mozilla::camera #endif diff --git a/dom/media/webrtc/MediaEngine.h b/dom/media/webrtc/MediaEngine.h @@ -49,13 +49,6 @@ class MediaEngine { virtual RefPtr<MediaEngineSource> CreateSource( const MediaDevice* aDevice) = 0; - /** - * Like CreateSource but in addition copies over capabilities and settings - * from another source. - */ - virtual RefPtr<MediaEngineSource> CreateSourceFrom( - const MediaEngineSource* aSource, const MediaDevice* aDevice) = 0; - virtual MediaEventSource<void>& DeviceListChangeEvent() = 0; /** * Return true if devices returned from EnumerateDevices are emulated media diff --git a/dom/media/webrtc/MediaEngineFake.cpp b/dom/media/webrtc/MediaEngineFake.cpp @@ -86,9 +86,6 @@ class MediaEngineFakeVideoSource : public MediaEngineSource { public: MediaEngineFakeVideoSource(); - static already_AddRefed<MediaEngineFakeVideoSource> CreateFrom( - const MediaEngineFakeVideoSource* aSource); - static nsString GetGroupId(); nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, @@ -146,15 +143,6 @@ MediaEngineFakeVideoSource::MediaEngineFakeVideoSource() dom::GetEnumString(dom::VideoResizeModeEnum::None))); } -/*static*/ already_AddRefed<MediaEngineFakeVideoSource> -MediaEngineFakeVideoSource::CreateFrom( - const MediaEngineFakeVideoSource* aSource) { - auto src = MakeRefPtr<MediaEngineFakeVideoSource>(); - *static_cast<MediaTrackSettings*>(src->mSettings) = *aSource->mSettings; - src->mOpts = aSource->mOpts; - return src.forget(); -} - nsString MediaEngineFakeVideoSource::GetGroupId() { return u"Fake Video Group"_ns; } @@ -628,20 +616,4 @@ RefPtr<MediaEngineSource> MediaEngineFake::CreateSource( } } -RefPtr<MediaEngineSource> MediaEngineFake::CreateSourceFrom( - const MediaEngineSource* aSource, const MediaDevice* aMediaDevice) { - MOZ_ASSERT(aMediaDevice->mEngine == this); - switch (aMediaDevice->mMediaSource) { - case MediaSourceEnum::Camera: - return MediaEngineFakeVideoSource::CreateFrom( - static_cast<const MediaEngineFakeVideoSource*>(aSource)); - case MediaSourceEnum::Microphone: - // No main thread members that need to be deep cloned. - return new MediaEngineFakeAudioSource(); - default: - MOZ_ASSERT_UNREACHABLE("Unsupported source type"); - return nullptr; - } -} - } // namespace mozilla diff --git a/dom/media/webrtc/MediaEngineFake.h b/dom/media/webrtc/MediaEngineFake.h @@ -22,8 +22,6 @@ class MediaEngineFake : public MediaEngine { nsTArray<RefPtr<MediaDevice>>*) override; void Shutdown() override {} RefPtr<MediaEngineSource> CreateSource(const MediaDevice* aDevice) override; - RefPtr<MediaEngineSource> CreateSourceFrom( - const MediaEngineSource* aSource, const MediaDevice* aDevice) override; MediaEventSource<void>& DeviceListChangeEvent() override { return mDeviceListChangeEvent; diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp @@ -203,22 +203,6 @@ MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource( } } -/*static*/ -already_AddRefed<MediaEngineRemoteVideoSource> -MediaEngineRemoteVideoSource::CreateFrom( - const MediaEngineRemoteVideoSource* aSource, - const MediaDevice* aMediaDevice) { - auto src = MakeRefPtr<MediaEngineRemoteVideoSource>(aMediaDevice); - *static_cast<MediaTrackSettings*>(src->mSettings) = *aSource->mSettings; - *static_cast<MediaTrackCapabilities*>(src->mTrackCapabilities) = - *aSource->mTrackCapabilities; - { - MutexAutoLock lock(aSource->mMutex); - src->mIncomingImageSize = aSource->mIncomingImageSize; - } - return src.forget(); -} - MediaEngineRemoteVideoSource::~MediaEngineRemoteVideoSource() { mFirstFramePromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__); } @@ -299,8 +283,8 @@ nsresult MediaEngineRemoteVideoSource::Allocate( .mCapabilityWidth = cw ? Some(cw) : Nothing(), .mCapabilityHeight = ch ? Some(ch) : Nothing(), .mCapEngine = mCapEngine, - .mInputWidth = cw ? cw : mIncomingImageSize.width, - .mInputHeight = ch ? ch : mIncomingImageSize.height, + .mInputWidth = cw, + .mInputHeight = ch, .mRotation = 0, }; framerate = input.mCanCropAndScale.valueOr(false) @@ -369,22 +353,12 @@ nsresult MediaEngineRemoteVideoSource::Deallocate() { LOG("Video device %d deallocated", mCaptureId); - int error = camera::GetChildAndCall(&camera::CamerasChild::ReleaseCapture, - mCapEngine, mCaptureId); - - if (error == camera::kSuccess) { - return NS_OK; - } - - if (error == camera::kIpcError) { - // Failure can occur when the parent process is shutting down, and the IPC - // channel is down. We still consider the capturer deallocated in this - // case, since it cannot deliver frames without the IPC channel open. - return NS_OK; + if (camera::GetChildAndCall(&camera::CamerasChild::ReleaseCapture, mCapEngine, + mCaptureId)) { + // Failure can occur when the parent process is shutting down. + return NS_ERROR_FAILURE; } - - MOZ_ASSERT(error == camera::kError); - return NS_ERROR_FAILURE; + return NS_OK; } void MediaEngineRemoteVideoSource::SetTrack(const RefPtr<MediaTrack>& aTrack, @@ -478,11 +452,9 @@ nsresult MediaEngineRemoteVideoSource::Stop() { MOZ_ASSERT(mState == kStarted); - int error = camera::GetChildAndCall(&camera::CamerasChild::StopCapture, - mCapEngine, mCaptureId); - - if (error == camera::kError) { - // CamerasParent replied with error. The capturer is still running. + if (camera::GetChildAndCall(&camera::CamerasChild::StopCapture, mCapEngine, + mCaptureId)) { + // Failure can occur when the parent process is shutting down. return NS_ERROR_FAILURE; } @@ -491,15 +463,7 @@ nsresult MediaEngineRemoteVideoSource::Stop() { mState = kStopped; } - if (error == camera::kSuccess) { - return NS_OK; - } - - MOZ_ASSERT(error == camera::kIpcError); - // Failure can occur when the parent process is shutting down, and the IPC - // channel is down. We still consider the capturer stopped in this case, - // since it cannot deliver frames without the IPC channel open. - return NS_ERROR_FAILURE; + return NS_OK; } nsresult MediaEngineRemoteVideoSource::Reconfigure( diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.h b/dom/media/webrtc/MediaEngineRemoteVideoSource.h @@ -88,10 +88,6 @@ class MediaEngineRemoteVideoSource : public MediaEngineSource, public: explicit MediaEngineRemoteVideoSource(const MediaDevice* aMediaDevice); - static already_AddRefed<MediaEngineRemoteVideoSource> CreateFrom( - const MediaEngineRemoteVideoSource* aSource, - const MediaDevice* aMediaDevice); - // ExternalRenderer /** * Signals that the capture stream has ended @@ -165,7 +161,7 @@ class MediaEngineRemoteVideoSource : public MediaEngineSource, // mMutex protects certain members on 3 threads: // MediaManager, Cameras IPC and MediaTrackGraph. - mutable Mutex mMutex MOZ_UNANNOTATED; + Mutex mMutex MOZ_UNANNOTATED; // Current state of this source. // Set under mMutex on the owning thread. Accessed under one of the two. diff --git a/dom/media/webrtc/MediaEngineWebRTC.cpp b/dom/media/webrtc/MediaEngineWebRTC.cpp @@ -299,25 +299,6 @@ RefPtr<MediaEngineSource> MediaEngineWebRTC::CreateSource( } } -RefPtr<MediaEngineSource> MediaEngineWebRTC::CreateSourceFrom( - const MediaEngineSource* aSource, const MediaDevice* aMediaDevice) { - MOZ_ASSERT(aMediaDevice->mEngine == this); - if (MediaEngineSource::IsVideo(aMediaDevice->mMediaSource)) { - return MediaEngineRemoteVideoSource::CreateFrom( - static_cast<const MediaEngineRemoteVideoSource*>(aSource), - aMediaDevice); - } - switch (aMediaDevice->mMediaSource) { - case MediaSourceEnum::Microphone: - return MediaEngineWebRTCMicrophoneSource::CreateFrom( - static_cast<const MediaEngineWebRTCMicrophoneSource*>(aSource), - aMediaDevice); - default: - MOZ_CRASH("Unsupported source type"); - return nullptr; - } -} - void MediaEngineWebRTC::Shutdown() { AssertIsOnOwningThread(); mCameraListChangeListener.DisconnectIfExists(); diff --git a/dom/media/webrtc/MediaEngineWebRTC.h b/dom/media/webrtc/MediaEngineWebRTC.h @@ -27,8 +27,6 @@ class MediaEngineWebRTC : public MediaEngine { void EnumerateDevices(dom::MediaSourceEnum, MediaSinkEnum, nsTArray<RefPtr<MediaDevice>>*) override; RefPtr<MediaEngineSource> CreateSource(const MediaDevice* aDevice) override; - RefPtr<MediaEngineSource> CreateSourceFrom(const MediaEngineSource* aSource, - const MediaDevice*) override; MediaEventSource<void>& DeviceListChangeEvent() override { return mDeviceListChangeEvent; diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp @@ -113,17 +113,6 @@ MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource( })); } -/*static*/ already_AddRefed<MediaEngineWebRTCMicrophoneSource> -MediaEngineWebRTCMicrophoneSource::CreateFrom( - const MediaEngineWebRTCMicrophoneSource* aSource, - const MediaDevice* aMediaDevice) { - auto src = MakeRefPtr<MediaEngineWebRTCMicrophoneSource>(aMediaDevice); - *static_cast<dom::MediaTrackSettings*>(src->mSettings) = *aSource->mSettings; - *static_cast<dom::MediaTrackCapabilities*>(src->mCapabilities) = - *aSource->mCapabilities; - return src.forget(); -} - nsresult MediaEngineWebRTCMicrophoneSource::EvaluateSettings( const NormalizedConstraints& aConstraintsUpdate, const MediaEnginePrefs& aInPrefs, MediaEnginePrefs* aOutPrefs, diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.h b/dom/media/webrtc/MediaEngineWebRTCAudio.h @@ -34,10 +34,6 @@ class MediaEngineWebRTCMicrophoneSource : public MediaEngineSource { public: explicit MediaEngineWebRTCMicrophoneSource(const MediaDevice* aMediaDevice); - static already_AddRefed<MediaEngineWebRTCMicrophoneSource> CreateFrom( - const MediaEngineWebRTCMicrophoneSource* aSource, - const MediaDevice* aMediaDevice); - nsresult Allocate(const dom::MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs, uint64_t aWindowID, const char** aOutBadConstraint) override; @@ -318,13 +314,9 @@ class AudioProcessingTrack : public DeviceInputConsumerTrack { void DestroyImpl() override; void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override; uint32_t NumberOfChannels() const override { - if (!mInputProcessing) { - // There's an async gap between adding the track to the graph - // (AudioProcessingTrack::Create) and setting mInputProcessing - // (SetInputProcessing on the media manager thread). - // Return 0 to indicate the default within this gap. - return 0; - } + MOZ_DIAGNOSTIC_ASSERT( + mInputProcessing, + "Must set mInputProcessing before exposing to content"); return mInputProcessing->GetRequestedInputChannelCount(); } // Pass the graph's mixed audio output to mInputProcessing for processing as diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html @@ -22,10 +22,6 @@ let streamUntilEnded; const tracks = []; runTest(async () => { try { - await pushPrefs( - ["media.getusermedia.camera.fake.force", true], - ["media.video_loopback_dev", ""], - ); let stream = await getUserMedia({audio: true, video: true}); // We need to test with multiple tracks. We add an extra of each kind. for (const track of stream.getTracks()) { diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html @@ -35,10 +35,6 @@ }); runTest(() => Promise.resolve() - .then(() => pushPrefs( - ["media.getusermedia.camera.fake.force", true], - ["media.video_loopback_dev", ""], - )) .then(() => testSingleTrackClonePlayback({audio: true})) .then(() => testSingleTrackClonePlayback({video: true})) .then(() => getUserMedia({video: true})).then(stream => { diff --git a/dom/midi/midir_impl/src/lib.rs b/dom/midi/midir_impl/src/lib.rs @@ -14,6 +14,18 @@ use uuid::Uuid; * You can obtain one at http://mozilla.org/MPL/2.0/. */ extern crate midir; +#[cfg(target_os = "windows")] +#[repr(C)] +#[derive(Clone, Copy)] +pub struct GeckoTimeStamp { + gtc: u64, + qpc: u64, + + is_null: u8, + has_qpc: u8, +} + +#[cfg(not(target_os = "windows"))] #[repr(C)] #[derive(Clone, Copy)] pub struct GeckoTimeStamp { diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp @@ -708,8 +708,8 @@ void Performance::MaybeEmitExternalProfilerMarker( uint64_t rawStart = startTimeStamp.RawClockMonotonicNanosecondsSinceBoot(); uint64_t rawEnd = endTimeStamp.RawClockMonotonicNanosecondsSinceBoot(); #elif XP_WIN - uint64_t rawStart = startTimeStamp.RawQueryPerformanceCounterValue(); - uint64_t rawEnd = endTimeStamp.RawQueryPerformanceCounterValue(); + uint64_t rawStart = startTimeStamp.RawQueryPerformanceCounterValue().value(); + uint64_t rawEnd = endTimeStamp.RawQueryPerformanceCounterValue().value(); #elif XP_MACOSX uint64_t rawStart = startTimeStamp.RawMachAbsoluteTimeNanoseconds(); uint64_t rawEnd = endTimeStamp.RawMachAbsoluteTimeNanoseconds(); diff --git a/dom/quota/ActorsParent.cpp b/dom/quota/ActorsParent.cpp @@ -7969,8 +7969,7 @@ QuotaManager::GetOriginInfosExceedingGlobalLimit() const { } QuotaManager::OriginInfosNestedTraversable -QuotaManager::GetOriginInfosWithZeroUsage( - const Maybe<int64_t>& aCutoffAccessTime) const { +QuotaManager::GetOriginInfosWithZeroUsage() const { MutexAutoLock lock(mQuotaMutex); QuotaManager::OriginInfosNestedTraversable res; @@ -7985,8 +7984,7 @@ QuotaManager::GetOriginInfosWithZeroUsage( MOZ_ASSERT(!entry.GetKey().IsEmpty()); MOZ_ASSERT(pair); - pair->MaybeInsertNonPersistedZeroUsageOriginInfos(inserter, - aCutoffAccessTime); + pair->MaybeInsertNonPersistedZeroUsageOriginInfos(inserter); } res.AppendElement(std::move(originInfos)); diff --git a/dom/quota/GroupInfoPair.h b/dom/quota/GroupInfoPair.h @@ -73,12 +73,8 @@ class GroupInfoPair { // Inserts non-persisted origins that also have zero quota-charged usage. // Used by cleanup routines to identify candidate origins for removal. - // - // See QuotaManager::GetOriginInfosWithZeroUsage for the semantics and time - // units of |aCutoffAccessTime|. template <typename Iterator> - void MaybeInsertNonPersistedZeroUsageOriginInfos( - Iterator aDest, const Maybe<int64_t>& aCutoffAccessTime) const; + void MaybeInsertNonPersistedZeroUsageOriginInfos(Iterator aDest) const; private: RefPtr<GroupInfo>& GetGroupInfoForPersistenceType( diff --git a/dom/quota/GroupInfoPairImpl.h b/dom/quota/GroupInfoPairImpl.h @@ -42,11 +42,9 @@ void GroupInfoPair::MaybeInsertNonPersistedOriginInfos(Iterator aDest) const { template <typename Iterator> void GroupInfoPair::MaybeInsertNonPersistedZeroUsageOriginInfos( - Iterator aDest, const Maybe<int64_t>& aCutoffAccessTime) const { - MaybeInsertOriginInfos(aDest, [aCutoffAccessTime](const auto& originInfo) { - return !originInfo->LockedPersisted() && originInfo->LockedUsage() == 0 && - (!aCutoffAccessTime || - originInfo->LockedAccessTime() < *aCutoffAccessTime); + Iterator aDest) const { + MaybeInsertOriginInfos(aDest, [](const auto& originInfo) { + return !originInfo->LockedPersisted() && originInfo->LockedUsage() == 0; }); } diff --git a/dom/quota/QuotaManager.h b/dom/quota/QuotaManager.h @@ -860,19 +860,7 @@ class QuotaManager final : public BackgroundThreadObject { OriginInfosNestedTraversable GetOriginInfosExceedingGlobalLimit() const; - // Returns origins with zero usage. If aCutoffAccessTime is provided, origins - // whose last access time is newer than the cutoff are excluded. - // - // The cutoff time is expressed as an int64_t value in microseconds since the - // Unix epoch (1970-01-01 00:00:00 UTC), matching the format returned by - // PR_Now(). This is the same time unit used throughout Quota Manager for - // access and modification timestamps. - // - // Typically callers compute it as: - // const int64_t cutoff = PR_Now() - (N * PR_USEC_PER_SEC); - // where N is the desired age threshold in seconds (for example, one week). - OriginInfosNestedTraversable GetOriginInfosWithZeroUsage( - const Maybe<int64_t>& aCutoffAccessTime = Nothing()) const; + OriginInfosNestedTraversable GetOriginInfosWithZeroUsage() const; /** * Clears the given set of origins. diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp @@ -3347,8 +3347,7 @@ void ScriptLoader::TryCacheRequest(ScriptLoadRequest* aRequest) { } /* static */ -nsCString& ScriptLoader::BytecodeMimeTypeFor( - const ScriptLoadRequest* aRequest) { +nsCString& ScriptLoader::BytecodeMimeTypeFor(ScriptLoadRequest* aRequest) { if (aRequest->IsModuleRequest()) { return nsContentUtils::JSModuleBytecodeMimeType(); } @@ -3357,7 +3356,7 @@ nsCString& ScriptLoader::BytecodeMimeTypeFor( /* static */ nsCString& ScriptLoader::BytecodeMimeTypeFor( - const JS::loader::LoadedScript* aLoadedScript) { + JS::loader::LoadedScript* aLoadedScript) { if (aLoadedScript->IsModuleScript()) { return nsContentUtils::JSModuleBytecodeMimeType(); } @@ -3615,7 +3614,6 @@ void ScriptLoader::MaybeUpdateDiskCache() { } void ScriptLoader::UpdateDiskCache() { - MOZ_ASSERT(!mCache); LOG(("ScriptLoader (%p): Start bytecode encoding.", this)); // If any script got added in the previous loop cycle, wait until all @@ -3637,31 +3635,20 @@ void ScriptLoader::UpdateDiskCache() { for (auto& loadedScript : mDiskCacheQueue) { // The bytecode encoding is performed only when there was no // bytecode stored in the necko cache. + // + // For in-memory cached case, the save might already be performed + // by other requests. + // See also ScriptLoader::MaybePrepareModuleForDiskCacheAfterExecute. + // + // TODO: Move this to SharedScriptCache. if (!loadedScript->HasDiskCacheReference()) { continue; } - MOZ_ASSERT(loadedScript->HasStencil()); - - Vector<uint8_t> compressed; - if (!EncodeAndCompress(fc, loadedScript, loadedScript->GetStencil(), - loadedScript->SRIAndBytecode(), compressed)) { - loadedScript->DropDiskCacheReference(); - loadedScript->DropBytecode(); - TRACE_FOR_TEST(loadedScript, "diskcache:failed"); - continue; - } - - if (!SaveToDiskCache(loadedScript, compressed)) { - loadedScript->DropDiskCacheReference(); - loadedScript->DropBytecode(); - TRACE_FOR_TEST(loadedScript, "diskcache:failed"); - continue; - } + EncodeBytecodeAndSave(fc, loadedScript); loadedScript->DropDiskCacheReference(); loadedScript->DropBytecode(); - TRACE_FOR_TEST(loadedScript, "diskcache:saved"); } mDiskCacheQueue.Clear(); @@ -3669,20 +3656,25 @@ void ScriptLoader::UpdateDiskCache() { } /* static */ -bool ScriptLoader::EncodeAndCompress( - JS::FrontendContext* aFc, const JS::loader::LoadedScript* aLoadedScript, - JS::Stencil* aStencil, const JS::TranscodeBuffer& aSRI, - Vector<uint8_t>& aCompressed) { - size_t SRILength = aSRI.length(); +void ScriptLoader::EncodeBytecodeAndSave( + JS::FrontendContext* aFc, JS::loader::LoadedScript* aLoadedScript) { + MOZ_ASSERT(aLoadedScript->HasDiskCacheReference()); + MOZ_ASSERT(aLoadedScript->HasStencil()); + + auto bytecodeFailed = mozilla::MakeScopeExit( + [&]() { TRACE_FOR_TEST(aLoadedScript, "diskcache:failed"); }); + + size_t SRILength = aLoadedScript->SRIAndBytecode().length(); MOZ_ASSERT(JS::IsTranscodingBytecodeOffsetAligned(SRILength)); JS::TranscodeBuffer SRIAndBytecode; - if (!SRIAndBytecode.appendAll(aSRI)) { + if (!SRIAndBytecode.appendAll(aLoadedScript->SRIAndBytecode())) { LOG(("LoadedScript (%p): Cannot allocate buffer", aLoadedScript)); - return false; + return; } - JS::TranscodeResult result = JS::EncodeStencil(aFc, aStencil, SRIAndBytecode); + JS::TranscodeResult result = + JS::EncodeStencil(aFc, aLoadedScript->GetStencil(), SRIAndBytecode); if (result != JS::TranscodeResult::Ok) { // Encoding can be aborted for non-supported syntax (e.g. asm.js), or @@ -3691,44 +3683,37 @@ bool ScriptLoader::EncodeAndCompress( JS::ClearFrontendErrors(aFc); LOG(("LoadedScript (%p): Cannot serialize bytecode", aLoadedScript)); - return false; + return; } + Vector<uint8_t> compressedBytecode; // TODO probably need to move this to a helper thread - if (!ScriptBytecodeCompress(SRIAndBytecode, SRILength, aCompressed)) { - return false; + if (!ScriptBytecodeCompress(SRIAndBytecode, SRILength, compressedBytecode)) { + return; } - if (aCompressed.length() >= UINT32_MAX) { + if (compressedBytecode.length() >= UINT32_MAX) { LOG( ("LoadedScript (%p): Bytecode cache is too large to be decoded " "correctly.", aLoadedScript)); - return false; + return; } - return true; -} - -/* static */ -bool ScriptLoader::SaveToDiskCache( - const JS::loader::LoadedScript* aLoadedScript, - const Vector<uint8_t>& aCompressed) { - MOZ_ASSERT(NS_IsMainThread()); - // Open the output stream to the cache entry alternate data storage. This // might fail if the stream is already open by another request, in which // case, we just ignore the current one. nsCOMPtr<nsIAsyncOutputStream> output; nsresult rv = aLoadedScript->mCacheInfo->OpenAlternativeOutputStream( BytecodeMimeTypeFor(aLoadedScript), - static_cast<int64_t>(aCompressed.length()), getter_AddRefs(output)); + static_cast<int64_t>(compressedBytecode.length()), + getter_AddRefs(output)); if (NS_FAILED(rv)) { LOG( ("LoadedScript (%p): Cannot open bytecode cache (rv = %X, output " "= %p)", aLoadedScript, unsigned(rv), output.get())); - return false; + return; } MOZ_ASSERT(output); @@ -3738,18 +3723,20 @@ bool ScriptLoader::SaveToDiskCache( }); uint32_t n; - rv = output->Write(reinterpret_cast<const char*>(aCompressed.begin()), - aCompressed.length(), &n); + rv = output->Write(reinterpret_cast<char*>(compressedBytecode.begin()), + compressedBytecode.length(), &n); LOG( ("LoadedScript (%p): Write bytecode cache (rv = %X, length = %u, " "written = %u)", - aLoadedScript, unsigned(rv), unsigned(aCompressed.length()), n)); + aLoadedScript, unsigned(rv), unsigned(compressedBytecode.length()), n)); if (NS_FAILED(rv)) { - return false; + return; } - MOZ_RELEASE_ASSERT(aCompressed.length() == n); - return true; + MOZ_RELEASE_ASSERT(compressedBytecode.length() == n); + + bytecodeFailed.release(); + TRACE_FOR_TEST(aLoadedScript, "diskcache:saved"); } void ScriptLoader::GiveUpDiskCaching() { diff --git a/dom/script/ScriptLoader.h b/dom/script/ScriptLoader.h @@ -709,9 +709,9 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { JS::Handle<JS::Value> aDebuggerPrivateValue, JS::Handle<JSScript*> aDebuggerIntroductionScript, ErrorResult& aRv); - static nsCString& BytecodeMimeTypeFor(const ScriptLoadRequest* aRequest); + static nsCString& BytecodeMimeTypeFor(ScriptLoadRequest* aRequest); static nsCString& BytecodeMimeTypeFor( - const JS::loader::LoadedScript* aLoadedScript); + JS::loader::LoadedScript* aLoadedScript); // Queue the script load request for caching if we decided to cache it, or // cleanup the script load request fields otherwise. @@ -757,21 +757,10 @@ class ScriptLoader final : public JS::loader::ScriptLoaderInterface { public: /** - * Encode the stencils and compress it. - * aLoadedScript is used only for logging purpose, in order to allow - * performing this off main thread. + * Encode the stencils and save the bytecode to the necko cache. */ - static bool EncodeAndCompress(JS::FrontendContext* aFc, - const JS::loader::LoadedScript* aLoadedScript, - JS::Stencil* aStencil, - const JS::TranscodeBuffer& aSRI, - Vector<uint8_t>& aCompressed); - - /** - * Save the bytecode to the necko cache. - */ - static bool SaveToDiskCache(const JS::loader::LoadedScript* aLoadedScript, - const Vector<uint8_t>& aCompressed); + static void EncodeBytecodeAndSave(JS::FrontendContext* aFc, + JS::loader::LoadedScript* aLoadedScript); private: /** diff --git a/dom/script/SharedScriptCache.cpp b/dom/script/SharedScriptCache.cpp @@ -8,10 +8,8 @@ #include "ScriptLoadHandler.h" // ScriptLoadHandler #include "ScriptLoader.h" // ScriptLoader -#include "ScriptTrace.h" // TRACE_FOR_TEST #include "js/experimental/CompileScript.h" // JS::FrontendContext, JS::NewFrontendContext, JS::DestroyFrontendContext #include "mozilla/Maybe.h" // Maybe, Some, Nothing -#include "mozilla/TaskController.h" // TaskController, Task #include "mozilla/dom/ContentParent.h" // dom::ContentParent #include "nsIMemoryReporter.h" // nsIMemoryReporter, MOZ_DEFINE_MALLOC_SIZE_OF, RegisterWeakMemoryReporter, UnregisterWeakMemoryReporter, MOZ_COLLECT_REPORT, KIND_HEAP, UNITS_BYTES #include "nsIPrefBranch.h" // nsIPrefBranch, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID @@ -223,54 +221,13 @@ bool SharedScriptCache::MaybeScheduleUpdateDiskCache() { return true; } -class ScriptEncodeAndCompressionTask : public mozilla::Task { - public: - ScriptEncodeAndCompressionTask() - : Task(Kind::OffMainThreadOnly, EventQueuePriority::Idle) {} - virtual ~ScriptEncodeAndCompressionTask() = default; - -#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY - bool GetName(nsACString& aName) override { - aName.AssignLiteral("ScriptEncodeAndCompressionTask"); - return true; - } -#endif - - TaskResult Run() override { - SharedScriptCache::Get()->EncodeAndCompress(); - return TaskResult::Complete; - } -}; - -class ScriptSaveTask : public mozilla::Task { - public: - ScriptSaveTask() : Task(Kind::MainThreadOnly, EventQueuePriority::Idle) {} - virtual ~ScriptSaveTask() = default; - -#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY - bool GetName(nsACString& aName) override { - aName.AssignLiteral("ScriptSaveTask"); - return true; - } -#endif - - TaskResult Run() override { - SharedScriptCache::Get()->SaveToDiskCache(); - return TaskResult::Complete; - } -}; - void SharedScriptCache::UpdateDiskCache() { auto strategy = ScriptLoader::GetDiskCacheStrategy(); if (strategy.mIsDisabled) { return; } - mozilla::MutexAutoLock lock(mEncodeMutex); - - if (!mEncodeItems.empty()) { - return; - } + JS::FrontendContext* fc = nullptr; for (auto iter = mComplete.Iter(); !iter.Done(); iter.Next()) { JS::loader::LoadedScript* loadedScript = iter.Data().mResource; @@ -278,69 +235,24 @@ void SharedScriptCache::UpdateDiskCache() { continue; } - if (!mEncodeItems.emplaceBack(loadedScript->GetStencil(), - std::move(loadedScript->SRIAndBytecode()), - loadedScript)) { - continue; + if (!fc) { + // Lazily create the context only when there's at least one script + // that needs to be saved. + fc = JS::NewFrontendContext(); + if (!fc) { + return; + } } - } - - if (mEncodeItems.empty()) { - return; - } - RefPtr<ScriptEncodeAndCompressionTask> encodeTask = - new ScriptEncodeAndCompressionTask(); - RefPtr<ScriptSaveTask> saveTask = new ScriptSaveTask(); - saveTask->AddDependency(encodeTask); + ScriptLoader::EncodeBytecodeAndSave(fc, loadedScript); - TaskController::Get()->AddTask(encodeTask.forget()); - TaskController::Get()->AddTask(saveTask.forget()); -} - -void SharedScriptCache::EncodeAndCompress() { - JS::FrontendContext* fc = JS::NewFrontendContext(); - if (!fc) { - return; + loadedScript->DropDiskCacheReference(); + loadedScript->DropBytecode(); } - mozilla::MutexAutoLock lock(mEncodeMutex); - - for (auto& item : mEncodeItems) { - if (!ScriptLoader::EncodeAndCompress(fc, item.mLoadedScript, item.mStencil, - item.mSRI, item.mCompressed)) { - item.mCompressed.clear(); - } + if (fc) { + JS::DestroyFrontendContext(fc); } - - JS::DestroyFrontendContext(fc); -} - -void SharedScriptCache::SaveToDiskCache() { - MOZ_ASSERT(NS_IsMainThread()); - - mozilla::MutexAutoLock lock(mEncodeMutex); - - for (const auto& item : mEncodeItems) { - if (item.mCompressed.empty()) { - item.mLoadedScript->DropDiskCacheReference(); - item.mLoadedScript->DropBytecode(); - TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed"); - continue; - } - - if (!ScriptLoader::SaveToDiskCache(item.mLoadedScript, item.mCompressed)) { - item.mLoadedScript->DropDiskCacheReference(); - item.mLoadedScript->DropBytecode(); - TRACE_FOR_TEST(item.mLoadedScript, "diskcache:failed"); - } - - item.mLoadedScript->DropDiskCacheReference(); - item.mLoadedScript->DropBytecode(); - TRACE_FOR_TEST(item.mLoadedScript, "diskcache:saved"); - } - - mEncodeItems.clear(); } } // namespace mozilla::dom diff --git a/dom/script/SharedScriptCache.h b/dom/script/SharedScriptCache.h @@ -14,10 +14,8 @@ #include "js/loader/ScriptLoadRequest.h" // JS::loader::ScriptLoadRequest #include "mozilla/CORSMode.h" // mozilla::CORSMode #include "mozilla/MemoryReporting.h" // MallocSizeOf -#include "mozilla/Mutex.h" // Mutex, GUARDED_BY, MutexAutoLock #include "mozilla/RefPtr.h" // RefPtr #include "mozilla/SharedSubResourceCache.h" // SharedSubResourceCache, SharedSubResourceCacheLoadingValueBase, SubResourceNetworkMetadataHolder -#include "mozilla/ThreadSafety.h" // MOZ_GUARDED_BY #include "mozilla/WeakPtr.h" // SupportsWeakPtr #include "mozilla/dom/CacheExpirationTime.h" // CacheExpirationTime #include "mozilla/dom/SRIMetadata.h" // mozilla::dom::SRIMetadata @@ -198,9 +196,6 @@ class SharedScriptCache final bool MaybeScheduleUpdateDiskCache(); void UpdateDiskCache(); - void EncodeAndCompress(); - void SaveToDiskCache(); - // This has to be static because it's also called for loaders that don't have // a sheet cache (loaders that are not owned by a document). static void LoadCompleted(SharedScriptCache*, ScriptLoadData&); @@ -215,28 +210,6 @@ class SharedScriptCache final protected: ~SharedScriptCache(); - - private: - class EncodeItem { - public: - EncodeItem(JS::Stencil* aStencil, JS::TranscodeBuffer&& aSRI, - JS::loader::LoadedScript* aLoadedScript) - : mStencil(aStencil), - mSRI(std::move(aSRI)), - mLoadedScript(aLoadedScript) {} - - // These fields can be touched from multiple threads. - RefPtr<JS::Stencil> mStencil; - JS::TranscodeBuffer mSRI; - Vector<uint8_t> mCompressed; - - // This can be dereferenced only from the main thread. - // Reading the pointer itself is allowed also off main thread. - RefPtr<JS::loader::LoadedScript> mLoadedScript; - }; - - Mutex mEncodeMutex{"SharedScriptCache::mEncodeMutex"}; - Vector<EncodeItem> mEncodeItems MOZ_GUARDED_BY(mEncodeMutex); }; } // namespace dom diff --git a/dom/svg/SVGGeometryElement.cpp b/dom/svg/SVGGeometryElement.cpp @@ -16,7 +16,6 @@ #include "gfxPlatform.h" #include "mozilla/RefPtr.h" #include "mozilla/SVGContentUtils.h" -#include "mozilla/SVGUtils.h" #include "mozilla/dom/DOMPointBinding.h" #include "mozilla/dom/SVGLengthBinding.h" #include "mozilla/gfx/2D.h" @@ -239,11 +238,24 @@ already_AddRefed<DOMSVGPoint> SVGGeometryElement::GetPointAtLength( } gfx::Matrix SVGGeometryElement::LocalTransform() const { + gfx::Matrix result; nsIFrame* f = GetPrimaryFrame(); if (!f || !f->IsTransformed()) { - return {}; + return result; } - return gfx::Matrix(SVGUtils::GetTransformMatrixInUserSpace(f)); + nsStyleTransformMatrix::TransformReferenceBox refBox(f); + const float a2css = AppUnitsPerCSSPixel(); + nsDisplayTransform::FrameTransformProperties props(f, refBox, a2css); + if (!props.HasTransform()) { + return result; + } + auto matrix = nsStyleTransformMatrix::ReadTransforms( + props.mTranslate, props.mRotate, props.mScale, + props.mMotion.ptrOr(nullptr), props.mTransform, refBox, a2css); + if (!matrix.IsIdentity()) { + std::ignore = matrix.CanDraw2D(&result); + } + return result; } float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) { diff --git a/dom/webidl/MediaDebugInfo.webidl b/dom/webidl/MediaDebugInfo.webidl @@ -117,7 +117,6 @@ dictionary DecodedStreamDebugInfo { DOMString instance = ""; long long startTime = 0; long long lastOutputTime = 0; - long long lastReportedPosition = 0; long playing = 0; long long lastAudio = 0; boolean audioQueueFinished = false; diff --git a/ipc/glue/IPCMessageUtilsSpecializations.h b/ipc/glue/IPCMessageUtilsSpecializations.h @@ -27,6 +27,9 @@ #include "mozilla/IntegerRange.h" #include "mozilla/Maybe.h" #include "mozilla/TimeStamp.h" +#ifdef XP_WIN +# include "mozilla/TimeStamp_windows.h" +#endif #include "mozilla/UniquePtr.h" #include "mozilla/Vector.h" @@ -432,6 +435,27 @@ struct ParamTraits<mozilla::TimeStamp> { }; }; +#ifdef XP_WIN + +template <> +struct ParamTraits<mozilla::TimeStampValue> { + typedef mozilla::TimeStampValue paramType; + static void Write(MessageWriter* aWriter, const paramType& aParam) { + WriteParam(aWriter, aParam.mGTC); + WriteParam(aWriter, aParam.mQPC); + WriteParam(aWriter, aParam.mIsNull); + WriteParam(aWriter, aParam.mHasQPC); + } + static bool Read(MessageReader* aReader, paramType* aResult) { + return (ReadParam(aReader, &aResult->mGTC) && + ReadParam(aReader, &aResult->mQPC) && + ReadParam(aReader, &aResult->mIsNull) && + ReadParam(aReader, &aResult->mHasQPC)); + } +}; + +#endif + template <> struct ParamTraits<mozilla::dom::ipc::StructuredCloneData> { typedef mozilla::dom::ipc::StructuredCloneData paramType; diff --git a/js/loader/ImportMap.cpp b/js/loader/ImportMap.cpp @@ -9,7 +9,6 @@ #include "js/Array.h" // IsArrayObject #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/JSON.h" // JS_ParseJSON -#include "js/PropertyDescriptor.h" // JS::PropertyDescriptor #include "LoadedScript.h" #include "ModuleLoaderBase.h" // ScriptLoaderInterface #include "nsContentUtils.h" @@ -372,21 +371,6 @@ static UniquePtr<IntegrityMap> NormalizeIntegrity( return normalized; } -static bool GetOwnProperty(JSContext* aCx, Handle<JSObject*> aObj, - const char* aName, MutableHandle<Value> aValueOut) { - JS::Rooted<mozilla::Maybe<JS::PropertyDescriptor>> desc(aCx); - if (!JS_GetOwnPropertyDescriptor(aCx, aObj, aName, &desc)) { - return false; - } - - if (desc.isNothing()) { - return true; - } - MOZ_ASSERT(!desc->isAccessorDescriptor()); - aValueOut.set(desc->value()); - return true; -} - // https://html.spec.whatwg.org/multipage/webappapis.html#parse-an-import-map-string // static UniquePtr<ImportMap> ImportMap::ParseString( @@ -433,7 +417,7 @@ UniquePtr<ImportMap> ImportMap::ParseString( RootedObject parsedObj(aCx, &parsedVal.toObject()); RootedValue importsVal(aCx); - if (!GetOwnProperty(aCx, parsedObj, "imports", &importsVal)) { + if (!JS_GetProperty(aCx, parsedObj, "imports", &importsVal)) { return nullptr; } @@ -469,7 +453,7 @@ UniquePtr<ImportMap> ImportMap::ParseString( } RootedValue scopesVal(aCx); - if (!GetOwnProperty(aCx, parsedObj, "scopes", &scopesVal)) { + if (!JS_GetProperty(aCx, parsedObj, "scopes", &scopesVal)) { return nullptr; } @@ -505,7 +489,7 @@ UniquePtr<ImportMap> ImportMap::ParseString( } RootedValue integrityVal(aCx); - if (!GetOwnProperty(aCx, parsedObj, "integrity", &integrityVal)) { + if (!JS_GetProperty(aCx, parsedObj, "integrity", &integrityVal)) { return nullptr; } @@ -599,6 +583,7 @@ static mozilla::Result<nsCOMPtr<nsIURI>, ResolveError> ResolveImportsMatch( const SpecifierMap* aSpecifierMap) { // Step 1. For each specifierKey → resolutionResult of specifierMap, for (auto&& [specifierKey, resolutionResult] : *aSpecifierMap) { + nsAutoString specifier{aNormalizedSpecifier}; nsCString asURL = aAsURL ? aAsURL->GetSpecOrDefault() : EmptyCString(); // Step 1.1. If specifierKey is normalizedSpecifier, then: diff --git a/js/src/jit-test/lib/adhoc-multiplatform-test.js b/js/src/jit-test/lib/adhoc-multiplatform-test.js @@ -94,7 +94,7 @@ const archOptions = ldr x29, \\[sp\\]` }, arm: { - encoding: `${HEX}{8}\\s+${HEX}{8}`, + encoding: `${HEX}{8} ${HEX}{8}`, // The move from r9 to fp is writing the callee's wasm instance into // the frame for debug checks -- see WasmFrame.h. prefix: `str fp, \\[sp, #-4\\]! @@ -223,17 +223,37 @@ function codegenTestMultiplatform_adhoc(module_text, export_name, if (!options.no_suffix) { expected = expected + '\n' + suffix; } - expected = fixlines(expected); + if (genArm) { + // For obscure reasons, the arm(32) disassembler prints the + // instruction word twice. Rather than forcing all expected lines to + // do the same, we detect any line starting with 8 hex digits followed + // by a space, and duplicate them so as to match the + // disassembler's output. + let newExpected = ""; + let pattern = /^[0-9a-fA-F]{8} /; + for (line of expected.split(/\n+/)) { + // Remove whitespace at the start of the line. This could happen + // for continuation lines in backtick-style expected strings. + while (line.match(/^\s/)) { + line = line.slice(1); + } + if (line.match(pattern)) { + line = line.slice(0,9) + line; + } + newExpected = newExpected + line + "\n"; + } + expected = newExpected; + } + expected = fixlines(expected, encoding); // Compile the test case and collect disassembly output. let ins = wasmEvalText(module_text, {}, options.features); if (options.instanceBox) options.instanceBox.value = ins; let output = wasmDis(ins.exports[export_name], {tier:"ion", asString:true}); - let output_simple = stripencoding(output, encoding); // Check for success, print diagnostics - let output_matches_expected = output_simple.match(new RegExp(expected)) != null; + let output_matches_expected = output.match(new RegExp(expected)) != null; if (!output_matches_expected) { print("---- adhoc-tier1-test.js: TEST FAILED ----"); } diff --git a/js/src/jit-test/lib/codegen-arm64-test.js b/js/src/jit-test/lib/codegen-arm64-test.js @@ -33,10 +33,9 @@ function codegenTestARM64_adhoc(module_text, export_name, expected, options = {} expected = arm64_prefix + '\n' + expected; if (!options.no_suffix) expected = expected + '\n' + arm64_suffix; - expected = fixlines(expected); + expected = fixlines(expected, `${HEX}{8}`); - const output_simple = stripencoding(output, `${HEX}{8}`); - const output_matches_expected = output_simple.match(new RegExp(expected)) != null; + const output_matches_expected = output.match(new RegExp(expected)) != null; if (!output_matches_expected) { print("---- codegen-arm64-test.js: TEST FAILED ----"); } diff --git a/js/src/jit-test/lib/codegen-test-common.js b/js/src/jit-test/lib/codegen-test-common.js @@ -11,28 +11,21 @@ function wrap(options, funcs) { return `(module ${funcs})`; } -function fixlines(s) { +function fixlines(s, insEncoding) { return s.split(/\n+/) .map(strip) .filter(x => x.length > 0) + .map(x => `(?:0x)?${HEX}+ ${insEncoding} ${x}`) .map(spaces) .join('\n'); } -function stripencoding(s, insEncoding) { - var encoding = RegExp(`^(?:0x)?${HEX}+\\s+${insEncoding}\\s+(.*)$`); - return s.split('\n') - .map(x => x.match(encoding)?.[1] ?? x) - .join('\n'); -} - function strip(s) { - var start = 0, end = s.length; - while (start < s.length && isspace(s.charAt(start))) - start++; - while (end > start && isspace(s.charAt(end - 1))) - end--; - return s.substring(start, end); + while (s.length > 0 && isspace(s.charAt(0))) + s = s.substring(1); + while (s.length > 0 && isspace(s.charAt(s.length-1))) + s = s.substring(0, s.length-1); + return s; } function striplines(s) { diff --git a/js/src/jit-test/lib/codegen-x64-test.js b/js/src/jit-test/lib/codegen-x64-test.js @@ -163,10 +163,9 @@ function codegenTestX64_adhoc(module_text, export_name, expected, options = {}) if (!options.no_suffix) expected = expected + '\n' + x64_suffix; const expected_pretty = striplines(expected); - expected = fixlines(expected); + expected = fixlines(expected, `(?:${HEX}{2} )*`); - const output_simple = stripencoding(output, `(?:${HEX}{2} )*`); - const success = output_simple.match(new RegExp(expected)) != null; + const success = output.match(new RegExp(expected)) != null; if (options.log || !success) { print("Module text:") print(module_text); diff --git a/js/src/jit-test/lib/codegen-x86-test.js b/js/src/jit-test/lib/codegen-x86-test.js @@ -57,10 +57,9 @@ function codegenTestX86_adhoc(module_text, export_name, expected, options = {}) expected = x86_prefix + '\n' + expected; if (!options.no_suffix) expected = expected + '\n' + x86_suffix; - expected = fixlines(expected); + expected = fixlines(expected, `(?:${HEX}{2} )*`); - const output_simple = stripencoding(output, `(?:${HEX}{2} )*`); - const output_matches_expected = output_simple.match(new RegExp(expected)) != null; + const output_matches_expected = output.match(new RegExp(expected)) != null; if (!output_matches_expected) { print("---- codegen-x86-test.js: TEST FAILED ----"); } diff --git a/js/src/jit-test/tests/wasm/binop-x64-ion-codegen.js b/js/src/jit-test/tests/wasm/binop-x64-ion-codegen.js @@ -383,7 +383,7 @@ for ( [pAnyCmp, pAnySel, cmpArgL, cmovArgL ] of )`, 'f', // On Linux we have an extra move - (getBuildConfiguration("windows") ? '' : 'mov %r.+, %r.+\n') + + (getBuildConfiguration("windows") ? '' : '48 89 .. mov %r.+, %r.+\n') + // 'q*' because the disassembler shows 'q' only for the memory cases `mov %r.+, %r.+ cmpq* ${cmpArgL}, %r.+ diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h @@ -2169,10 +2169,13 @@ AnyRegister LAllocation::toAnyRegister() const { } // namespace js #include "jit/shared/LIR-shared.h" -#if defined(JS_CODEGEN_X86) -# include "jit/x86/LIR-x86.h" -#elif defined(JS_CODEGEN_X64) -# include "jit/x64/LIR-x64.h" +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) +# if defined(JS_CODEGEN_X86) +# include "jit/x86/LIR-x86.h" +# elif defined(JS_CODEGEN_X64) +# include "jit/x64/LIR-x64.h" +# endif +# include "jit/x86-shared/LIR-x86-shared.h" #elif defined(JS_CODEGEN_ARM) # include "jit/arm/LIR-arm.h" #elif defined(JS_CODEGEN_ARM64) diff --git a/js/src/jit/LIROps.yaml b/js/src/jit/LIROps.yaml @@ -4160,60 +4160,6 @@ defer_init: true #endif -#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) -- name: DivConstantI - result_type: WordSized - operands: - numerator: WordSized - arguments: - denominator: int32_t - num_temps: 1 - mir_op: Div - -- name: ModConstantI - result_type: WordSized - operands: - numerator: WordSized - arguments: - denominator: int32_t - num_temps: 1 - mir_op: Mod - -- name: UDivConstant - result_type: WordSized - operands: - numerator: WordSized - arguments: - denominator: uint32_t - num_temps: 1 - mir_op: Div - -- name: UModConstant - result_type: WordSized - operands: - numerator: WordSized - arguments: - denominator: uint32_t - num_temps: 1 - mir_op: Mod - -- name: UDiv - result_type: WordSized - operands: - lhs: WordSized - rhs: WordSized - num_temps: 1 - mir_op: Div - -- name: UMod - result_type: WordSized - operands: - lhs: WordSized - rhs: WordSized - num_temps: 1 - mir_op: Mod -#endif - #ifdef JS_CODEGEN_X86 - name: BoxFloatingPoint result_type: BoxedValue @@ -4230,6 +4176,15 @@ - name: UDivOrModI64 gen_boilerplate: false +- name: DivOrModConstantI + gen_boilerplate: false + +- name: UDivOrMod + gen_boilerplate: false + +- name: UDivOrModConstant + gen_boilerplate: false + - name: WasmTruncateToInt64 result_type: Int64 operands: @@ -4290,37 +4245,20 @@ #endif #ifdef JS_CODEGEN_X64 -- name: DivI64 - result_type: Int64 - operands: - lhs: WordSized - rhs: WordSized - num_temps: 1 - mir_op: Div +- name: DivOrModI64 + gen_boilerplate: false -- name: ModI64 - result_type: Int64 - operands: - lhs: WordSized - rhs: WordSized - num_temps: 1 - mir_op: Mod +- name: UDivOrModI64 + gen_boilerplate: false -- name: UDivI64 - result_type: Int64 - operands: - lhs: WordSized - rhs: WordSized - num_temps: 1 - mir_op: Div +- name: DivOrModConstantI + gen_boilerplate: false -- name: UModI64 - result_type: Int64 - operands: - lhs: WordSized - rhs: WordSized - num_temps: 1 - mir_op: Mod +- name: UDivOrMod + gen_boilerplate: false + +- name: UDivOrModConstant + gen_boilerplate: false - name: WasmTruncateToInt64 result_type: Int64 diff --git a/js/src/jit/PerfSpewer.cpp b/js/src/jit/PerfSpewer.cpp @@ -122,7 +122,7 @@ static uint64_t GetMonotonicTimestamp() { # ifdef XP_LINUX return TimeStamp::Now().RawClockMonotonicNanosecondsSinceBoot(); # elif XP_WIN - return TimeStamp::Now().RawQueryPerformanceCounterValue(); + return TimeStamp::Now().RawQueryPerformanceCounterValue().value(); # elif XP_DARWIN return TimeStamp::Now().RawMachAbsoluteTimeNanoseconds(); # else diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -20,6 +20,8 @@ using namespace js; using namespace js::jit; +using mozilla::DebugOnly; + CodeGeneratorX64::CodeGeneratorX64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm, const wasm::CodeMetadata* wasmCodeMeta) @@ -195,75 +197,42 @@ void CodeGenerator::visitMulI64(LMulI64* lir) { } } -template <class LIR> -static void TrapIfDivideByZero(MacroAssembler& masm, LIR* lir, Register rhs) { - auto* mir = lir->mir(); - MOZ_ASSERT(mir->trapOnError()); - - if (mir->canBeDivideByZero()) { - Label nonZero; - masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero); - masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); - masm.bind(&nonZero); - } -} - -void CodeGenerator::visitDivI64(LDivI64* lir) { - Register lhs = ToRegister(lir->lhs()); - Register rhs = ToRegister(lir->rhs()); - - MOZ_ASSERT(lhs == rax); - MOZ_ASSERT(rhs != rax); - MOZ_ASSERT(rhs != rdx); - MOZ_ASSERT(ToRegister(lir->output()) == rax); - MOZ_ASSERT(ToRegister(lir->temp0()) == rdx); - - MDiv* mir = lir->mir(); - - // Handle divide by zero. - TrapIfDivideByZero(masm, lir, rhs); - - // Handle an integer overflow exception from INT64_MIN / -1. - if (mir->canBeNegativeOverflow()) { - Label notOverflow; - masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), &notOverflow); - masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), &notOverflow); - masm.wasmTrap(wasm::Trap::IntegerOverflow, mir->trapSiteDesc()); - masm.bind(&notOverflow); - } - - // Sign extend the lhs into rdx to make rdx:rax. - masm.cqo(); - masm.idivq(rhs); -} - -void CodeGenerator::visitModI64(LModI64* lir) { +void CodeGenerator::visitDivOrModI64(LDivOrModI64* lir) { Register lhs = ToRegister(lir->lhs()); Register rhs = ToRegister(lir->rhs()); Register output = ToRegister(lir->output()); - MOZ_ASSERT(lhs == rax); - MOZ_ASSERT(rhs != rax); + MOZ_ASSERT_IF(lhs != rhs, rhs != rax); MOZ_ASSERT(rhs != rdx); - MOZ_ASSERT(ToRegister(lir->output()) == rdx); - MOZ_ASSERT(ToRegister(lir->temp0()) == rax); - - MMod* mir = lir->mir(); + MOZ_ASSERT_IF(output == rax, ToRegister(lir->remainder()) == rdx); + MOZ_ASSERT_IF(output == rdx, ToRegister(lir->remainder()) == rax); Label done; + // Put the lhs in rax. + if (lhs != rax) { + masm.mov(lhs, rax); + } + // Handle divide by zero. - TrapIfDivideByZero(masm, lir, rhs); + if (lir->canBeDivideByZero()) { + Label nonZero; + masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, lir->trapSiteDesc()); + masm.bind(&nonZero); + } // Handle an integer overflow exception from INT64_MIN / -1. - if (mir->canBeNegativeDividend()) { + if (lir->canBeNegativeOverflow()) { Label notOverflow; masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), &notOverflow); masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), &notOverflow); - { + if (lir->mir()->isMod()) { masm.xorl(output, output); - masm.jump(&done); + } else { + masm.wasmTrap(wasm::Trap::IntegerOverflow, lir->trapSiteDesc()); } + masm.jump(&done); masm.bind(&notOverflow); } @@ -274,38 +243,36 @@ void CodeGenerator::visitModI64(LModI64* lir) { masm.bind(&done); } -void CodeGenerator::visitUDivI64(LUDivI64* lir) { +void CodeGenerator::visitUDivOrModI64(LUDivOrModI64* lir) { + Register lhs = ToRegister(lir->lhs()); Register rhs = ToRegister(lir->rhs()); - MOZ_ASSERT(ToRegister(lir->lhs()) == rax); - MOZ_ASSERT(rhs != rax); + DebugOnly<Register> output = ToRegister(lir->output()); + MOZ_ASSERT_IF(lhs != rhs, rhs != rax); MOZ_ASSERT(rhs != rdx); - MOZ_ASSERT(ToRegister(lir->output()) == rax); - MOZ_ASSERT(ToRegister(lir->temp0()) == rdx); + MOZ_ASSERT_IF(output.value == rax, ToRegister(lir->remainder()) == rdx); + MOZ_ASSERT_IF(output.value == rdx, ToRegister(lir->remainder()) == rax); - // Prevent divide by zero. - TrapIfDivideByZero(masm, lir, rhs); - - // Zero extend the lhs into rdx to make (rdx:rax). - masm.xorl(rdx, rdx); - masm.udivq(rhs); -} - -void CodeGenerator::visitUModI64(LUModI64* lir) { - Register rhs = ToRegister(lir->rhs()); + // Put the lhs in rax. + if (lhs != rax) { + masm.mov(lhs, rax); + } - MOZ_ASSERT(ToRegister(lir->lhs()) == rax); - MOZ_ASSERT(rhs != rax); - MOZ_ASSERT(rhs != rdx); - MOZ_ASSERT(ToRegister(lir->output()) == rdx); - MOZ_ASSERT(ToRegister(lir->temp0()) == rax); + Label done; // Prevent divide by zero. - TrapIfDivideByZero(masm, lir, rhs); + if (lir->canBeDivideByZero()) { + Label nonZero; + masm.branchTestPtr(Assembler::NonZero, rhs, rhs, &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, lir->trapSiteDesc()); + masm.bind(&nonZero); + } // Zero extend the lhs into rdx to make (rdx:rax). masm.xorl(rdx, rdx); masm.udivq(rhs); + + masm.bind(&done); } void CodeGeneratorX64::emitBigIntPtrDiv(LBigIntPtrDiv* ins, Register dividend, diff --git a/js/src/jit/x64/LIR-x64.h b/js/src/jit/x64/LIR-x64.h @@ -28,6 +28,87 @@ class LUnbox : public LInstructionHelper<1, BOX_PIECES, 0> { const char* extraName() const { return StringFromMIRType(mir()->type()); } }; +class LDivOrModI64 : public LBinaryMath<1> { + public: + LIR_HEADER(DivOrModI64) + + LDivOrModI64(const LAllocation& lhs, const LAllocation& rhs, + const LDefinition& temp) + : LBinaryMath(classOpcode) { + setOperand(0, lhs); + setOperand(1, rhs); + setTemp(0, temp); + } + + const LDefinition* remainder() { return getTemp(0); } + + MBinaryArithInstruction* mir() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + return static_cast<MBinaryArithInstruction*>(mir_); + } + bool canBeDivideByZero() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeDivideByZero(); + } + return mir_->toDiv()->canBeDivideByZero(); + } + bool canBeNegativeOverflow() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeNegativeDividend(); + } + return mir_->toDiv()->canBeNegativeOverflow(); + } + const wasm::TrapSiteDesc& trapSiteDesc() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + if (mir_->isMod()) { + return mir_->toMod()->trapSiteDesc(); + } + return mir_->toDiv()->trapSiteDesc(); + } +}; + +// This class performs a simple x86 'div', yielding either a quotient or +// remainder depending on whether this instruction is defined to output +// rax (quotient) or rdx (remainder). +class LUDivOrModI64 : public LBinaryMath<1> { + public: + LIR_HEADER(UDivOrModI64); + + LUDivOrModI64(const LAllocation& lhs, const LAllocation& rhs, + const LDefinition& temp) + : LBinaryMath(classOpcode) { + setOperand(0, lhs); + setOperand(1, rhs); + setTemp(0, temp); + } + + const LDefinition* remainder() { return getTemp(0); } + + const char* extraName() const { + return mir()->isTruncated() ? "Truncated" : nullptr; + } + + MBinaryArithInstruction* mir() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + return static_cast<MBinaryArithInstruction*>(mir_); + } + + bool canBeDivideByZero() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeDivideByZero(); + } + return mir_->toDiv()->canBeDivideByZero(); + } + + const wasm::TrapSiteDesc& trapSiteDesc() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + if (mir_->isMod()) { + return mir_->toMod()->trapSiteDesc(); + } + return mir_->toDiv()->trapSiteDesc(); + } +}; + } // namespace jit } // namespace js diff --git a/js/src/jit/x64/Lowering-x64.cpp b/js/src/jit/x64/Lowering-x64.cpp @@ -526,8 +526,8 @@ void LIRGenerator::visitSubstr(MSubstr* ins) { } void LIRGeneratorX64::lowerDivI64(MDiv* div) { - auto* lir = new (alloc()) LDivI64(useFixedAtStart(div->lhs(), rax), - useRegister(div->rhs()), tempFixed(rdx)); + LDivOrModI64* lir = new (alloc()) LDivOrModI64( + useRegister(div->lhs()), useRegister(div->rhs()), tempFixed(rdx)); defineInt64Fixed(lir, div, LInt64Allocation(LAllocation(AnyRegister(rax)))); } @@ -536,8 +536,8 @@ void LIRGeneratorX64::lowerWasmBuiltinDivI64(MWasmBuiltinDivI64* div) { } void LIRGeneratorX64::lowerModI64(MMod* mod) { - auto* lir = new (alloc()) LModI64(useFixedAtStart(mod->lhs(), rax), - useRegister(mod->rhs()), tempFixed(rax)); + LDivOrModI64* lir = new (alloc()) LDivOrModI64( + useRegister(mod->lhs()), useRegister(mod->rhs()), tempFixed(rax)); defineInt64Fixed(lir, mod, LInt64Allocation(LAllocation(AnyRegister(rdx)))); } @@ -546,14 +546,14 @@ void LIRGeneratorX64::lowerWasmBuiltinModI64(MWasmBuiltinModI64* mod) { } void LIRGeneratorX64::lowerUDivI64(MDiv* div) { - auto* lir = new (alloc()) LUDivI64(useFixedAtStart(div->lhs(), rax), - useRegister(div->rhs()), tempFixed(rdx)); + LUDivOrModI64* lir = new (alloc()) LUDivOrModI64( + useRegister(div->lhs()), useRegister(div->rhs()), tempFixed(rdx)); defineInt64Fixed(lir, div, LInt64Allocation(LAllocation(AnyRegister(rax)))); } void LIRGeneratorX64::lowerUModI64(MMod* mod) { - auto* lir = new (alloc()) LUModI64(useFixedAtStart(mod->lhs(), rax), - useRegister(mod->rhs()), tempFixed(rax)); + LUDivOrModI64* lir = new (alloc()) LUDivOrModI64( + useRegister(mod->lhs()), useRegister(mod->rhs()), tempFixed(rax)); defineInt64Fixed(lir, mod, LInt64Allocation(LAllocation(AnyRegister(rdx)))); } diff --git a/js/src/jit/x64/MacroAssembler-x64-inl.h b/js/src/jit/x64/MacroAssembler-x64-inl.h @@ -84,28 +84,18 @@ void MacroAssembler::notPtr(Register reg) { notq(reg); } void MacroAssembler::andPtr(Register src, Register dest) { andq(src, dest); } -void MacroAssembler::andPtr(Imm32 imm, Register dest) { - if (imm.value >= 0) { - andl(imm, dest); - } else { - andq(imm, dest); - } -} +void MacroAssembler::andPtr(Imm32 imm, Register dest) { andq(imm, dest); } void MacroAssembler::andPtr(Imm32 imm, Register src, Register dest) { if (src != dest) { movq(src, dest); } - andPtr(imm, dest); + andq(imm, dest); } void MacroAssembler::and64(Imm64 imm, Register64 dest) { if (INT32_MIN <= int64_t(imm.value) && int64_t(imm.value) <= INT32_MAX) { - if (int32_t(imm.value) >= 0) { - andl(Imm32(imm.value), dest.reg); - } else { - andq(Imm32(imm.value), dest.reg); - } + andq(Imm32(imm.value), dest.reg); } else { ScratchRegisterScope scratch(*this); movq(ImmWord(uintptr_t(imm.value)), scratch); diff --git a/js/src/jit/x64/MacroAssembler-x64.cpp b/js/src/jit/x64/MacroAssembler-x64.cpp @@ -1578,7 +1578,7 @@ void MacroAssembler::convertUInt64ToDouble(Register64 input, mov(input.reg, scratch); mov(input.reg, temp); shrq(Imm32(1), scratch); - andl(Imm32(1), temp); + andq(Imm32(1), temp); orq(temp, scratch); vcvtsq2sd(scratch, output, output); @@ -1608,7 +1608,7 @@ void MacroAssembler::convertUInt64ToFloat32(Register64 input, mov(input.reg, scratch); mov(input.reg, temp); shrq(Imm32(1), scratch); - andl(Imm32(1), temp); + andq(Imm32(1), temp); orq(temp, scratch); vcvtsq2ss(scratch, output, output); diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -872,54 +872,40 @@ void CodeGenerator::visitMulI(LMulI* ins) { } } -template <class LIR> -static void TrapIfDivideByZero(MacroAssembler& masm, LIR* lir, Register rhs) { - auto* mir = lir->mir(); - MOZ_ASSERT(mir->trapOnError()); - MOZ_ASSERT(mir->canBeDivideByZero()); - - Label nonZero; - masm.branchTest32(Assembler::NonZero, rhs, rhs, &nonZero); - masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); - masm.bind(&nonZero); -} - -OutOfLineCode* CodeGeneratorX86Shared::emitOutOfLineZeroForDivideByZero( - Register rhs, Register output) { - // Truncated division by zero is zero: (±Infinity|0 == 0) and (NaN|0 == 0). - auto* ool = new (alloc()) LambdaOutOfLineCode([=](OutOfLineCode& ool) { - masm.mov(ImmWord(0), output); - masm.jmp(ool.rejoin()); - }); - masm.branchTest32(Assembler::Zero, rhs, rhs, ool->entry()); - - return ool; -} - -void CodeGenerator::visitUDiv(LUDiv* ins) { +void CodeGenerator::visitUDivOrMod(LUDivOrMod* ins) { + Register lhs = ToRegister(ins->lhs()); Register rhs = ToRegister(ins->rhs()); Register output = ToRegister(ins->output()); - Register remainder = ToRegister(ins->temp0()); - MOZ_ASSERT(ToRegister(ins->lhs()) == eax); - MOZ_ASSERT(rhs != eax); + MOZ_ASSERT_IF(lhs != rhs, rhs != eax); MOZ_ASSERT(rhs != edx); - MOZ_ASSERT(output == eax); - MOZ_ASSERT(remainder == edx); - - MDiv* mir = ins->mir(); + MOZ_ASSERT_IF(output == eax, ToRegister(ins->remainder()) == edx); OutOfLineCode* ool = nullptr; + // Put the lhs in eax. + if (lhs != eax) { + masm.mov(lhs, eax); + } + // Prevent divide by zero. - if (mir->canBeDivideByZero()) { - if (mir->trapOnError()) { - TrapIfDivideByZero(masm, ins, rhs); - } else if (mir->isTruncated()) { - ool = emitOutOfLineZeroForDivideByZero(rhs, output); + if (ins->canBeDivideByZero()) { + masm.test32(rhs, rhs); + if (ins->mir()->isTruncated()) { + if (ins->trapOnError()) { + Label nonZero; + masm.j(Assembler::NonZero, &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, ins->trapSiteDesc()); + masm.bind(&nonZero); + } else { + ool = new (alloc()) LambdaOutOfLineCode([=](OutOfLineCode& ool) { + masm.mov(ImmWord(0), output); + masm.jmp(ool.rejoin()); + }); + masm.j(Assembler::Zero, ool->entry()); + } } else { - MOZ_ASSERT(mir->fallible()); - bailoutTest32(Assembler::Zero, rhs, rhs, ins->snapshot()); + bailoutIf(Assembler::Zero, ins->snapshot()); } } @@ -928,102 +914,56 @@ void CodeGenerator::visitUDiv(LUDiv* ins) { masm.udiv(rhs); // If the remainder is > 0, bailout since this must be a double. - if (!mir->canTruncateRemainder()) { - bailoutTest32(Assembler::NonZero, remainder, remainder, ins->snapshot()); + if (ins->mir()->isDiv() && !ins->mir()->toDiv()->canTruncateRemainder()) { + Register remainder = ToRegister(ins->remainder()); + masm.test32(remainder, remainder); + bailoutIf(Assembler::NonZero, ins->snapshot()); } - // Unsigned div can return a value that's not a signed int32. + // Unsigned div or mod can return a value that's not a signed int32. // If our users aren't expecting that, bail. - if (!mir->isTruncated()) { - bailoutTest32(Assembler::Signed, output, output, ins->snapshot()); + if (!ins->mir()->isTruncated()) { + masm.test32(output, output); + bailoutIf(Assembler::Signed, ins->snapshot()); } if (ool) { - addOutOfLineCode(ool, mir); + addOutOfLineCode(ool, ins->mir()); masm.bind(ool->rejoin()); } } -void CodeGenerator::visitUMod(LUMod* ins) { - Register rhs = ToRegister(ins->rhs()); +void CodeGenerator::visitUDivOrModConstant(LUDivOrModConstant* ins) { + Register lhs = ToRegister(ins->numerator()); Register output = ToRegister(ins->output()); + uint32_t d = ins->denominator(); - MOZ_ASSERT(ToRegister(ins->lhs()) == eax); - MOZ_ASSERT(rhs != eax); - MOZ_ASSERT(rhs != edx); - MOZ_ASSERT(output == edx); - MOZ_ASSERT(ToRegister(ins->temp0()) == eax); - - MMod* mir = ins->mir(); - - OutOfLineCode* ool = nullptr; + // This emits the division answer into edx or the modulus answer into eax. + MOZ_ASSERT(output == eax || output == edx); + MOZ_ASSERT(lhs != eax && lhs != edx); + bool isDiv = (output == edx); - // Prevent divide by zero. - if (mir->canBeDivideByZero()) { - if (mir->trapOnError()) { - TrapIfDivideByZero(masm, ins, rhs); - } else if (mir->isTruncated()) { - ool = emitOutOfLineZeroForDivideByZero(rhs, output); + if (d == 0) { + if (ins->mir()->isTruncated()) { + if (ins->trapOnError()) { + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, ins->trapSiteDesc()); + } else { + masm.xorl(output, output); + } } else { - MOZ_ASSERT(mir->fallible()); - bailoutTest32(Assembler::Zero, rhs, rhs, ins->snapshot()); + bailout(ins->snapshot()); } + return; } - // Zero extend the lhs into edx to make (edx:eax), since udiv is 64-bit. - masm.mov(ImmWord(0), edx); - masm.udiv(rhs); - - // Unsigned mod can return a value that's not a signed int32. - // If our users aren't expecting that, bail. - if (!mir->isTruncated()) { - bailoutTest32(Assembler::Signed, output, output, ins->snapshot()); - } - - if (ool) { - addOutOfLineCode(ool, mir); - masm.bind(ool->rejoin()); - } -} - -template <class LUDivOrUMod> -static void UnsignedDivideWithConstant(MacroAssembler& masm, LUDivOrUMod* ins, - Register result, Register temp) { - Register lhs = ToRegister(ins->numerator()); - uint32_t d = ins->denominator(); - - MOZ_ASSERT(lhs != result && lhs != temp); -#ifdef JS_CODEGEN_X86 - MOZ_ASSERT(result == edx && temp == eax); -#else - MOZ_ASSERT(result != temp); -#endif - // The denominator isn't a power of 2 (see LDivPowTwoI and LModPowTwoI). - MOZ_ASSERT(!mozilla::IsPowerOfTwo(d)); + MOZ_ASSERT((d & (d - 1)) != 0); auto rmc = ReciprocalMulConstants::computeUnsignedDivisionConstants(d); // We first compute (M * n) >> 32, where M = rmc.multiplier. -#ifdef JS_CODEGEN_X86 masm.movl(Imm32(rmc.multiplier), eax); masm.umull(lhs); -#else - // Zero-extend |lhs| in preparation for a 64-bit multiplication. - masm.movl(lhs, result); - - // Note that imul sign-extends its 32-bit immediate, but we need an unsigned - // multiplication. - if (int32_t(rmc.multiplier) >= 0) { - masm.imulq(Imm32(rmc.multiplier), result, result); - } else { - masm.movl(Imm32(rmc.multiplier), temp); - masm.imulq(temp, result); - } - if (rmc.multiplier > UINT32_MAX || rmc.shiftAmount == 0) { - masm.shrq(Imm32(32), result); - } -#endif if (rmc.multiplier > UINT32_MAX) { // M >= 2^32 and shift == 0 is impossible, as d >= 2 implies that // ((M * n) >> (32 + shift)) >= n > floor(n/d) whenever n >= d, @@ -1031,112 +971,45 @@ static void UnsignedDivideWithConstant(MacroAssembler& masm, LUDivOrUMod* ins, MOZ_ASSERT(rmc.shiftAmount > 0); MOZ_ASSERT(rmc.multiplier < (int64_t(1) << 33)); - // We actually computed result = ((uint32_t(M) * n) >> 32) instead. Since - // (M * n) >> (32 + shift) is the same as (result + n) >> shift, we can + // We actually computed edx = ((uint32_t(M) * n) >> 32) instead. Since + // (M * n) >> (32 + shift) is the same as (edx + n) >> shift, we can // correct for the overflow. This case is a bit trickier than the signed - // case, though, as the (result + n) addition itself can overflow; however, - // note that - // (result + n) >> shift == (((n - result) >> 1) + result) >> (shift - 1), + // case, though, as the (edx + n) addition itself can overflow; however, + // note that (edx + n) >> shift == (((n - edx) >> 1) + edx) >> (shift - 1), // which is overflow-free. See Hacker's Delight, section 10-8 for details. - // Compute (n - result) >> 1 into temp. - masm.movl(lhs, temp); - masm.subl(result, temp); - masm.shrl(Imm32(1), temp); + // Compute (n - edx) >> 1 into eax. + masm.movl(lhs, eax); + masm.subl(edx, eax); + masm.shrl(Imm32(1), eax); // Finish the computation. - masm.addl(temp, result); - if (rmc.shiftAmount > 1) { - masm.shrl(Imm32(rmc.shiftAmount - 1), result); - } + masm.addl(eax, edx); + masm.shrl(Imm32(rmc.shiftAmount - 1), edx); } else { - if (rmc.shiftAmount > 0) { -#ifdef JS_CODEGEN_X86 - masm.shrl(Imm32(rmc.shiftAmount), result); -#else - masm.shrq(Imm32(32 + rmc.shiftAmount), result); -#endif - } - } -} - -void CodeGenerator::visitUDivConstant(LUDivConstant* ins) { - Register lhs = ToRegister(ins->numerator()); - Register output = ToRegister(ins->output()); - Register temp = ToRegister(ins->temp0()); - uint32_t d = ins->denominator(); - - MDiv* mir = ins->mir(); - -#ifdef JS_CODEGEN_X86 - // This emits the division answer into edx. - MOZ_ASSERT(output == edx); - MOZ_ASSERT(temp == eax); -#endif - - if (d == 0) { - if (mir->trapOnError()) { - masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); - } else if (mir->isTruncated()) { - masm.xorl(output, output); - } else { - bailout(ins->snapshot()); - } - return; - } - - // Compute the truncated division result in |output|. - UnsignedDivideWithConstant(masm, ins, output, temp); - - if (!mir->isTruncated()) { - masm.imull(Imm32(d), output, temp); - bailoutCmp32(Assembler::NotEqual, lhs, temp, ins->snapshot()); - } -} - -void CodeGenerator::visitUModConstant(LUModConstant* ins) { - Register lhs = ToRegister(ins->numerator()); - Register output = ToRegister(ins->output()); - Register temp = ToRegister(ins->temp0()); - uint32_t d = ins->denominator(); - - MMod* mir = ins->mir(); - -#ifdef JS_CODEGEN_X86 - // This emits the modulus answer into eax. - MOZ_ASSERT(output == eax); - MOZ_ASSERT(temp == edx); -#endif - - if (d == 0) { - if (mir->trapOnError()) { - masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); - } else if (mir->isTruncated()) { - masm.xorl(output, output); - } else { - bailout(ins->snapshot()); + masm.shrl(Imm32(rmc.shiftAmount), edx); + } + + // We now have the truncated division value in edx. If we're + // computing a modulus or checking whether the division resulted + // in an integer, we need to multiply the obtained value by d and + // finish the computation/check. + if (!isDiv) { + masm.imull(Imm32(d), edx, edx); + masm.movl(lhs, eax); + masm.subl(edx, eax); + + // The final result of the modulus op, just computed above by the + // sub instruction, can be a number in the range [2^31, 2^32). If + // this is the case and the modulus is not truncated, we must bail + // out. + if (!ins->mir()->isTruncated()) { + bailoutIf(Assembler::Signed, ins->snapshot()); } - return; - } - - // Compute the truncated division result in |temp|. - UnsignedDivideWithConstant(masm, ins, temp, output); - - // We now have the truncated division value in |temp|. If we're computing a - // modulus or checking whether the division resulted in an integer, we need - // to multiply the obtained value by d and finish the computation/check. - // - // output = lhs - d * temp - masm.imull(Imm32(d), temp, temp); - masm.movl(lhs, output); - masm.subl(temp, output); - - // The final result of the modulus op, just computed above by the - // sub instruction, can be a number in the range [2^31, 2^32). If - // this is the case and the modulus is not truncated, we must bail - // out. - if (!mir->isTruncated()) { - bailoutIf(Assembler::Signed, ins->snapshot()); + } else if (!ins->mir()->isTruncated()) { + masm.imull(Imm32(d), edx, eax); + masm.cmpl(lhs, eax); + bailoutIf(Assembler::NotEqual, ins->snapshot()); } } @@ -1154,14 +1027,15 @@ void CodeGenerator::visitDivPowTwoI(LDivPowTwoI* ins) { if (!mir->isTruncated() && negativeDivisor) { // 0 divided by a negative number must return a double. - bailoutTest32(Assembler::Zero, lhs, lhs, ins->snapshot()); + masm.test32(lhs, lhs); + bailoutIf(Assembler::Zero, ins->snapshot()); } if (shift) { if (!mir->isTruncated()) { // If the remainder is != 0, bailout since this must be a double. - bailoutTest32(Assembler::NonZero, lhs, Imm32(UINT32_MAX >> (32 - shift)), - ins->snapshot()); + masm.test32(lhs, Imm32(UINT32_MAX >> (32 - shift))); + bailoutIf(Assembler::NonZero, ins->snapshot()); } if (mir->isUnsigned()) { @@ -1210,165 +1084,93 @@ void CodeGenerator::visitDivPowTwoI(LDivPowTwoI* ins) { masm.bind(&ok); } } else if (mir->isUnsigned() && !mir->isTruncated()) { - // Unsigned division by 1 can overflow if output is not truncated. - bailoutTest32(Assembler::Signed, lhs, lhs, ins->snapshot()); + // Unsigned division by 1 can overflow if output is not + // truncated. + masm.test32(lhs, lhs); + bailoutIf(Assembler::Signed, ins->snapshot()); } } -template <class LDivOrMod> -static void DivideWithConstant(MacroAssembler& masm, LDivOrMod* ins, - Register result, Register temp) { +void CodeGenerator::visitDivOrModConstantI(LDivOrModConstantI* ins) { Register lhs = ToRegister(ins->numerator()); + Register output = ToRegister(ins->output()); int32_t d = ins->denominator(); - MOZ_ASSERT(lhs != result && lhs != temp); -#ifdef JS_CODEGEN_X86 - MOZ_ASSERT(result == edx && temp == eax); -#else - MOZ_ASSERT(result != temp); -#endif + // This emits the division answer into edx or the modulus answer into eax. + MOZ_ASSERT(output == eax || output == edx); + MOZ_ASSERT(lhs != eax && lhs != edx); + bool isDiv = (output == edx); // The absolute value of the denominator isn't a power of 2 (see LDivPowTwoI // and LModPowTwoI). MOZ_ASSERT(!mozilla::IsPowerOfTwo(mozilla::Abs(d))); - auto* mir = ins->mir(); - // We will first divide by Abs(d), and negate the answer if d is negative. // If desired, this can be avoided by generalizing computeDivisionConstants. auto rmc = ReciprocalMulConstants::computeSignedDivisionConstants(d); // We first compute (M * n) >> 32, where M = rmc.multiplier. -#ifdef JS_CODEGEN_X86 masm.movl(Imm32(rmc.multiplier), eax); masm.imull(lhs); -#else - // Sign-extend |lhs| in preparation for a 64-bit multiplication. - masm.movslq(lhs, result); - masm.imulq(Imm32(rmc.multiplier), result, result); - if (rmc.multiplier > INT32_MAX || rmc.shiftAmount == 0) { - masm.shrq(Imm32(32), result); - } -#endif if (rmc.multiplier > INT32_MAX) { MOZ_ASSERT(rmc.multiplier < (int64_t(1) << 32)); - // We actually computed result = ((int32_t(M) * n) >> 32) instead. Since - // (M * n) >> 32 is the same as (result + n), we can correct for the - // overflow. (result + n) can't overflow, as n and |result| have opposite - // signs because int32_t(M) is negative. - masm.addl(lhs, result); + // We actually computed edx = ((int32_t(M) * n) >> 32) instead. Since + // (M * n) >> 32 is the same as (edx + n), we can correct for the overflow. + // (edx + n) can't overflow, as n and edx have opposite signs because + // int32_t(M) is negative. + masm.addl(lhs, edx); } // (M * n) >> (32 + shift) is the truncated division answer if n is // non-negative, as proved in the comments of computeDivisionConstants. We // must add 1 later if n is negative to get the right answer in all cases. - if (rmc.shiftAmount > 0) { -#ifdef JS_CODEGEN_X86 - masm.sarl(Imm32(rmc.shiftAmount), result); -#else - if (rmc.multiplier > INT32_MAX) { - masm.sarl(Imm32(rmc.shiftAmount), result); - } else { - masm.sarq(Imm32(32 + rmc.shiftAmount), result); - } -#endif - } + masm.sarl(Imm32(rmc.shiftAmount), edx); // We'll subtract -1 instead of adding 1, because (n < 0 ? -1 : 0) can be // computed with just a sign-extending shift of 31 bits. - if (mir->canBeNegativeDividend()) { - masm.movl(lhs, temp); - masm.sarl(Imm32(31), temp); - masm.subl(temp, result); + if (ins->canBeNegativeDividend()) { + masm.movl(lhs, eax); + masm.sarl(Imm32(31), eax); + masm.subl(eax, edx); } - // After this, |result| contains the correct truncated division result. + // After this, edx contains the correct truncated division result. if (d < 0) { - masm.negl(result); + masm.negl(edx); } -} - -void CodeGenerator::visitDivConstantI(LDivConstantI* ins) { - Register lhs = ToRegister(ins->numerator()); - Register output = ToRegister(ins->output()); - Register temp = ToRegister(ins->temp0()); - int32_t d = ins->denominator(); - - MDiv* mir = ins->mir(); -#ifdef JS_CODEGEN_X86 - // This emits the division answer into edx. - MOZ_ASSERT(output == edx); - MOZ_ASSERT(temp == eax); -#endif - - if (d == 0) { - if (mir->trapOnError()) { - masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); - } else if (mir->isTruncated()) { - masm.xorl(output, output); - } else { - bailout(ins->snapshot()); - } - return; + if (!isDiv) { + masm.imull(Imm32(-d), edx, eax); + masm.addl(lhs, eax); } - // Compute the truncated division result in |output|. - DivideWithConstant(masm, ins, output, temp); + if (!ins->mir()->isTruncated()) { + if (isDiv) { + // This is a division op. Multiply the obtained value by d to check if + // the correct answer is an integer. This cannot overflow, since |d| > 1. + masm.imull(Imm32(d), edx, eax); + masm.cmp32(lhs, eax); + bailoutIf(Assembler::NotEqual, ins->snapshot()); - if (!mir->isTruncated()) { - // This is a division op. Multiply the obtained value by d to check if - // the correct answer is an integer. This cannot overflow, since |d| > 1. - masm.imull(Imm32(d), output, temp); - bailoutCmp32(Assembler::NotEqual, lhs, temp, ins->snapshot()); - - // If lhs is zero and the divisor is negative, the answer should have - // been -0. - if (d < 0) { - bailoutTest32(Assembler::Zero, lhs, lhs, ins->snapshot()); - } - } -} - -void CodeGenerator::visitModConstantI(LModConstantI* ins) { - Register lhs = ToRegister(ins->numerator()); - Register output = ToRegister(ins->output()); - Register temp = ToRegister(ins->temp0()); - int32_t d = ins->denominator(); + // If lhs is zero and the divisor is negative, the answer should have + // been -0. + if (d < 0) { + masm.test32(lhs, lhs); + bailoutIf(Assembler::Zero, ins->snapshot()); + } + } else if (ins->canBeNegativeDividend()) { + // This is a mod op. If the computed value is zero and lhs + // is negative, the answer should have been -0. + Label done; - MMod* mir = ins->mir(); + masm.cmp32(lhs, Imm32(0)); + masm.j(Assembler::GreaterThanOrEqual, &done); -#ifdef JS_CODEGEN_X86 - // This emits the modulus answer into eax. - MOZ_ASSERT(output == eax); - MOZ_ASSERT(temp == edx); -#endif + masm.test32(eax, eax); + bailoutIf(Assembler::Zero, ins->snapshot()); - if (d == 0) { - if (mir->trapOnError()) { - masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); - } else if (mir->isTruncated()) { - masm.xorl(output, output); - } else { - bailout(ins->snapshot()); + masm.bind(&done); } - return; - } - - // Compute the truncated division result in |temp|. - DivideWithConstant(masm, ins, temp, output); - - // Compute the remainder in |output|: output = lhs - d * temp - masm.imull(Imm32(-d), temp, output); - masm.addl(lhs, output); - - if (!mir->isTruncated() && mir->canBeNegativeDividend()) { - // This is a mod op. If the computed value is zero and lhs - // is negative, the answer should have been -0. - Label done; - masm.branch32(Assembler::GreaterThanOrEqual, lhs, Imm32(0), &done); - bailoutTest32(Assembler::Zero, output, output, ins->snapshot()); - masm.bind(&done); } } @@ -1378,43 +1180,61 @@ void CodeGenerator::visitDivI(LDivI* ins) { Register rhs = ToRegister(ins->rhs()); Register output = ToRegister(ins->output()); - MOZ_ASSERT(lhs == eax); - MOZ_ASSERT(rhs != eax); + MDiv* mir = ins->mir(); + + MOZ_ASSERT_IF(lhs != rhs, rhs != eax); MOZ_ASSERT(rhs != edx); MOZ_ASSERT(remainder == edx); MOZ_ASSERT(output == eax); - MDiv* mir = ins->mir(); - Label done; OutOfLineCode* ool = nullptr; + // Put the lhs in eax, for either the negative overflow case or the regular + // divide case. + if (lhs != eax) { + masm.mov(lhs, eax); + } + // Handle divide by zero. if (mir->canBeDivideByZero()) { + masm.test32(rhs, rhs); if (mir->trapOnError()) { - TrapIfDivideByZero(masm, ins, rhs); + Label nonZero; + masm.j(Assembler::NonZero, &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); + masm.bind(&nonZero); } else if (mir->canTruncateInfinities()) { - ool = emitOutOfLineZeroForDivideByZero(rhs, output); + // Truncated division by zero is zero (Infinity|0 == 0) + if (!ool) { + ool = new (alloc()) LambdaOutOfLineCode([=](OutOfLineCode& ool) { + masm.mov(ImmWord(0), output); + masm.jmp(ool.rejoin()); + }); + } + masm.j(Assembler::Zero, ool->entry()); } else { MOZ_ASSERT(mir->fallible()); - bailoutTest32(Assembler::Zero, rhs, rhs, ins->snapshot()); + bailoutIf(Assembler::Zero, ins->snapshot()); } } // Handle an integer overflow exception from -2147483648 / -1. if (mir->canBeNegativeOverflow()) { Label notOverflow; - masm.branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), &notOverflow); + masm.cmp32(lhs, Imm32(INT32_MIN)); + masm.j(Assembler::NotEqual, &notOverflow); + masm.cmp32(rhs, Imm32(-1)); if (mir->trapOnError()) { - masm.branch32(Assembler::NotEqual, rhs, Imm32(-1), &notOverflow); + masm.j(Assembler::NotEqual, &notOverflow); masm.wasmTrap(wasm::Trap::IntegerOverflow, mir->trapSiteDesc()); } else if (mir->canTruncateOverflow()) { // (-INT32_MIN)|0 == INT32_MIN and INT32_MIN is already in the // output register (lhs == eax). - masm.branch32(Assembler::Equal, rhs, Imm32(-1), &done); + masm.j(Assembler::Equal, &done); } else { MOZ_ASSERT(mir->fallible()); - bailoutCmp32(Assembler::Equal, rhs, Imm32(-1), ins->snapshot()); + bailoutIf(Assembler::Equal, ins->snapshot()); } masm.bind(&notOverflow); } @@ -1422,18 +1242,24 @@ void CodeGenerator::visitDivI(LDivI* ins) { // Handle negative 0. if (!mir->canTruncateNegativeZero() && mir->canBeNegativeZero()) { Label nonzero; - masm.branchTest32(Assembler::NonZero, lhs, lhs, &nonzero); - bailoutCmp32(Assembler::LessThan, rhs, Imm32(0), ins->snapshot()); + masm.test32(lhs, lhs); + masm.j(Assembler::NonZero, &nonzero); + masm.cmp32(rhs, Imm32(0)); + bailoutIf(Assembler::LessThan, ins->snapshot()); masm.bind(&nonzero); } // Sign extend the lhs into edx to make (edx:eax), since idiv is 64-bit. + if (lhs != eax) { + masm.mov(lhs, eax); + } masm.cdq(); masm.idiv(rhs); if (!mir->canTruncateRemainder()) { // If the remainder is > 0, bailout since this must be a double. - bailoutTest32(Assembler::NonZero, remainder, remainder, ins->snapshot()); + masm.test32(remainder, remainder); + bailoutIf(Assembler::NonZero, ins->snapshot()); } masm.bind(&done); @@ -1518,27 +1344,42 @@ void CodeGenerator::visitModI(LModI* ins) { Register rhs = ToRegister(ins->rhs()); // Required to use idiv. - MOZ_ASSERT(lhs == eax); - MOZ_ASSERT(rhs != eax); + MOZ_ASSERT_IF(lhs != rhs, rhs != eax); MOZ_ASSERT(rhs != edx); MOZ_ASSERT(remainder == edx); MOZ_ASSERT(ToRegister(ins->temp0()) == eax); - MMod* mir = ins->mir(); - Label done; OutOfLineCode* ool = nullptr; ModOverflowCheck* overflow = nullptr; + // Set up eax in preparation for doing a div. + if (lhs != eax) { + masm.mov(lhs, eax); + } + + MMod* mir = ins->mir(); + // Prevent divide by zero. if (mir->canBeDivideByZero()) { - if (mir->trapOnError()) { - TrapIfDivideByZero(masm, ins, rhs); - } else if (mir->isTruncated()) { - ool = emitOutOfLineZeroForDivideByZero(rhs, remainder); + masm.test32(rhs, rhs); + if (mir->isTruncated()) { + if (mir->trapOnError()) { + Label nonZero; + masm.j(Assembler::NonZero, &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); + masm.bind(&nonZero); + } else { + if (!ool) { + ool = new (alloc()) LambdaOutOfLineCode([=](OutOfLineCode& ool) { + masm.mov(ImmWord(0), edx); + masm.jmp(ool.rejoin()); + }); + } + masm.j(Assembler::Zero, ool->entry()); + } } else { - MOZ_ASSERT(mir->fallible()); - bailoutTest32(Assembler::Zero, rhs, rhs, ins->snapshot()); + bailoutIf(Assembler::Zero, ins->snapshot()); } } @@ -1584,16 +1425,17 @@ void CodeGenerator::visitModI(LModI* ins) { masm.bind(&negative); // Prevent an integer overflow exception from -2147483648 % -1 + masm.cmp32(lhs, Imm32(INT32_MIN)); overflow = new (alloc()) ModOverflowCheck(ins, rhs); - masm.branch32(Assembler::Equal, lhs, Imm32(INT32_MIN), overflow->entry()); + masm.j(Assembler::Equal, overflow->entry()); masm.bind(overflow->rejoin()); - masm.cdq(); masm.idiv(rhs); if (!mir->isTruncated()) { // A remainder of 0 means that the rval must be -0, which is a double. - bailoutTest32(Assembler::Zero, remainder, remainder, ins->snapshot()); + masm.test32(remainder, remainder); + bailoutIf(Assembler::Zero, ins->snapshot()); } } diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h @@ -84,11 +84,6 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared { void emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base); - // Emit out-of-line code to zero |output| if |rhs| is zero. Used for truncated - // division and modulus instructions. - OutOfLineCode* emitOutOfLineZeroForDivideByZero(Register rhs, - Register output); - void generateInvalidateEpilogue(); template <typename T> diff --git a/js/src/jit/x86-shared/LIR-x86-shared.h b/js/src/jit/x86-shared/LIR-x86-shared.h @@ -0,0 +1,130 @@ +/* -*- 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 jit_x86_shared_LIR_x86_shared_h +#define jit_x86_shared_LIR_x86_shared_h + +namespace js { +namespace jit { + +class LDivOrModConstantI : public LInstructionHelper<1, 1, 1> { + const int32_t denominator_; + + public: + LIR_HEADER(DivOrModConstantI) + + LDivOrModConstantI(const LAllocation& lhs, int32_t denominator, + const LDefinition& temp) + : LInstructionHelper(classOpcode), denominator_(denominator) { + setOperand(0, lhs); + setTemp(0, temp); + } + + const LAllocation* numerator() { return getOperand(0); } + int32_t denominator() const { return denominator_; } + MBinaryArithInstruction* mir() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + return static_cast<MBinaryArithInstruction*>(mir_); + } + bool canBeNegativeDividend() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeNegativeDividend(); + } + return mir_->toDiv()->canBeNegativeDividend(); + } +}; + +// This class performs a simple x86 'div', yielding either a quotient or +// remainder depending on whether this instruction is defined to output eax +// (quotient) or edx (remainder). +class LUDivOrMod : public LBinaryMath<1> { + public: + LIR_HEADER(UDivOrMod); + + LUDivOrMod(const LAllocation& lhs, const LAllocation& rhs, + const LDefinition& temp) + : LBinaryMath(classOpcode) { + setOperand(0, lhs); + setOperand(1, rhs); + setTemp(0, temp); + } + + const LDefinition* remainder() { return getTemp(0); } + + const char* extraName() const { + return mir()->isTruncated() ? "Truncated" : nullptr; + } + + MBinaryArithInstruction* mir() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + return static_cast<MBinaryArithInstruction*>(mir_); + } + + bool canBeDivideByZero() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeDivideByZero(); + } + return mir_->toDiv()->canBeDivideByZero(); + } + + bool trapOnError() const { + if (mir_->isMod()) { + return mir_->toMod()->trapOnError(); + } + return mir_->toDiv()->trapOnError(); + } + + wasm::TrapSiteDesc trapSiteDesc() const { + if (mir_->isMod()) { + return mir_->toMod()->trapSiteDesc(); + } + return mir_->toDiv()->trapSiteDesc(); + } +}; + +class LUDivOrModConstant : public LInstructionHelper<1, 1, 1> { + const uint32_t denominator_; + + public: + LIR_HEADER(UDivOrModConstant) + + LUDivOrModConstant(const LAllocation& lhs, uint32_t denominator, + const LDefinition& temp) + : LInstructionHelper(classOpcode), denominator_(denominator) { + setOperand(0, lhs); + setTemp(0, temp); + } + + const LAllocation* numerator() { return getOperand(0); } + uint32_t denominator() const { return denominator_; } + MBinaryArithInstruction* mir() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + return static_cast<MBinaryArithInstruction*>(mir_); + } + bool canBeNegativeDividend() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeNegativeDividend(); + } + return mir_->toDiv()->canBeNegativeDividend(); + } + bool trapOnError() const { + if (mir_->isMod()) { + return mir_->toMod()->trapOnError(); + } + return mir_->toDiv()->trapOnError(); + } + wasm::TrapSiteDesc trapSiteDesc() const { + if (mir_->isMod()) { + return mir_->toMod()->trapSiteDesc(); + } + return mir_->toDiv()->trapSiteDesc(); + } +}; + +} // namespace jit +} // namespace js + +#endif /* jit_x86_shared_LIR_x86_shared_h */ diff --git a/js/src/jit/x86-shared/Lowering-x86-shared.cpp b/js/src/jit/x86-shared/Lowering-x86-shared.cpp @@ -179,48 +179,40 @@ void LIRGeneratorX86Shared::lowerDivI(MDiv* div) { int32_t shift = FloorLog2(Abs(rhs)); if (rhs != 0 && uint32_t(1) << shift == Abs(rhs)) { LAllocation lhs = useRegisterAtStart(div->lhs()); - + LDivPowTwoI* lir; // When truncated with maybe a non-zero remainder, we have to round the // result toward 0. This requires an extra register to round up/down // whether the left-hand-side is signed. - // - // If the numerator might be signed, and needs adjusting, then an extra - // lhs copy is needed to round the result of the integer division towards - // zero. - // - // Otherwise the numerator is unsigned, so does not need adjusting. bool needRoundNeg = div->canBeNegativeDividend() && div->isTruncated(); - LAllocation lhsCopy = - needRoundNeg ? useRegister(div->lhs()) : LAllocation(); - - auto* lir = new (alloc()) LDivPowTwoI(lhs, lhsCopy, shift, rhs < 0); + if (!needRoundNeg) { + // Numerator is unsigned, so does not need adjusting. + lir = new (alloc()) LDivPowTwoI(lhs, lhs, shift, rhs < 0); + } else { + // Numerator might be signed, and needs adjusting, and an extra lhs copy + // is needed to round the result of the integer division towards zero. + lir = new (alloc()) + LDivPowTwoI(lhs, useRegister(div->lhs()), shift, rhs < 0); + } if (div->fallible()) { assignSnapshot(lir, div->bailoutKind()); } defineReuseInput(lir, div, 0); return; } - -#ifdef JS_CODEGEN_X86 - auto* lir = new (alloc()) - LDivConstantI(useRegister(div->lhs()), tempFixed(eax), rhs); - if (div->fallible()) { - assignSnapshot(lir, div->bailoutKind()); - } - defineFixed(lir, div, LAllocation(AnyRegister(edx))); -#else - auto* lir = - new (alloc()) LDivConstantI(useRegister(div->lhs()), temp(), rhs); - if (div->fallible()) { - assignSnapshot(lir, div->bailoutKind()); + if (rhs != 0) { + LDivOrModConstantI* lir; + lir = new (alloc()) + LDivOrModConstantI(useRegister(div->lhs()), rhs, tempFixed(eax)); + if (div->fallible()) { + assignSnapshot(lir, div->bailoutKind()); + } + defineFixed(lir, div, LAllocation(AnyRegister(edx))); + return; } - define(lir, div); -#endif - return; } - auto* lir = new (alloc()) LDivI(useFixedAtStart(div->lhs(), eax), - useRegister(div->rhs()), tempFixed(edx)); + LDivI* lir = new (alloc()) + LDivI(useRegister(div->lhs()), useRegister(div->rhs()), tempFixed(edx)); if (div->fallible()) { assignSnapshot(lir, div->bailoutKind()); } @@ -232,7 +224,7 @@ void LIRGeneratorX86Shared::lowerModI(MMod* mod) { int32_t rhs = mod->rhs()->toConstant()->toInt32(); int32_t shift = FloorLog2(Abs(rhs)); if (rhs != 0 && uint32_t(1) << shift == Abs(rhs)) { - auto* lir = + LModPowTwoI* lir = new (alloc()) LModPowTwoI(useRegisterAtStart(mod->lhs()), shift); if (mod->fallible()) { assignSnapshot(lir, mod->bailoutKind()); @@ -240,27 +232,20 @@ void LIRGeneratorX86Shared::lowerModI(MMod* mod) { defineReuseInput(lir, mod, 0); return; } - -#ifdef JS_CODEGEN_X86 - auto* lir = new (alloc()) - LModConstantI(useRegister(mod->lhs()), tempFixed(edx), rhs); - if (mod->fallible()) { - assignSnapshot(lir, mod->bailoutKind()); - } - defineFixed(lir, mod, LAllocation(AnyRegister(eax))); -#else - auto* lir = - new (alloc()) LModConstantI(useRegister(mod->lhs()), temp(), rhs); - if (mod->fallible()) { - assignSnapshot(lir, mod->bailoutKind()); + if (rhs != 0) { + LDivOrModConstantI* lir; + lir = new (alloc()) + LDivOrModConstantI(useRegister(mod->lhs()), rhs, tempFixed(edx)); + if (mod->fallible()) { + assignSnapshot(lir, mod->bailoutKind()); + } + defineFixed(lir, mod, LAllocation(AnyRegister(eax))); + return; } - define(lir, mod); -#endif - return; } - auto* lir = new (alloc()) LModI(useFixedAtStart(mod->lhs(), eax), - useRegister(mod->rhs()), tempFixed(eax)); + LModI* lir = new (alloc()) + LModI(useRegister(mod->lhs()), useRegister(mod->rhs()), tempFixed(eax)); if (mod->fallible()) { assignSnapshot(lir, mod->bailoutKind()); } @@ -373,35 +358,26 @@ void LIRGeneratorX86Shared::lowerUDiv(MDiv* div) { uint32_t rhs = div->rhs()->toConstant()->toInt32(); int32_t shift = FloorLog2(rhs); + LAllocation lhs = useRegisterAtStart(div->lhs()); if (rhs != 0 && uint32_t(1) << shift == rhs) { - auto* lir = new (alloc()) LDivPowTwoI(useRegisterAtStart(div->lhs()), - LAllocation(), shift, false); + LDivPowTwoI* lir = new (alloc()) LDivPowTwoI(lhs, lhs, shift, false); if (div->fallible()) { assignSnapshot(lir, div->bailoutKind()); } defineReuseInput(lir, div, 0); } else { -#ifdef JS_CODEGEN_X86 - auto* lir = new (alloc()) - LUDivConstant(useRegister(div->lhs()), tempFixed(eax), rhs); + LUDivOrModConstant* lir = new (alloc()) + LUDivOrModConstant(useRegister(div->lhs()), rhs, tempFixed(eax)); if (div->fallible()) { assignSnapshot(lir, div->bailoutKind()); } defineFixed(lir, div, LAllocation(AnyRegister(edx))); -#else - auto* lir = - new (alloc()) LUDivConstant(useRegister(div->lhs()), temp(), rhs); - if (div->fallible()) { - assignSnapshot(lir, div->bailoutKind()); - } - define(lir, div); -#endif } return; } - auto* lir = new (alloc()) LUDiv(useFixedAtStart(div->lhs(), eax), - useRegister(div->rhs()), tempFixed(edx)); + LUDivOrMod* lir = new (alloc()) LUDivOrMod( + useRegister(div->lhs()), useRegister(div->rhs()), tempFixed(edx)); if (div->fallible()) { assignSnapshot(lir, div->bailoutKind()); } @@ -414,34 +390,25 @@ void LIRGeneratorX86Shared::lowerUMod(MMod* mod) { int32_t shift = FloorLog2(rhs); if (rhs != 0 && uint32_t(1) << shift == rhs) { - auto* lir = + LModPowTwoI* lir = new (alloc()) LModPowTwoI(useRegisterAtStart(mod->lhs()), shift); if (mod->fallible()) { assignSnapshot(lir, mod->bailoutKind()); } defineReuseInput(lir, mod, 0); } else { -#ifdef JS_CODEGEN_X86 - auto* lir = new (alloc()) - LUModConstant(useRegister(mod->lhs()), tempFixed(edx), rhs); + LUDivOrModConstant* lir = new (alloc()) + LUDivOrModConstant(useRegister(mod->lhs()), rhs, tempFixed(edx)); if (mod->fallible()) { assignSnapshot(lir, mod->bailoutKind()); } defineFixed(lir, mod, LAllocation(AnyRegister(eax))); -#else - auto* lir = - new (alloc()) LUModConstant(useRegister(mod->lhs()), temp(), rhs); - if (mod->fallible()) { - assignSnapshot(lir, mod->bailoutKind()); - } - define(lir, mod); -#endif } return; } - auto* lir = new (alloc()) LUMod(useFixedAtStart(mod->lhs(), eax), - useRegister(mod->rhs()), tempFixed(eax)); + LUDivOrMod* lir = new (alloc()) LUDivOrMod( + useRegister(mod->lhs()), useRegister(mod->rhs()), tempFixed(eax)); if (mod->fallible()) { assignSnapshot(lir, mod->bailoutKind()); } diff --git a/js/src/jit/x86/CodeGenerator-x86.cpp b/js/src/jit/x86/CodeGenerator-x86.cpp @@ -886,10 +886,10 @@ void CodeGenerator::visitDivOrModI64(LDivOrModI64* lir) { masm.branch64(Assembler::NotEqual, rhs, Imm64(-1), &notOverflow); if (mir->isWasmBuiltinModI64()) { masm.xor64(output, output); - masm.jump(&done); } else { masm.wasmTrap(wasm::Trap::IntegerOverflow, lir->trapSiteDesc()); } + masm.jump(&done); masm.bind(&notOverflow); } diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp @@ -1243,13 +1243,6 @@ void PresShell::Destroy() { weakFrame->Clear(this); } - // Clear the embedding frame only after tearing down the frame tree, since we - // rely on reaching the display root frame from frame destruction. - if (nsSubDocumentFrame* f = GetInProcessEmbedderFrame()) { - f->RemoveEmbeddingPresShell(this); - } - mEmbedderFrame = nullptr; - // Terminate AccessibleCaretEventHub after tearing down the frame tree so that // we don't need to remove caret element's frame in // AccessibleCaret::RemoveCaretElement(). @@ -1291,15 +1284,6 @@ nsRefreshDriver* PresShell::GetRefreshDriver() const { return mPresContext ? mPresContext->RefreshDriver() : nullptr; } -// NOTE(emilio): It'd be ideal if instead of this explicit tracking we could -// rely on mDocument->GetEmbedderElement()->GetPrimaryFrame() + relevant -// null-checks. However, given how things are set up now, the embedder element -// in BrowsingContext / Window get cleared before tearing down the pres shell, -// and RDL relies on getting ahold of it to get the display root. -void PresShell::SetInProcessEmbedderFrame(nsSubDocumentFrame* aFrame) { - mEmbedderFrame = aFrame; -} - void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) { if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) { StyleSet()->SetAuthorStyleDisabled(aStyleDisabled); @@ -6178,17 +6162,20 @@ void PresShell::RebuildApproximateFrameVisibilityDisplayList( DecApproximateVisibleCount(oldApproximatelyVisibleFrames); } -void PresShell::ClearApproximateFrameVisibilityVisited() { - if (!mApproximateFrameVisibilityVisited) { - ClearApproximatelyVisibleFramesList(); - } - mApproximateFrameVisibilityVisited = false; - mDocument->EnumerateSubDocuments([](Document& aSubdoc) { - if (auto* ps = aSubdoc.GetPresShell()) { - ps->ClearApproximateFrameVisibilityVisited(); +/* static */ +void PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView, + bool aClear) { + nsViewManager* vm = aView->GetViewManager(); + if (aClear) { + PresShell* presShell = vm->GetPresShell(); + if (!presShell->mApproximateFrameVisibilityVisited) { + presShell->ClearApproximatelyVisibleFramesList(); } - return CallState::Continue; - }); + presShell->mApproximateFrameVisibilityVisited = false; + } + for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) { + ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm); + } } void PresShell::ClearApproximatelyVisibleFramesList( @@ -6396,7 +6383,7 @@ void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) { } RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly); - ClearApproximateFrameVisibilityVisited(); + ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true); #ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST // This can be used to debug the frame walker by comparing beforeFrameList @@ -6427,7 +6414,7 @@ void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) { RebuildApproximateFrameVisibilityDisplayList(list); - ClearApproximateFrameVisibilityVisited(); + ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true); list.DeleteAll(&builder); #endif @@ -6984,16 +6971,17 @@ void PresShell::DisableNonTestMouseEvents(bool aDisable) { nsPoint PresShell::GetEventLocation(const WidgetMouseEvent& aEvent) const { nsIFrame* rootFrame = GetRootFrame(); - if (!rootFrame) { - // Matches old TranslateWidgetToView behavior - return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + if (rootFrame) { + RelativeTo relativeTo{rootFrame}; + if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + relativeTo.mViewportType = ViewportType::Visual; + } + return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo); } - RelativeTo relativeTo{rootFrame}; - if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) { - relativeTo.mViewportType = ViewportType::Visual; - } - return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo); + nsView* rootView = mViewManager->GetRootView(); + return nsLayoutUtils::TranslateWidgetToView(mPresContext, aEvent.mWidget, + aEvent.mRefPoint, rootView); } void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) { @@ -9997,24 +9985,22 @@ bool PresShell::EventHandler::AdjustContextMenuKeyEvent( // up in the upper left of the relevant content area before we create // the DOM event. Since we never call InitMouseEvent() on the event, // the client X/Y will be 0,0. We can make use of that if the widget is null. - // Use the root widget since it's most likely to exist, and the coordinates - // returned by GetCurrentItemAndPositionForElement are relative to it. + // Use the root view manager's widget since it's most likely to have one, + // and the coordinates returned by GetCurrentItemAndPositionForElement + // are relative to the widget of the root of the root view manager. nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext(); - aMouseEvent->mRefPoint = LayoutDeviceIntPoint(); + aMouseEvent->mRefPoint = LayoutDeviceIntPoint(0, 0); if (rootPC) { aMouseEvent->mWidget = rootPC->PresShell()->GetRootWidget(); if (aMouseEvent->mWidget) { // default the refpoint to the topleft of our document - nsPoint frameToWidgetOffset; - if (nsIFrame* rootFrame = FrameConstructor()->GetRootFrame()) { - nsIWidget* widget = rootFrame->GetNearestWidget(frameToWidgetOffset); - MOZ_ASSERT(widget, "If rootPC has a widget, so should we"); - auto widgetToWidgetOffset = - nsLayoutUtils::WidgetToWidgetOffset(widget, aMouseEvent->mWidget); - aMouseEvent->mRefPoint = - widgetToWidgetOffset + - LayoutDeviceIntPoint::FromAppUnitsToNearest( - frameToWidgetOffset, GetPresContext()->AppUnitsPerDevPixel()); + nsPoint offset(0, 0); + nsIFrame* rootFrame = FrameConstructor()->GetRootFrame(); + if (rootFrame) { + nsView* view = rootFrame->GetClosestView(&offset); + offset += view->GetOffsetToWidget(aMouseEvent->mWidget); + aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest( + offset, GetPresContext()->AppUnitsPerDevPixel()); } } } else { @@ -10147,24 +10133,22 @@ bool PresShell::EventHandler::PrepareToUseCaretPosition( nsPresContext* presContext = GetPresContext(); - // get caret position relative to the closest widget + // get caret position relative to the closest view nsRect caretCoords; nsIFrame* caretFrame = caret->GetGeometry(&caretCoords); if (!caretFrame) { return false; } - nsPoint widgetOffset; - nsIWidget* widget = caretFrame->GetNearestWidget(widgetOffset); - if (!widget) { + nsPoint viewOffset; + nsView* view = caretFrame->GetClosestView(&viewOffset); + if (!view) { return false; } // and then get the caret coords relative to the event widget if (aEventWidget) { - widgetOffset += LayoutDeviceIntPoint::ToAppUnits( - nsLayoutUtils::WidgetToWidgetOffset(widget, aEventWidget), - presContext->AppUnitsPerDevPixel()); + viewOffset += view->GetOffsetToWidget(aEventWidget); } - caretCoords.MoveBy(widgetOffset); + caretCoords.MoveBy(viewOffset); // caret coordinates are in app units, convert to pixels aTargetPt.x = @@ -10254,21 +10238,21 @@ void PresShell::EventHandler::GetCurrentItemAndPositionForElement( focusedContent = item; } - if (nsIFrame* frame = focusedContent->GetPrimaryFrame()) { + nsIFrame* frame = focusedContent->GetPrimaryFrame(); + if (frame) { NS_ASSERTION( frame->PresContext() == GetPresContext(), "handling event for focused content that is not in our document?"); - nsPoint widgetOffset(0, 0); + nsPoint frameOrigin(0, 0); - // Get the frame's origin within its closest widget - nsIWidget* widget = frame->GetNearestWidget(widgetOffset); + // Get the frame's origin within its view + nsView* view = frame->GetClosestView(&frameOrigin); + NS_ASSERTION(view, "No view for frame"); - // And make it relative to aRootWidget + // View's origin relative the widget if (aRootWidget) { - widgetOffset += LayoutDeviceIntPoint::ToAppUnits( - nsLayoutUtils::WidgetToWidgetOffset(widget, aRootWidget), - frame->PresContext()->AppUnitsPerDevPixel()); + frameOrigin += view->GetOffsetToWidget(aRootWidget); } // Start context menu down and to the right from top left of frame @@ -10300,9 +10284,9 @@ void PresShell::EventHandler::GetCurrentItemAndPositionForElement( } } - aTargetPt.x = presContext->AppUnitsToDevPixels(widgetOffset.x); + aTargetPt.x = presContext->AppUnitsToDevPixels(frameOrigin.x); aTargetPt.y = - presContext->AppUnitsToDevPixels(widgetOffset.y + extra + extraTreeY); + presContext->AppUnitsToDevPixels(frameOrigin.y + extra + extraTreeY); } NS_IF_ADDREF(*aTargetToUse = focusedContent); @@ -10343,18 +10327,6 @@ void PresShell::WillPaint() { FlushPendingNotifications(ChangesToFlush(FlushType::InterruptibleLayout, /* aFlushAnimations = */ false, /* aUpdateRelevancy = */ false)); - if (mIsDestroying) { - return; - } - mDocument->EnumerateSubDocuments( - [](Document& aSubdoc) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { - if (RefPtr ps = aSubdoc.GetPresShell()) { - if (!ps->IsUnderHiddenEmbedderElement()) { - ps->WillPaint(); - } - } - return CallState::Continue; - }); } void PresShell::DidPaintWindow() { @@ -10379,9 +10351,26 @@ void PresShell::DidPaintWindow() { } nsSubDocumentFrame* PresShell::GetInProcessEmbedderFrame() const { - nsIFrame* f = mEmbedderFrame.GetFrame(); + if (!mViewManager) { + return nullptr; + } + // We may not have a root frame yet, so use views. + nsView* view = mViewManager->GetRootView(); + if (!view) { + return nullptr; + } + view = view->GetParent(); // anonymous inner view + if (!view) { + return nullptr; + } + view = view->GetParent(); // subdocumentframe's view + if (!view) { + return nullptr; + } + + nsIFrame* f = view->GetFrame(); MOZ_ASSERT_IF(f, f->IsSubDocumentFrame()); - return static_cast<nsSubDocumentFrame*>(f); + return do_QueryFrame(f); } bool PresShell::IsVisible() const { @@ -12242,19 +12231,7 @@ void PresShell::UpdateImageLockingState() { } nsIWidget* PresShell::GetRootWidget() const { - if (!mPresContext) { - return nullptr; - } - for (nsPresContext* pc = mPresContext; pc; pc = pc->GetParentPresContext()) { - if (auto* vm = pc->PresShell()->GetViewManager()) { - if (auto* view = vm->GetRootView()) { - if (auto* widget = view->GetWidget()) { - return widget; - } - } - } - } - return nullptr; + return mViewManager ? mViewManager->GetRootWidget() : nullptr; } PresShell* PresShell::GetRootPresShell() const { diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h @@ -463,7 +463,6 @@ class PresShell final : public nsStubDocumentObserver, // Get the current frame of our embedder, if it's in our same process. nsSubDocumentFrame* GetInProcessEmbedderFrame() const; - void SetInProcessEmbedderFrame(nsSubDocumentFrame*); /** * Get root scroll container frame from the frame constructor. @@ -3158,7 +3157,8 @@ class PresShell final : public nsStubDocumentObserver, void ClearApproximatelyVisibleFramesList( const Maybe<OnNonvisible>& aNonvisibleAction = Nothing()); - void ClearApproximateFrameVisibilityVisited(); + static void ClearApproximateFrameVisibilityVisited(nsView* aView, + bool aClear); static void MarkFramesInListApproximatelyVisible(const nsDisplayList& aList); void MarkFramesInSubtreeApproximatelyVisible(nsIFrame* aFrame, const nsRect& aRect, @@ -3359,9 +3359,6 @@ class PresShell final : public nsStubDocumentObserver, // The focus sequence number of the last processed input event uint64_t mAPZFocusSequenceNumber; - // The nsSubDocumentFrame* that is embedding us. - WeakFrame mEmbedderFrame; - nscoord mLastAnchorScrollPositionY = 0; // Most recent canvas background color. diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp @@ -126,9 +126,11 @@ #include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/WindowGlobalChild.h" -namespace mozilla::dom { +namespace mozilla { +namespace dom { class PrintPreviewResultInfo; -} // namespace mozilla::dom +} // namespace dom +} // namespace mozilla using namespace mozilla; using namespace mozilla::dom; @@ -337,9 +339,15 @@ class nsDocumentViewer final : public nsIDocumentViewer, * Creates a view manager, root view, and widget for the root view, setting * mViewManager and mWindow. * @param aSize the initial size in appunits + * @param aContainerView the container view to hook our root view up + * to as a child, or null if this will be the root view manager */ - void MakeWindow(const nsSize& aSize); - nsresult CreateDeviceContext(nsSubDocumentFrame* aContainerFrame); + void MakeWindow(const nsSize& aSize, nsView* aContainerView); + + /** + * Create our device context + */ + nsresult CreateDeviceContext(nsView* aContainerView); /** * If aDoCreation is true, this creates the device context, creates a @@ -684,8 +692,7 @@ nsresult nsDocumentViewer::InitPresentationStuff(bool aDoInitialReflow) { nsCOMPtr<Document> doc = mDocument; RefPtr<nsPresContext> presContext = mPresContext; RefPtr<nsViewManager> viewManager = mViewManager; - mPresShell = - doc->CreatePresShell(presContext, viewManager, FindContainerFrame()); + mPresShell = doc->CreatePresShell(presContext, viewManager); if (!mPresShell) { return NS_ERROR_FAILURE; } @@ -762,8 +769,8 @@ nsresult nsDocumentViewer::InitPresentationStuff(bool aDoInitialReflow) { static already_AddRefed<nsPresContext> CreatePresContext( Document* aDocument, nsPresContext::nsPresContextType aType, - nsIFrame* aContainerFrame) { - RefPtr<nsPresContext> result = aContainerFrame + nsView* aContainerView) { + RefPtr<nsPresContext> result = aContainerView ? new nsPresContext(aDocument, aType) : new nsRootPresContext(aDocument, aType); @@ -790,11 +797,11 @@ nsresult nsDocumentViewer::InitInternal( nsresult rv = NS_OK; NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER); - nsSubDocumentFrame* containerFrame = FindContainerFrame(); + nsView* containerView = FindContainerView(); bool makeCX = false; if (aDoCreation) { - nsresult rv = CreateDeviceContext(containerFrame); + nsresult rv = CreateDeviceContext(containerView); NS_ENSURE_SUCCESS(rv, rv); // XXXbz this is a nasty hack to do with the fact that we create @@ -802,7 +809,7 @@ nsresult nsDocumentViewer::InitInternal( // it in one place (Show()) and require that callers call init(), open(), // show() in that order or something. if (!mPresContext && - (aParentWidget || containerFrame || mDocument->IsBeingUsedAsImage() || + (aParentWidget || containerView || mDocument->IsBeingUsedAsImage() || (mDocument->GetDisplayDocument() && mDocument->GetDisplayDocument()->GetPresShell()))) { // Create presentation context @@ -811,7 +818,7 @@ nsresult nsDocumentViewer::InitInternal( // is calling this method } else { mPresContext = CreatePresContext( - mDocument, nsPresContext::eContext_Galley, containerFrame); + mDocument, nsPresContext::eContext_Galley, containerView); } NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); @@ -839,7 +846,8 @@ nsresult nsDocumentViewer::InitInternal( // FlushPendingNotifications() calls down the road... MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(aBounds.width), - mPresContext->DevPixelsToAppUnits(aBounds.height))); + mPresContext->DevPixelsToAppUnits(aBounds.height)), + containerView); Hide(); #ifdef NS_PRINT_PREVIEW @@ -888,6 +896,7 @@ nsresult nsDocumentViewer::InitInternal( if (aDoCreation && mPresContext) { // The ViewManager and Root View was created above (in // MakeWindow())... + rv = InitPresentationStuff(!makeCX); } @@ -1597,9 +1606,32 @@ nsDocumentViewer::Destroy() { mSHEntry->SetSticky(mIsSticky); mIsSticky = true; - // Clear our display items. - if (nsSubDocumentFrame* f = FindContainerFrame()) { - f->ClearDisplayItems(); + // Remove our root view from the view hierarchy. + if (mPresShell) { + nsViewManager* vm = mPresShell->GetViewManager(); + if (vm) { + nsView* rootView = vm->GetRootView(); + + if (rootView) { + nsView* rootViewParent = rootView->GetParent(); + if (rootViewParent) { + nsView* subdocview = rootViewParent->GetParent(); + if (subdocview) { + nsIFrame* f = subdocview->GetFrame(); + if (f) { + nsSubDocumentFrame* s = do_QueryFrame(f); + if (s) { + s->ClearDisplayItems(); + } + } + } + nsViewManager* parentVM = rootViewParent->GetViewManager(); + if (parentVM) { + parentVM->RemoveChild(rootView); + } + } + } + } } Hide(); @@ -2041,16 +2073,16 @@ nsDocumentViewer::Show() { } } - nsSubDocumentFrame* containerFrame = FindContainerFrame(); + nsView* containerView = FindContainerView(); - nsresult rv = CreateDeviceContext(containerFrame); + nsresult rv = CreateDeviceContext(containerView); NS_ENSURE_SUCCESS(rv, rv); // Create presentation context NS_ASSERTION(!mPresContext, "Shouldn't have a prescontext if we have no shell!"); mPresContext = CreatePresContext(mDocument, nsPresContext::eContext_Galley, - containerFrame); + containerView); NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); rv = mPresContext->Init(mDeviceContext); @@ -2060,7 +2092,8 @@ nsDocumentViewer::Show() { } MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(mBounds.width), - mPresContext->DevPixelsToAppUnits(mBounds.height))); + mPresContext->DevPixelsToAppUnits(mBounds.height)), + containerView); if (mPresContext) { Hide(); @@ -2175,7 +2208,7 @@ nsDocumentViewer::ClearHistoryEntry() { //------------------------------------------------------- -void nsDocumentViewer::MakeWindow(const nsSize& aSize) { +void nsDocumentViewer::MakeWindow(const nsSize& aSize, nsView* aContainerView) { if (GetIsPrintPreview()) { return; } @@ -2185,7 +2218,8 @@ void nsDocumentViewer::MakeWindow(const nsSize& aSize) { // The root view is always at 0,0. nsRect tbounds(nsPoint(), aSize); // Create a view - nsView* view = mViewManager->CreateView(tbounds, nullptr); + nsView* view = mViewManager->CreateView(tbounds, aContainerView); + MOZ_ASSERT(view); // Create a widget if we were given a parent widget or don't have a // container view that we can hook up to without a widget. @@ -2193,7 +2227,7 @@ void nsDocumentViewer::MakeWindow(const nsSize& aSize) { // because when they're displayed, they're painted into *another* document's // widget. if (!mDocument->IsResourceDoc()) { - MOZ_ASSERT_IF(!FindContainerFrame(), mParentWidget); + MOZ_ASSERT_IF(!aContainerView, mParentWidget); if (mParentWidget) { // Reuse the top level parent widget. view->AttachToTopLevelWidget(mParentWidget); @@ -2222,7 +2256,7 @@ void nsDocumentViewer::DetachFromTopLevelWidget() { mAttachedToParent = false; } -nsSubDocumentFrame* nsDocumentViewer::FindContainerFrame() { +nsView* nsDocumentViewer::FindContainerView() { if (!mContainer) { return nullptr; } @@ -2253,20 +2287,22 @@ nsSubDocumentFrame* nsDocumentViewer::FindContainerFrame() { return nullptr; } - return static_cast<nsSubDocumentFrame*>(subdocFrame); + NS_ASSERTION(subdocFrame->GetView(), "Subdoc frames must have views"); + return static_cast<nsSubDocumentFrame*>(subdocFrame)->EnsureInnerView(); } -nsresult nsDocumentViewer::CreateDeviceContext( - nsSubDocumentFrame* aContainerFrame) { +nsresult nsDocumentViewer::CreateDeviceContext(nsView* aContainerView) { MOZ_ASSERT(!mPresShell && !mWindow, "This will screw up our existing presentation"); MOZ_ASSERT(mDocument, "Gotta have a document here"); - if (Document* doc = mDocument->GetDisplayDocument()) { - NS_ASSERTION(!aContainerFrame, + Document* doc = mDocument->GetDisplayDocument(); + if (doc) { + NS_ASSERTION(!aContainerView, "External resource document embedded somewhere?"); // We want to use our display document's device context if possible - if (nsPresContext* ctx = doc->GetPresContext()) { + nsPresContext* ctx = doc->GetPresContext(); + if (ctx) { mDeviceContext = ctx->DeviceContext(); return NS_OK; } @@ -2275,8 +2311,8 @@ nsresult nsDocumentViewer::CreateDeviceContext( // Create a device context even if we already have one, since our widget // might have changed. nsIWidget* widget = nullptr; - if (aContainerFrame) { - widget = aContainerFrame->GetNearestWidget(); + if (aContainerView) { + widget = aContainerView->GetNearestWidget(nullptr); } if (!widget) { widget = mParentWidget; @@ -3309,13 +3345,14 @@ NS_IMETHODIMP nsDocumentViewer::SetPrintSettingsForSubdocument( NS_ENSURE_SUCCESS(rv, rv); mPresContext = CreatePresContext( - mDocument, nsPresContext::eContext_PrintPreview, FindContainerFrame()); + mDocument, nsPresContext::eContext_PrintPreview, FindContainerView()); mPresContext->SetPrintSettings(aPrintSettings); rv = mPresContext->Init(mDeviceContext); NS_ENSURE_SUCCESS(rv, rv); MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(mBounds.width), - mPresContext->DevPixelsToAppUnits(mBounds.height))); + mPresContext->DevPixelsToAppUnits(mBounds.height)), + FindContainerView()); MOZ_TRY(InitPresentationStuff(true)); } @@ -3351,7 +3388,7 @@ NS_IMETHODIMP nsDocumentViewer::SetPageModeForTesting( NS_ENSURE_STATE(mDocument); if (aPageMode) { mPresContext = CreatePresContext( - mDocument, nsPresContext::eContext_PageLayout, FindContainerFrame()); + mDocument, nsPresContext::eContext_PageLayout, FindContainerView()); NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY); mPresContext->SetPaginatedScrolling(true); mPresContext->SetPrintSettings(aPrintSettings); @@ -3411,8 +3448,18 @@ void nsDocumentViewer::DestroyPresShell() { } void nsDocumentViewer::InvalidatePotentialSubDocDisplayItem() { - if (nsSubDocumentFrame* f = FindContainerFrame()) { - f->MarkNeedsDisplayItemRebuild(); + if (mViewManager) { + if (nsView* rootView = mViewManager->GetRootView()) { + if (nsView* rootViewParent = rootView->GetParent()) { + if (nsView* subdocview = rootViewParent->GetParent()) { + if (nsIFrame* f = subdocview->GetFrame()) { + if (nsSubDocumentFrame* s = do_QueryFrame(f)) { + s->MarkNeedsDisplayItemRebuild(); + } + } + } + } + } } } diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp @@ -1211,6 +1211,37 @@ nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) { } // static +nsView* nsLayoutUtils::FindSiblingViewFor(nsView* aParentView, + nsIFrame* aFrame) { + nsIFrame* parentViewFrame = aParentView->GetFrame(); + nsIContent* parentViewContent = + parentViewFrame ? parentViewFrame->GetContent() : nullptr; + for (nsView* insertBefore = aParentView->GetFirstChild(); insertBefore; + insertBefore = insertBefore->GetNextSibling()) { + nsIFrame* f = insertBefore->GetFrame(); + if (!f) { + // this view could be some anonymous view attached to a meaningful parent + for (nsView* searchView = insertBefore->GetParent(); searchView; + searchView = searchView->GetParent()) { + f = searchView->GetFrame(); + if (f) { + break; + } + } + NS_ASSERTION(f, "Can't find a frame anywhere!"); + } + if (!f || !aFrame->GetContent() || !f->GetContent() || + nsContentUtils::CompareTreePosition<TreeKind::Flat>( + aFrame->GetContent(), f->GetContent(), parentViewContent) > 0) { + // aFrame's content is after f's content (or we just don't know), + // so put our view before f's view + return insertBefore; + } + } + return nullptr; +} + +// static ScrollContainerFrame* nsLayoutUtils::GetScrollContainerFrameFor( const nsIFrame* aScrolledFrame) { nsIFrame* frame = aScrolledFrame->GetParent(); @@ -1485,21 +1516,15 @@ nsPoint GetEventCoordinatesRelativeTo(nsIWidget* aWidget, rootFrame = f; } - nsPoint widgetToRoot = [&] { - nsPresContext* pc = rootFrame->PresContext(); - nsPoint widgetOffset; - nsIWidget* widget = rootFrame->GetNearestWidget(widgetOffset); - if (NS_WARN_IF(!widget)) { - return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); - } - LayoutDeviceIntPoint widgetPoint = - aPoint + nsLayoutUtils::WidgetToWidgetOffset(aWidget, widget); - nsPoint widgetAppUnits(pc->DevPixelsToAppUnits(widgetPoint.x), - pc->DevPixelsToAppUnits(widgetPoint.y)); - return widgetAppUnits - widgetOffset; - }(); + nsView* rootView = rootFrame->GetView(); + if (!rootView) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } - if (widgetToRoot == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { + nsPoint widgetToView = nsLayoutUtils::TranslateWidgetToView( + rootFrame->PresContext(), aWidget, aPoint, rootView); + + if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); } @@ -1507,20 +1532,20 @@ nsPoint GetEventCoordinatesRelativeTo(nsIWidget* aWidget, // is in. int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel(); int32_t localAPD = frame->PresContext()->AppUnitsPerDevPixel(); - widgetToRoot = widgetToRoot.ScaleToOtherAppUnits(rootAPD, localAPD); + widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD); /* If we encountered a transform, we can't do simple arithmetic to figure * out how to convert back to aFrame's coordinates and must use the CTM. */ if (transformFound || frame->IsInSVGTextSubtree()) { return nsLayoutUtils::TransformRootPointToFrame(ViewportType::Visual, - aFrame, widgetToRoot); + aFrame, widgetToView); } /* Otherwise, all coordinate systems are translations of one another, * so we can just subtract out the difference. */ - return widgetToRoot - frame->GetOffsetToCrossDoc(rootFrame); + return widgetToView - frame->GetOffsetToCrossDoc(rootFrame); } nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo( @@ -2359,19 +2384,6 @@ nsRect nsLayoutUtils::TransformFrameRectToAncestor( result, aAncestor.mFrame->PresContext()->AppUnitsPerDevPixel()); } -Maybe<nsPoint> nsLayoutUtils::FrameToWidgetOffset(const nsIFrame* aFrame, - nsIWidget* aWidget) { - nsPoint toNearestOffset; - auto* nearest = aFrame->GetNearestWidget(toNearestOffset); - if (!nearest) { - return {}; - } - return Some(toNearestOffset + - LayoutDeviceIntPoint::ToAppUnits( - WidgetToWidgetOffset(nearest, aWidget), - aFrame->PresContext()->AppUnitsPerDevPixel())); -} - LayoutDeviceIntPoint nsLayoutUtils::WidgetToWidgetOffset(nsIWidget* aFrom, nsIWidget* aTo) { if (aFrom == aTo) { @@ -2382,6 +2394,23 @@ LayoutDeviceIntPoint nsLayoutUtils::WidgetToWidgetOffset(nsIWidget* aFrom, return fromOffset - toOffset; } +nsPoint nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext, + nsIWidget* aWidget, + const LayoutDeviceIntPoint& aPt, + nsView* aView) { + nsPoint viewOffset; + nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset); + if (!viewWidget) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } + + LayoutDeviceIntPoint widgetPoint = + aPt + WidgetToWidgetOffset(aWidget, viewWidget); + nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x), + aPresContext->DevPixelsToAppUnits(widgetPoint.y)); + return widgetAppUnits - viewOffset; +} + LayoutDeviceIntPoint nsLayoutUtils::TranslateViewToWidget( nsPresContext* aPresContext, nsView* aView, nsPoint aPt, ViewportType aViewportType, nsIWidget* aWidget) { diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h @@ -740,6 +740,19 @@ class nsLayoutUtils { int32_t* aOffset); /** + * Translate from widget coordinates to the view's coordinates + * @param aPresContext the PresContext for the view + * @param aWidget the widget + * @param aPt the point relative to the widget + * @param aView view to which returned coordinates are relative + * @return the point in the view's coordinates + */ + static nsPoint TranslateWidgetToView(nsPresContext* aPresContext, + nsIWidget* aWidget, + const mozilla::LayoutDeviceIntPoint& aPt, + nsView* aView); + + /** * Translate from view coordinates to the widget's coordinates. * @param aPresContext the PresContext for the view * @param aView the view @@ -755,9 +768,6 @@ class nsLayoutUtils { static mozilla::LayoutDeviceIntPoint WidgetToWidgetOffset( nsIWidget* aFromWidget, nsIWidget* aToWidget); - static mozilla::Maybe<nsPoint> FrameToWidgetOffset(const nsIFrame* aFrame, - nsIWidget* aWidget); - enum class FrameForPointOption { /** * When set, paint suppression is ignored, so we'll return non-root page diff --git a/layout/generic/FrameClasses.py b/layout/generic/FrameClasses.py @@ -117,7 +117,7 @@ FRAME_CLASSES = [ Frame("nsPageSequenceFrame", "PageSequence", COMMON), Frame("nsSliderFrame", "Slider", COMMON), Frame("nsSplitterFrame", "SimpleXULLeaf", COMMON | LEAF), - Frame("nsSubDocumentFrame", "SubDocument", REPLACED_SIZING | LEAF), + Frame("nsSubDocumentFrame", "SubDocument", REPLACED_SIZING | LEAF | MAY_HAVE_VIEW), Frame("PrintedSheetFrame", "PrintedSheet", COMMON), Frame("SVGAFrame", "SVGA", SVG_CONTAINER), Frame("SVGClipPathFrame", "SVGClipPath", SVG_RENDERING_OBSERVER_CONTAINER), diff --git a/layout/generic/crashtests/1999326.html b/layout/generic/crashtests/1999326.html @@ -1,11 +0,0 @@ -<style> -*::first-line {} -</style> -<script> -document.addEventListener("DOMContentLoaded", () => { - a.href = "x" - a.text = "A" -}) -</script> -<s lang="zh-CN"> -<a id="a" style="float: right"> diff --git a/layout/generic/crashtests/crashtests.list b/layout/generic/crashtests/crashtests.list @@ -825,4 +825,3 @@ load 1978475-1.html load 1990258.html load 1990319.html load 1998521.html -load 1999326.html diff --git a/layout/generic/nsSubDocumentFrame.cpp b/layout/generic/nsSubDocumentFrame.cpp @@ -30,7 +30,6 @@ #include "nsAttrValueInlines.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" -#include "nsDeviceContext.h" #include "nsDisplayList.h" #include "nsFrameSetFrame.h" #include "nsGenericHTMLElement.h" @@ -40,7 +39,6 @@ #include "nsIDocShell.h" #include "nsIDocumentViewer.h" #include "nsIObjectLoadingContent.h" -#include "nsIWeakReferenceUtils.h" #include "nsLayoutUtils.h" #include "nsNameSpaceManager.h" #include "nsObjectLoadingContent.h" @@ -50,12 +48,22 @@ #include "nsStyleConsts.h" #include "nsStyleStruct.h" #include "nsStyleStructInlines.h" +#include "nsView.h" +#include "nsViewManager.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::layers; +static Document* GetDocumentFromView(nsView* aView) { + MOZ_ASSERT(aView, "null view"); + + nsViewManager* vm = aView->GetViewManager(); + PresShell* presShell = vm ? vm->GetPresShell() : nullptr; + return presShell ? presShell->GetDocument() : nullptr; +} + static void PropagateIsUnderHiddenEmbedderElement(nsFrameLoader* aFrameLoader, bool aValue) { if (!aFrameLoader) { @@ -72,6 +80,8 @@ static void PropagateIsUnderHiddenEmbedderElement(nsFrameLoader* aFrameLoader, nsSubDocumentFrame::nsSubDocumentFrame(ComputedStyle* aStyle, nsPresContext* aPresContext) : nsAtomicContainerFrame(aStyle, aPresContext, kClassID), + mOuterView(nullptr), + mInnerView(nullptr), mIsInline(false), mPostedReflowCallback(false), mDidCreateDoc(false), @@ -104,34 +114,6 @@ class AsyncFrameInit : public Runnable { WeakFrame mFrame; }; -void nsSubDocumentFrame::EnsureEmbeddingPresShell(class PresShell* aPs) { - MOZ_ASSERT(aPs); - nsWeakPtr weakRef = do_GetWeakReference(aPs); - if (!mInProcessPresShells.Contains(weakRef)) { - aPs->SetInProcessEmbedderFrame(this); - mInProcessPresShells.AppendElement(std::move(weakRef)); - } -} - -void nsSubDocumentFrame::AddEmbeddingPresShell(class PresShell* aPs) { - MOZ_ASSERT(aPs); - nsWeakPtr weakRef = do_GetWeakReference(aPs); - MOZ_ASSERT(!mInProcessPresShells.Contains(weakRef)); - aPs->SetInProcessEmbedderFrame(this); - mInProcessPresShells.AppendElement(std::move(weakRef)); -} - -void nsSubDocumentFrame::RemoveEmbeddingPresShell(class PresShell* aPs) { - MOZ_ASSERT(aPs); - nsWeakPtr weakRef = do_GetWeakReference(aPs); - MOZ_ASSERT(mInProcessPresShells.Contains(weakRef)); - aPs->SetInProcessEmbedderFrame(nullptr); - if (mLastPaintedPresShell == weakRef) { - mLastPaintedPresShell = nullptr; - } - mInProcessPresShells.RemoveElement(weakRef); -} - void nsSubDocumentFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { MOZ_ASSERT(aContent); @@ -140,16 +122,29 @@ void nsSubDocumentFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsAtomicContainerFrame::Init(aContent, aParent, aPrevInFlow); + // CreateView() creates this frame's view, stored in mOuterView. It needs to + // be created first since it's the parent of the inner view, stored in + // mInnerView. + CreateView(); + EnsureInnerView(); + + // Set the primary frame now so that nsDocumentViewer::FindContainerView + // called from within EndSwapDocShellsForViews below can find it if needed. aContent->SetPrimaryFrame(this); // If we have a detached subdoc's root view on our frame loader, re-insert it // into the view tree. This happens when we've been reframed, and ensures the // presentation persists across reframes. if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) { - mInProcessPresShells = frameloader->TakeDetachedSubdocs(); - const bool anyLiveShell = FixUpInProcessPresShellsAfterAttach(); - if (!mInProcessPresShells.IsEmpty() && !anyLiveShell) { - mInProcessPresShells.Clear(); + bool hadFrame = false; + nsIFrame* detachedFrame = frameloader->GetDetachedSubdocFrame(&hadFrame); + frameloader->SetDetachedSubdocFrame(nullptr); + nsView* detachedView = detachedFrame ? detachedFrame->GetView() : nullptr; + if (detachedView) { + // Restore stashed presentation. + InsertViewsInReverseOrder(detachedView, mInnerView); + EndSwapDocShellsForViews(mInnerView->GetFirstChild()); + } else if (hadFrame) { // Presentation is for a different document, don't restore it. frameloader->Hide(); } @@ -194,7 +189,8 @@ void nsSubDocumentFrame::ShowViewer() { if (!frameloader->IsRemoteFrame() && !PresContext()->IsDynamic()) { // We let the printing code take care of loading the document and - // initializing the shell. + // initializing the shell; just create the inner view for it to use. + (void)EnsureInnerView(); } else { AutoWeakFrame weakThis(this); mCallingShow = true; @@ -215,36 +211,94 @@ void nsSubDocumentFrame::ShowViewer() { } } -Document* nsSubDocumentFrame::GetExtantSubdocument() { - nsIDocShell* ds = GetExtantDocShell(); - return ds ? ds->GetExtantDocument() : nullptr; -} +void nsSubDocumentFrame::CreateView() { + MOZ_ASSERT(!GetView()); -mozilla::PresShell* nsSubDocumentFrame::GetSubdocumentPresShell() { - Document* doc = GetExtantSubdocument(); - return doc ? doc->GetPresShell() : nullptr; + nsView* parentView = GetParent()->GetClosestView(); + MOZ_ASSERT(parentView, "no parent with view"); + + nsViewManager* viewManager = parentView->GetViewManager(); + MOZ_ASSERT(viewManager, "null view manager"); + + nsView* view = viewManager->CreateView(GetRect(), parentView); + SyncFrameViewProperties(view); + + nsView* insertBefore = nsLayoutUtils::FindSiblingViewFor(parentView, this); + // we insert this view 'above' the insertBefore view, unless insertBefore is + // null, in which case we want to call with aAbove == false to insert at the + // beginning in document order + viewManager->InsertChild(parentView, view, insertBefore, + insertBefore != nullptr); + + // Remember our view + SetView(view); + + NS_FRAME_LOG(NS_FRAME_TRACE_CALLS, + ("nsIFrame::CreateView: frame=%p view=%p", this, view)); } nsIFrame* nsSubDocumentFrame::GetSubdocumentRootFrame() { - mozilla::PresShell* ps = GetSubdocumentPresShell(); - return ps ? ps->GetRootFrame() : nullptr; + if (!mInnerView) { + return nullptr; + } + nsView* subdocView = mInnerView->GetFirstChild(); + return subdocView ? subdocView->GetFrame() : nullptr; } mozilla::PresShell* nsSubDocumentFrame::GetSubdocumentPresShellForPainting( uint32_t aFlags) { - mozilla::PresShell* presShell = GetSubdocumentPresShell(); - if (presShell && (!presShell->IsPaintingSuppressed() || - (aFlags & IGNORE_PAINT_SUPPRESSION))) { - return presShell; - } - // If painting is suppressed in the presshell or there's no presShell, we try - // to look for a better presshell to use. - if (StaticPrefs::layout_show_previous_page()) { - RefPtr<mozilla::PresShell> old = do_QueryReferent(mLastPaintedPresShell); - if (old && old->GetInProcessEmbedderFrame() == this) { - return old; + if (!mInnerView) { + return nullptr; + } + + nsView* subdocView = mInnerView->GetFirstChild(); + if (!subdocView) { + return nullptr; + } + + mozilla::PresShell* presShell = nullptr; + + nsIFrame* subdocRootFrame = subdocView->GetFrame(); + if (subdocRootFrame) { + presShell = subdocRootFrame->PresShell(); + } + + // If painting is suppressed in the presshell, we try to look for a better + // presshell to use. + if (!presShell || (presShell->IsPaintingSuppressed() && + !(aFlags & IGNORE_PAINT_SUPPRESSION))) { + // During page transition mInnerView will sometimes have two children, the + // first being the new page that may not have any frame, and the second + // being the old page that will probably have a frame. + nsView* nextView = subdocView->GetNextSibling(); + nsIFrame* frame = nullptr; + if (nextView) { + frame = nextView->GetFrame(); + } + if (frame) { + mozilla::PresShell* presShellForNextView = frame->PresShell(); + if (!presShell || (presShellForNextView && + !presShellForNextView->IsPaintingSuppressed() && + StaticPrefs::layout_show_previous_page())) { + subdocView = nextView; + subdocRootFrame = frame; + presShell = presShellForNextView; + } + } + if (!presShell) { + // If we don't have a frame we use this roundabout way to get the pres + // shell. + if (!mFrameLoader) { + return nullptr; + } + nsIDocShell* docShell = mFrameLoader->GetDocShell(IgnoreErrors()); + if (!docShell) { + return nullptr; + } + presShell = docShell->GetPresShell(); } } + return presShell; } @@ -265,14 +319,12 @@ nsRect nsSubDocumentFrame::GetDestRect(const nsRect& aConstraintRect) const { LayoutDeviceIntSize nsSubDocumentFrame::GetInitialSubdocumentSize() const { if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) { - for (const auto& detachedShell : frameloader->GetDetachedSubdocs()) { - if (RefPtr<mozilla::PresShell> ps = do_QueryReferent(detachedShell)) { - if (nsPresContext* pc = ps->GetPresContext()) { - return LayoutDeviceIntSize( - pc->AppUnitsToDevPixels(pc->GetVisibleArea().width), - pc->AppUnitsToDevPixels(pc->GetVisibleArea().height)); - } - } + nsIFrame* detachedFrame = frameloader->GetDetachedSubdocFrame(); + if (nsView* view = detachedFrame ? detachedFrame->GetView() : nullptr) { + nsSize size = view->GetBounds().Size(); + nsPresContext* presContext = detachedFrame->PresContext(); + return LayoutDeviceIntSize(presContext->AppUnitsToDevPixels(size.width), + presContext->AppUnitsToDevPixels(size.height)); } } // Pick some default size for now. Using 10x10 because that's what the @@ -347,7 +399,7 @@ void nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, // If we're passing pointer events to children then we have to descend into // subdocuments no matter what, to determine which parts are transparent for // hit-testing or event regions. - if (!aBuilder->GetDescendIntoSubdocuments()) { + if (!mInnerView || !aBuilder->GetDescendIntoSubdocuments()) { return; } @@ -368,10 +420,6 @@ void nsSubDocumentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, return; } - if (aBuilder->IsForPainting() && !aBuilder->IsIgnoringPaintSuppression()) { - mLastPaintedPresShell = do_GetWeakReference(presShell); - } - if (aBuilder->IsInFilter()) { Document* outerDoc = PresShell()->GetDocument(); Document* innerDoc = presShell->GetDocument(); @@ -653,18 +701,16 @@ void nsSubDocumentFrame::Reflow(nsPresContext* aPresContext, nsPoint offset = nsPoint(aReflowInput.ComputedPhysicalBorderPadding().left, aReflowInput.ComputedPhysicalBorderPadding().top); - if (nsCOMPtr<nsIDocShell> ds = GetExtantDocShell()) { + if (mInnerView) { const nsMargin& bp = aReflowInput.ComputedPhysicalBorderPadding(); nsSize innerSize(aDesiredSize.Width() - bp.LeftRight(), aDesiredSize.Height() - bp.TopBottom()); // Size & position the view according to 'object-fit' & 'object-position'. - const nsRect destRect = GetDestRect(nsRect(offset, innerSize)); - auto rect = LayoutDeviceIntRect::FromAppUnitsToInside( - destRect, PresContext()->AppUnitsPerDevPixel()); - mExtraOffset = destRect.TopLeft(); - nsDocShell::Cast(ds)->SetPositionAndSize(0, 0, rect.width, rect.height, - nsIBaseWindow::eDelayResize); + nsRect destRect = GetDestRect(nsRect(offset, innerSize)); + nsViewManager* vm = mInnerView->GetViewManager(); + vm->MoveViewTo(mInnerView, destRect.x, destRect.y); + vm->ResizeView(mInnerView, nsRect(nsPoint(0, 0), destRect.Size())); } aDesiredSize.SetOverflowAreasToDesiredBounds(); @@ -897,7 +943,7 @@ class nsHideViewer final : public Runnable { // Either the frame has been constructed by now, or it never will be, // either way we want to clear the stashed views. - mFrameLoader->SetDetachedSubdocs({}); + mFrameLoader->SetDetachedSubdocFrame(nullptr); nsSubDocumentFrame* frame = do_QueryFrame(mFrameElement->GetPrimaryFrame()); if (!frame || frame->FrameLoader() != mFrameLoader) { @@ -918,6 +964,8 @@ class nsHideViewer final : public Runnable { const bool mHideViewerIfFrameless; }; +static nsView* BeginSwapDocShellsForViews(nsView* aSibling); + void nsSubDocumentFrame::Destroy(DestroyContext& aContext) { if (mPostedReflowCallback) { PresShell()->CancelReflowCallback(this); @@ -930,8 +978,11 @@ void nsSubDocumentFrame::Destroy(DestroyContext& aContext) { if (RefPtr<nsFrameLoader> frameloader = FrameLoader()) { ClearDisplayItems(); - PrepareInProcessPresShellsForDetach(); - frameloader->SetDetachedSubdocs(std::move(mInProcessPresShells)); + nsView* detachedViews = + ::BeginSwapDocShellsForViews(mInnerView->GetFirstChild()); + + frameloader->SetDetachedSubdocFrame( + detachedViews ? detachedViews->GetFrame() : nullptr); // We call nsFrameLoader::HideViewer() in a script runner so that we can // safely determine whether the frame is being reframed or destroyed. @@ -1001,10 +1052,6 @@ nsIDocShell* nsSubDocumentFrame::GetDocShell() const { return mFrameLoader->GetDocShell(IgnoreErrors()); } -nsIDocShell* nsSubDocumentFrame::GetExtantDocShell() const { - return mFrameLoader ? mFrameLoader->GetExistingDocShell() : nullptr; -} - static void DestroyDisplayItemDataForFrames(nsIFrame* aFrame) { // Destroying a WebRenderUserDataTable can cause destruction of other objects // which can remove frame properties in their destructor. If we delete a frame @@ -1029,6 +1076,52 @@ static void DestroyDisplayItemDataForFrames(nsIFrame* aFrame) { } } +static CallState BeginSwapDocShellsForDocument(Document& aDocument) { + if (PresShell* presShell = aDocument.GetPresShell()) { + // Disable painting while the views are detached, see bug 946929. + presShell->SetNeverPainting(true); + + if (nsIFrame* rootFrame = presShell->GetRootFrame()) { + ::DestroyDisplayItemDataForFrames(rootFrame); + } + } + aDocument.EnumerateSubDocuments(BeginSwapDocShellsForDocument); + return CallState::Continue; +} + +static nsView* BeginSwapDocShellsForViews(nsView* aSibling) { + // Collect the removed sibling views in reverse order in 'removedViews'. + nsView* removedViews = nullptr; + while (aSibling) { + if (Document* doc = ::GetDocumentFromView(aSibling)) { + ::BeginSwapDocShellsForDocument(*doc); + } + nsView* next = aSibling->GetNextSibling(); + aSibling->GetViewManager()->RemoveChild(aSibling); + aSibling->SetNextSibling(removedViews); + removedViews = aSibling; + aSibling = next; + } + return removedViews; +} + +/* static */ +void nsSubDocumentFrame::InsertViewsInReverseOrder(nsView* aSibling, + nsView* aParent) { + MOZ_ASSERT(aParent, "null view"); + MOZ_ASSERT(!aParent->GetFirstChild(), "inserting into non-empty list"); + + nsViewManager* vm = aParent->GetViewManager(); + while (aSibling) { + nsView* next = aSibling->GetNextSibling(); + aSibling->SetNextSibling(nullptr); + // true means 'after' in document order which is 'before' in view order, + // so this call prepends the child, thus reversing the siblings as we go. + vm->InsertChild(aParent, aSibling, nullptr, true); + aSibling = next; + } +} + nsresult nsSubDocumentFrame::BeginSwapDocShells(nsIFrame* aOther) { if (!aOther || !aOther->IsSubDocumentFrame()) { return NS_ERROR_NOT_IMPLEMENTED; @@ -1043,17 +1136,23 @@ nsresult nsSubDocumentFrame::BeginSwapDocShells(nsIFrame* aOther) { ClearDisplayItems(); other->ClearDisplayItems(); - PrepareInProcessPresShellsForDetach(); - other->PrepareInProcessPresShellsForDetach(); + if (mInnerView && other->mInnerView) { + nsView* ourSubdocViews = mInnerView->GetFirstChild(); + nsView* ourRemovedViews = ::BeginSwapDocShellsForViews(ourSubdocViews); + nsView* otherSubdocViews = other->mInnerView->GetFirstChild(); + nsView* otherRemovedViews = ::BeginSwapDocShellsForViews(otherSubdocViews); + InsertViewsInReverseOrder(ourRemovedViews, other->mInnerView); + InsertViewsInReverseOrder(otherRemovedViews, mInnerView); + } mFrameLoader.swap(other->mFrameLoader); return NS_OK; } static CallState EndSwapDocShellsForDocument(Document& aDocument) { - // Our docshell trees have been updated for the new hierarchy. Now also update - // all nsDeviceContext::mWidget to that of the container view in the new - // hierarchy. + // Our docshell and view trees have been updated for the new hierarchy. + // Now also update all nsDeviceContext::mWidget to that of the + // container view in the new hierarchy. if (nsCOMPtr<nsIDocShell> ds = aDocument.GetDocShell()) { nsCOMPtr<nsIDocumentViewer> viewer; ds->GetDocViewer(getter_AddRefs(viewer)); @@ -1064,12 +1163,8 @@ static CallState EndSwapDocShellsForDocument(Document& aDocument) { } nsDeviceContext* dc = pc ? pc->DeviceContext() : nullptr; if (dc) { - nsSubDocumentFrame* f = viewer->FindContainerFrame(); - nsIWidget* widget = f ? f->GetNearestWidget() : nullptr; - if (widget) { - widget = widget->GetTopLevelWidget(); - } - dc->Init(widget); + nsView* v = viewer->FindContainerView(); + dc->Init(v ? v->GetNearestWidget(nullptr) : nullptr); } viewer = viewer->GetPreviousViewer(); } @@ -1079,65 +1174,64 @@ static CallState EndSwapDocShellsForDocument(Document& aDocument) { return CallState::Continue; } -static CallState BeginSwapDocShellsForDocument(Document& aDocument) { - if (PresShell* presShell = aDocument.GetPresShell()) { - // Disable painting while the presentation shell is detached. - presShell->SetNeverPainting(true); - - if (nsIFrame* rootFrame = presShell->GetRootFrame()) { - ::DestroyDisplayItemDataForFrames(rootFrame); - } - } - aDocument.EnumerateSubDocuments(BeginSwapDocShellsForDocument); - return CallState::Continue; -} - -void nsSubDocumentFrame::PrepareInProcessPresShellsForDetach() { - for (const auto& shell : mInProcessPresShells) { - if (RefPtr<class PresShell> ps = do_QueryReferent(shell)) { - BeginSwapDocShellsForDocument(*ps->GetDocument()); +/* static */ +void nsSubDocumentFrame::EndSwapDocShellsForViews(nsView* aSibling) { + for (; aSibling; aSibling = aSibling->GetNextSibling()) { + if (Document* doc = ::GetDocumentFromView(aSibling)) { + ::EndSwapDocShellsForDocument(*doc); } - } -} - -bool nsSubDocumentFrame::FixUpInProcessPresShellsAfterAttach() { - bool anyLiveShell = false; - for (auto& shell : mInProcessPresShells) { - if (RefPtr<mozilla::PresShell> ps = do_QueryReferent(shell)) { - if (ps && !ps->IsDestroying()) { - anyLiveShell = true; - ps->SetInProcessEmbedderFrame(this); - EndSwapDocShellsForDocument(*ps->GetDocument()); + nsIFrame* frame = aSibling->GetFrame(); + if (frame) { + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame); + if (parent->HasAnyStateBits(NS_FRAME_IN_POPUP)) { + nsIFrame::AddInPopupStateBitToDescendants(frame); + } else { + nsIFrame::RemoveInPopupStateBitFromDescendants(frame); + } + if (frame->HasInvalidFrameInSubtree()) { + while (parent && + !parent->HasAnyStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT | + NS_FRAME_IS_NONDISPLAY)) { + parent->AddStateBits(NS_FRAME_DESCENDANT_NEEDS_PAINT); + parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(parent); + } } } } - return anyLiveShell; } void nsSubDocumentFrame::EndSwapDocShells(nsIFrame* aOther) { - auto* other = static_cast<nsSubDocumentFrame*>(aOther); + nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther); + AutoWeakFrame weakThis(this); + AutoWeakFrame weakOther(aOther); - mInProcessPresShells.SwapElements(other->mInProcessPresShells); - FixUpInProcessPresShellsAfterAttach(); - other->FixUpInProcessPresShellsAfterAttach(); + if (mInnerView) { + EndSwapDocShellsForViews(mInnerView->GetFirstChild()); + } + if (other->mInnerView) { + EndSwapDocShellsForViews(other->mInnerView->GetFirstChild()); + } // Now make sure we reflow both frames, in case their contents // determine their size. // And repaint them, for good measure, in case there's nothing // interesting that happens during reflow. - PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, - NS_FRAME_IS_DIRTY); - InvalidateFrameSubtree(); - PropagateIsUnderHiddenEmbedderElement( - PresShell()->IsUnderHiddenEmbedderElement() || - !StyleVisibility()->IsVisible()); - - other->PresShell()->FrameNeedsReflow(other, IntrinsicDirty::FrameAndAncestors, - NS_FRAME_IS_DIRTY); - other->InvalidateFrameSubtree(); - other->PropagateIsUnderHiddenEmbedderElement( - other->PresShell()->IsUnderHiddenEmbedderElement() || - !other->StyleVisibility()->IsVisible()); + if (weakThis.IsAlive()) { + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors, + NS_FRAME_IS_DIRTY); + InvalidateFrameSubtree(); + PropagateIsUnderHiddenEmbedderElement( + PresShell()->IsUnderHiddenEmbedderElement() || + !StyleVisibility()->IsVisible()); + } + if (weakOther.IsAlive()) { + other->PresShell()->FrameNeedsReflow( + other, IntrinsicDirty::FrameAndAncestors, NS_FRAME_IS_DIRTY); + other->InvalidateFrameSubtree(); + other->PropagateIsUnderHiddenEmbedderElement( + other->PresShell()->IsUnderHiddenEmbedderElement() || + !other->StyleVisibility()->IsVisible()); + } } void nsSubDocumentFrame::ClearDisplayItems() { @@ -1147,6 +1241,33 @@ void nsSubDocumentFrame::ClearDisplayItems() { } } +nsView* nsSubDocumentFrame::EnsureInnerView() { + if (mInnerView) { + return mInnerView; + } + + // create, init, set the parent of the view + nsView* outerView = GetView(); + NS_ASSERTION(outerView, "Must have an outer view already"); + nsRect viewBounds(0, 0, 0, 0); // size will be fixed during reflow + + nsViewManager* viewMan = outerView->GetViewManager(); + nsView* innerView = viewMan->CreateView(viewBounds, outerView); + if (!innerView) { + NS_ERROR("Could not create inner view"); + return nullptr; + } + mInnerView = innerView; + viewMan->InsertChild(outerView, innerView, nullptr, true); + + return mInnerView; +} + +nsPoint nsSubDocumentFrame::GetExtraOffset() const { + MOZ_ASSERT(mInnerView); + return mInnerView->GetPosition(); +} + void nsSubDocumentFrame::SubdocumentIntrinsicSizeOrRatioChanged() { const nsStylePosition* pos = StylePosition(); const auto anchorResolutionParams = AnchorPosResolutionParams::From(this); diff --git a/layout/generic/nsSubDocumentFrame.h b/layout/generic/nsSubDocumentFrame.h @@ -55,8 +55,6 @@ class nsSubDocumentFrame final : public nsAtomicContainerFrame, mozilla::IntrinsicSize GetIntrinsicSize() override; mozilla::AspectRatio GetIntrinsicRatio() const override; - const nsPoint& GetExtraOffset() const { return mExtraOffset; } - SizeComputationResult ComputeSize( gfxContext* aRenderingContext, mozilla::WritingMode aWM, const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, @@ -91,12 +89,14 @@ class nsSubDocumentFrame final : public nsAtomicContainerFrame, bool aIgnoreContainment = false) const; nsIDocShell* GetDocShell() const; - nsIDocShell* GetExtantDocShell() const; nsresult BeginSwapDocShells(nsIFrame* aOther); void EndSwapDocShells(nsIFrame* aOther); - mozilla::dom::Document* GetExtantSubdocument(); - mozilla::PresShell* GetSubdocumentPresShell(); + static void InsertViewsInReverseOrder(nsView* aSibling, nsView* aParent); + static void EndSwapDocShellsForViews(nsView* aView); + + nsView* EnsureInnerView(); + nsPoint GetExtraOffset() const; nsIFrame* GetSubdocumentRootFrame(); enum { IGNORE_PAINT_SUPPRESSION = 0x1 }; mozilla::PresShell* GetSubdocumentPresShellForPainting(uint32_t aFlags); @@ -151,10 +151,6 @@ class nsSubDocumentFrame final : public nsAtomicContainerFrame, const Maybe<nsRect>& GetVisibleRect() const { return mVisibleRect; } void SetVisibleRect(const Maybe<nsRect>& aRect) { mVisibleRect = aRect; } - void AddEmbeddingPresShell(mozilla::PresShell*); - void EnsureEmbeddingPresShell(mozilla::PresShell*); - void RemoveEmbeddingPresShell(mozilla::PresShell*); - protected: friend class AsyncFrameInit; @@ -164,11 +160,6 @@ class nsSubDocumentFrame final : public nsAtomicContainerFrame, void PropagateIsUnderHiddenEmbedderElement(bool aValue); void UpdateEmbeddedBrowsingContextDependentData(); - // Makes sure that all the live pres shells are pointing to `this`. Returns - // true if there's any live shells. - bool FixUpInProcessPresShellsAfterAttach(); - void PrepareInProcessPresShellsForDetach(); - bool IsInline() const { return mIsInline; } // Show our document viewer. The document viewer is hidden via a script @@ -176,27 +167,24 @@ class nsSubDocumentFrame final : public nsAtomicContainerFrame, // being reframed. void ShowViewer(); + nsView* GetViewInternal() const override { return mOuterView; } + void SetViewInternal(nsView* aView) override { mOuterView = aView; } + void CreateView(); + mutable RefPtr<nsFrameLoader> mFrameLoader; - // The in-process pres shells that we're currently embedding. May be multiple - // because of in-process BFCache. - // TODO(emilio): Maybe that's not relevant anymore? We definitely hit that - // code-path, but maybe it could be simplified nowadays? - AutoTArray<nsWeakPtr, 1> mInProcessPresShells; + nsView* mOuterView; + nsView* mInnerView; + // When process-switching a remote tab, we might temporarily paint the old // one. Maybe<RemoteFramePaintData> mRetainedRemoteFrame; - nsWeakPtr mLastPaintedPresShell; // The raster scale from our last paint. mozilla::gfx::MatrixScales mRasterScale; // The visible rect from our last paint. Maybe<nsRect> mVisibleRect; - // The extra offset from our padding box to the child, needed to deal with - // object-fit and co. - nsPoint mExtraOffset; - bool mIsInline : 1; bool mPostedReflowCallback : 1; bool mDidCreateDoc : 1; diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp @@ -3563,16 +3563,20 @@ static int32_t GetFrameLineNum(nsIFrame* aFrame, nsILineIterator* aLineIter) { if (!aLineIter) { return -1; } - // If we don't find the frame directly, but its parent is an inline or other - // "line participant" (e.g. nsFirstLineFrame), we want the line that the - // inline ancestor is on. - do { - int32_t n = aLineIter->FindLineContaining(aFrame); + int32_t n = aLineIter->FindLineContaining(aFrame); + if (n >= 0) { + return n; + } + // If we didn't find the frame directly, but its parent is an inline, + // we want the line that the inline ancestor is on. + nsIFrame* ancestor = aFrame->GetParent(); + while (ancestor && ancestor->IsInlineFrame()) { + n = aLineIter->FindLineContaining(ancestor); if (n >= 0) { return n; } - aFrame = aFrame->GetParent(); - } while (aFrame && aFrame->IsLineParticipant()); + ancestor = ancestor->GetParent(); + } return -1; } diff --git a/layout/mathml/nsMathMLOperators.cpp b/layout/mathml/nsMathMLOperators.cpp @@ -6,7 +6,6 @@ #include "nsMathMLOperators.h" -#include "mozilla/StaticPrefs_mathml.h" #include "mozilla/intl/UnicodeProperties.h" #include "nsCOMPtr.h" #include "nsCRT.h" @@ -17,8 +16,6 @@ #include "nsTArray.h" #include "nsTHashMap.h" -using namespace mozilla; - // operator dictionary entry struct OperatorData { OperatorData(void) : mFlags(0), mLeadingSpace(0.0f), mTrailingSpace(0.0f) {} @@ -59,8 +56,7 @@ static void SetBooleanProperty(OperatorData* aOperatorData, nsString aName) { aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY; } else if (aName.EqualsLiteral("fence")) { aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE; - } else if (!StaticPrefs::mathml_operator_dictionary_accent_disabled() && - aName.EqualsLiteral("accent")) { + } else if (aName.EqualsLiteral("accent")) { aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT; } else if (aName.EqualsLiteral("largeop")) { aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP; @@ -431,7 +427,7 @@ bool nsMathMLOperators::LookupOperatorWithFallback(const nsString& aOperator, /* static */ bool nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) { if (auto codePoint = ToUnicodeCodePoint(aOperator)) { - return intl::UnicodeProperties::IsMirrored(codePoint); + return mozilla::intl::UnicodeProperties::IsMirrored(codePoint); } return false; } @@ -440,7 +436,7 @@ bool nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) { nsString nsMathMLOperators::GetMirroredOperator(const nsString& aOperator) { nsString result; if (auto codePoint = ToUnicodeCodePoint(aOperator)) { - result.Assign(intl::UnicodeProperties::CharMirror(codePoint)); + result.Assign(mozilla::intl::UnicodeProperties::CharMirror(codePoint)); } return result; } diff --git a/layout/mathml/nsMathMLmoFrame.cpp b/layout/mathml/nsMathMLmoFrame.cpp @@ -11,12 +11,10 @@ #include "gfxContext.h" #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs_mathml.h" -#include "mozilla/dom/Document.h" #include "mozilla/dom/MathMLElement.h" #include "nsCSSValue.h" #include "nsContentUtils.h" #include "nsFrameSelection.h" -#include "nsGkAtoms.h" #include "nsLayoutUtils.h" #include "nsPresContext.h" @@ -227,31 +225,11 @@ void nsMathMLmoFrame::ProcessOperatorData() { } // see if the accent attribute is there - if (mContent->AsElement()->GetAttr(nsGkAtoms::accent, value)) { - [&]() { - AutoTArray<nsString, 2> params; - auto parentName = GetParent()->GetContent()->NodeInfo()->NameAtom(); - if (parentName == nsGkAtoms::mover) { - params.AppendElement(u"accent"); - params.AppendElement(u"mover"); - } else if (parentName == nsGkAtoms::munder) { - params.AppendElement(u"accentunder"); - params.AppendElement(u"munder"); - } else if (parentName == nsGkAtoms::munderover) { - params.AppendElement(u"accent/accentunder"); - params.AppendElement(u"munderover"); - } else { - return; - } - PresContext()->Document()->WarnOnceAbout( - dom::DeprecatedOperations::eMathML_DeprecatedMoExplicitAccent, - false, params); - }(); - if (value.LowerCaseEqualsLiteral("true")) { - mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENT; - } else if (value.LowerCaseEqualsLiteral("false")) { - mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENT; - } + mContent->AsElement()->GetAttr(nsGkAtoms::accent, value); + if (value.LowerCaseEqualsLiteral("true")) { + mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENT; + } else if (value.LowerCaseEqualsLiteral("false")) { + mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENT; } // see if the movablelimits attribute is there diff --git a/layout/mathml/nsMathMLmunderoverFrame.cpp b/layout/mathml/nsMathMLmunderoverFrame.cpp @@ -15,7 +15,6 @@ #include "mozilla/StaticPrefs_mathml.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/MathMLElement.h" -#include "nsIMathMLFrame.h" #include "nsLayoutUtils.h" #include "nsMathMLmmultiscriptsFrame.h" #include "nsPresContext.h" @@ -232,13 +231,6 @@ XXX The winner is the outermost setting in conflicting settings like these: } else if (value.LowerCaseEqualsLiteral("false")) { mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTUNDER; } - } else if (NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) { - AutoTArray<nsString, 1> params; - params.AppendElement(mContent->NodeInfo()->NodeName()); - PresContext()->Document()->WarnOnceAbout( - dom::DeprecatedOperations:: - eMathML_DeprecatedMunderNonExplicitAccentunder, - false, params); } } @@ -260,12 +252,6 @@ XXX The winner is the outermost setting in conflicting settings like these: } else if (value.LowerCaseEqualsLiteral("false")) { mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTOVER; } - } else if (NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)) { - AutoTArray<nsString, 1> params; - params.AppendElement(mContent->NodeInfo()->NodeName()); - PresContext()->Document()->WarnOnceAbout( - dom::DeprecatedOperations::eMathML_DeprecatedMoverNonExplicitAccent, - false, params); } } diff --git a/layout/printing/nsPrintJob.cpp b/layout/printing/nsPrintJob.cpp @@ -40,7 +40,6 @@ #include "nsPrintObject.h" #include "nsQueryObject.h" #include "nsReadableUtils.h" -#include "nsSubDocumentFrame.h" #include "nsView.h" // Print Options @@ -720,9 +719,11 @@ nsresult nsPrintJob::ReconstructAndReflow() { bool documentIsTopLevel = true; if (po->mParent) { nsSize adjSize; - bool doReturn = false; - documentIsTopLevel = false; - nsresult rv = SetRootView(po, documentIsTopLevel, doReturn, adjSize); + bool doReturn; + nsresult rv = SetRootView(po, doReturn, documentIsTopLevel, adjSize); + + MOZ_ASSERT(!documentIsTopLevel, "How could this happen?"); + if (NS_FAILED(rv) || doReturn) { return rv; } @@ -1180,12 +1181,26 @@ nsresult nsPrintJob::UpdateSelectionAndShrinkPrintObject( return NS_OK; } -nsresult nsPrintJob::SetRootView(nsPrintObject* aPO, bool aDocumentIsTopLevel, - bool& doReturn, nsSize& adjSize) { +nsView* nsPrintJob::GetParentViewForRoot() { + if (mIsCreatingPrintPreview) { + if (nsCOMPtr<nsIDocumentViewer> viewer = + do_QueryInterface(mDocViewerPrint)) { + return viewer->FindContainerView(); + } + } + return nullptr; +} + +nsresult nsPrintJob::SetRootView(nsPrintObject* aPO, bool& doReturn, + bool& documentIsTopLevel, nsSize& adjSize) { bool canCreateScrollbars = true; nsView* rootView; - if (!aDocumentIsTopLevel) { + nsView* parentView = nullptr; + + doReturn = false; + + if (aPO->mParent && aPO->mParent->PrintingIsEnabled()) { nsIFrame* frame = aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr; // Without a frame, this document can't be displayed; therefore, there is no @@ -1200,28 +1215,38 @@ nsresult nsPrintJob::SetRootView(nsPrintObject* aPO, bool aDocumentIsTopLevel, // zoom this would be wrong as we use the same mPrt->mPrintDC for all // subdocuments. adjSize = frame->GetContentRect().Size(); + documentIsTopLevel = false; // presshell exists because parent is printable // the top nsPrintObject's widget will always have scrollbars if (frame && frame->IsSubDocumentFrame()) { + nsView* view = frame->GetView(); + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + view = view->GetFirstChild(); + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + parentView = view; canCreateScrollbars = false; } } else { adjSize = mPrt->mPrintDC->GetDeviceSurfaceDimensions(); + documentIsTopLevel = true; + parentView = GetParentViewForRoot(); } - if ((rootView = aPO->mViewManager->GetRootView())) { + if (aPO->mViewManager->GetRootView()) { + // Reuse the root view that is already on the root frame. + rootView = aPO->mViewManager->GetRootView(); // Remove it from its existing parent if necessary aPO->mViewManager->RemoveChild(rootView); - rootView->SetParent(nullptr); + rootView->SetParent(parentView); } else { // Create a child window of the parent that is our "root view/window" - nsRect tbounds = nsRect(nsPoint(), adjSize); - rootView = aPO->mViewManager->CreateView(tbounds, nullptr); + nsRect tbounds = nsRect(nsPoint(0, 0), adjSize); + rootView = aPO->mViewManager->CreateView(tbounds, parentView); NS_ENSURE_TRUE(rootView, NS_ERROR_OUT_OF_MEMORY); } - if (mIsCreatingPrintPreview && aDocumentIsTopLevel) { + if (mIsCreatingPrintPreview && documentIsTopLevel) { aPO->mPresContext->SetPaginatedScrolling(canCreateScrollbars); } @@ -1250,21 +1275,9 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) { nsPresContext::nsPresContextType type = mIsCreatingPrintPreview ? nsPresContext::eContext_PrintPreview : nsPresContext::eContext_Print; - const bool documentIsTopLevel = - !aPO->mParent || !aPO->mParent->PrintingIsEnabled(); - auto* embedderFrame = [&]() -> nsSubDocumentFrame* { - if (documentIsTopLevel) { - if (nsCOMPtr<nsIDocumentViewer> viewer = - do_QueryInterface(mDocViewerPrint)) { - return viewer->FindContainerFrame(); - } - } else if (aPO->mContent) { - return do_QueryFrame(aPO->mContent->GetPrimaryFrame()); - } - return nullptr; - }(); - - const bool shouldBeRoot = documentIsTopLevel && !embedderFrame; + const bool shouldBeRoot = + (!aPO->mParent || !aPO->mParent->PrintingIsEnabled()) && + !GetParentViewForRoot(); aPO->mPresContext = shouldBeRoot ? new nsRootPresContext(aPO->mDocument, type) : new nsPresContext(aPO->mDocument, type); aPO->mPresContext->SetPrintSettings(mPrintSettings); @@ -1275,8 +1288,11 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) { aPO->mViewManager = new nsViewManager(printData->mPrintDC); bool doReturn = false; + bool documentIsTopLevel = false; nsSize adjSize; - nsresult rv = SetRootView(aPO.get(), documentIsTopLevel, doReturn, adjSize); + + nsresult rv = SetRootView(aPO.get(), doReturn, documentIsTopLevel, adjSize); + if (NS_FAILED(rv) || doReturn) { return rv; } @@ -1322,8 +1338,7 @@ nsresult nsPrintJob::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO) { RefPtr<nsPresContext> presContext = aPO->mPresContext; RefPtr<nsViewManager> viewManager = aPO->mViewManager; - aPO->mPresShell = - doc->CreatePresShell(presContext, viewManager, embedderFrame); + aPO->mPresShell = doc->CreatePresShell(presContext, viewManager); if (!aPO->mPresShell) { return NS_ERROR_FAILURE; } diff --git a/layout/printing/nsPrintJob.h b/layout/printing/nsPrintJob.h @@ -24,7 +24,6 @@ // Classes class nsIFrame; -class nsSubDocumentFrame; class nsIPrintSettings; class nsPrintData; class nsPagePrintTimer; @@ -35,6 +34,7 @@ class nsPrintObject; class nsIDocShell; class nsPageSequenceFrame; class nsPIDOMWindowOuter; +class nsView; namespace mozilla { class PresShell; @@ -223,8 +223,9 @@ class nsPrintJob final : public nsIWebProgressListener, bool ShouldResumePrint() const; - nsresult SetRootView(nsPrintObject* aPO, bool aDocumentIsTopLevel, - bool& aDoReturn, nsSize& aAdjSize); + nsresult SetRootView(nsPrintObject* aPO, bool& aDoReturn, + bool& aDocumentIsTopLevel, nsSize& aAdjSize); + nsView* GetParentViewForRoot(); void UpdateZoomRatio(nsPrintObject* aPO); MOZ_CAN_RUN_SCRIPT nsresult ReconstructAndReflow(); MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult UpdateSelectionAndShrinkPrintObject( diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp @@ -341,7 +341,8 @@ void nsMenuPopupFrame::CreateWidget() { LayoutDeviceIntRect nsMenuPopupFrame::CalcWidgetBounds() const { return nsView::CalcWidgetBounds( GetRect(), PresContext()->AppUnitsPerDevPixel(), - PresShell()->GetRootFrame(), nullptr, widget::WindowType::Popup, + PresShell()->GetViewManager()->GetRootView(), nullptr, + widget::WindowType::Popup, nsLayoutUtils::GetFrameTransparency(this, this)); } diff --git a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragment.kt b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragment.kt @@ -3,35 +3,33 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package mozilla.components.feature.prompts.dialog +import android.annotation.SuppressLint +import android.app.AlertDialog import android.app.DatePickerDialog import android.app.Dialog import android.app.TimePickerDialog +import android.content.Context import android.content.DialogInterface import android.content.DialogInterface.BUTTON_NEGATIVE import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.DialogInterface.BUTTON_POSITIVE -import android.icu.util.TimeZone import android.os.Bundle import android.text.format.DateFormat +import android.view.LayoutInflater import android.view.View import android.widget.DatePicker import android.widget.TimePicker import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.Companion.PRIVATE -import androidx.appcompat.app.AlertDialog -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.DateValidatorPointBackward -import com.google.android.material.datepicker.DateValidatorPointForward -import com.google.android.material.datepicker.MaterialDatePicker -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.timepicker.MaterialTimePicker -import com.google.android.material.timepicker.TimeFormat import mozilla.components.feature.prompts.R +import mozilla.components.feature.prompts.ext.day import mozilla.components.feature.prompts.ext.hour import mozilla.components.feature.prompts.ext.millisecond import mozilla.components.feature.prompts.ext.minute +import mozilla.components.feature.prompts.ext.month import mozilla.components.feature.prompts.ext.second import mozilla.components.feature.prompts.ext.toCalendar +import mozilla.components.feature.prompts.ext.year import mozilla.components.feature.prompts.widget.MonthAndYearPicker import mozilla.components.feature.prompts.widget.TimePrecisionPicker import mozilla.components.support.utils.TimePicker.shouldShowSecondsPicker @@ -79,29 +77,55 @@ internal class TimePickerDialogFragment : } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return when (selectionType) { - SELECTION_TYPE_DATE, SELECTION_TYPE_DATE_AND_TIME -> { - createMaterialDatePickerDialog(isDateTimePicker = selectionType == SELECTION_TYPE_DATE_AND_TIME) - Dialog(requireContext()) + val context = requireContext() + val dialog = when (selectionType) { + SELECTION_TYPE_TIME -> createTimePickerDialog(context) + SELECTION_TYPE_DATE -> initialDate.toCalendar().let { cal -> + DatePickerDialog( + context, + this@TimePickerDialogFragment, + cal.year, + cal.month, + cal.day, + ).apply { setMinMaxDate(datePicker) } } - - SELECTION_TYPE_TIME -> { - val step = stepSize?.toFloat() - if (shouldShowSecondsPicker(step) && step != null) { - createTimeStepPickerDialog(step) - } else { - createMaterialTimePickerDialog(selectedDate.time) - Dialog(requireContext()) + SELECTION_TYPE_DATE_AND_TIME -> AlertDialog.Builder(context) + .setView(inflateDateTimePicker(LayoutInflater.from(context))) + .create() + .also { + it.setButton(BUTTON_POSITIVE, context.getString(R.string.mozac_feature_prompts_set_date), this) + it.setButton(BUTTON_NEGATIVE, context.getString(R.string.mozac_feature_prompts_cancel), this) } - } + SELECTION_TYPE_MONTH -> AlertDialog.Builder(context) + .setTitle(R.string.mozac_feature_prompts_set_month) + .setView(inflateDateMonthPicker()) + .create() + .also { + it.setButton(BUTTON_POSITIVE, context.getString(R.string.mozac_feature_prompts_set_date), this) + it.setButton(BUTTON_NEGATIVE, context.getString(R.string.mozac_feature_prompts_cancel), this) + } + else -> throw IllegalArgumentException() + } - SELECTION_TYPE_MONTH -> createMonthPickerDialog() - else -> throw IllegalArgumentException("Invalid selection type: $selectionType") + dialog.also { + it.setCancelable(true) + it.setButton(BUTTON_NEUTRAL, context.getString(R.string.mozac_feature_prompts_clear), this) } + + return dialog + } + + /** + * Called when the user touches outside of the dialog. + */ + override fun onCancel(dialog: DialogInterface) { + super.onCancel(dialog) + onClick(dialog, BUTTON_NEGATIVE) } override fun onStart() { super.onStart() + val alertDialog = dialog if (alertDialog is AlertDialog) { // We want to call the extension function after the show() call on the dialog, @@ -110,103 +134,74 @@ internal class TimePickerDialogFragment : } } - private fun createMaterialTimePickerDialog(dateSelection: Long? = null) { - val calendar = initialDate.toCalendar() - val is24Hour = DateFormat.is24HourFormat(requireContext()) - val timeFormat = if (is24Hour) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H - - val timePicker = MaterialTimePicker.Builder() - .setTimeFormat(timeFormat) - .setHour(calendar.hour) - .setMinute(calendar.minute) - .setTitleText(R.string.mozac_feature_prompts_set_time) - .build() - - timePicker.addOnPositiveButtonClickListener { - val finalCalendar = ( - dateSelection?.let { - Calendar.getInstance().apply { - timeInMillis = it - TimeZone.getDefault().getOffset(it) - } - } ?: selectedDate.toCalendar() + // Create the appropriate time picker dialog for the given step value. + private fun createTimePickerDialog(context: Context): AlertDialog { + // Create the Android time picker dialog + fun createTimePickerDialog(): AlertDialog { + return initialDate.toCalendar().let { cal -> + TimePickerDialog( + context, + this, + cal.hour, + cal.minute, + DateFormat.is24HourFormat(context), ) - - finalCalendar.set(Calendar.HOUR_OF_DAY, timePicker.hour) - finalCalendar.set(Calendar.MINUTE, timePicker.minute) - finalCalendar.set(Calendar.SECOND, 0) - finalCalendar.set(Calendar.MILLISECOND, 0) - selectedDate = finalCalendar.time - - feature?.onConfirm(sessionId, promptRequestUID, selectedDate) - dismiss() - } - timePicker.addOnNegativeButtonClickListener { - feature?.onCancel(sessionId, promptRequestUID) - dismiss() - } - timePicker.addOnCancelListener { - feature?.onCancel(sessionId, promptRequestUID) - dismiss() + } } - timePicker.show(parentFragmentManager, timePicker.toString()) - } - private fun createMaterialDatePickerDialog(isDateTimePicker: Boolean = false) { - val constraintsBuilder = CalendarConstraints.Builder().apply { - minimumDate?.let { setValidator(DateValidatorPointForward.from(it.time)) } - maximumDate?.let { setValidator(DateValidatorPointBackward.before(it.time)) } + // Create the custom time picker dialog + fun createTimeStepPickerDialog(stepValue: Float): AlertDialog { + return AlertDialog.Builder(context) + .setTitle(R.string.mozac_feature_prompts_set_time) + .setView( + TimePrecisionPicker( + context = requireContext(), + selectedTime = initialDate.toCalendar(), + maxTime = maximumDate?.toCalendar() + ?: TimePrecisionPicker.getDefaultMaxTime(), + minTime = minimumDate?.toCalendar() + ?: TimePrecisionPicker.getDefaultMinTime(), + stepValue = stepValue, + timeSetListener = this, + ), + ) + .create() + .also { + it.setButton( + BUTTON_POSITIVE, + context.getString(R.string.mozac_feature_prompts_set_date), + this, + ) + it.setButton( + BUTTON_NEGATIVE, + context.getString(R.string.mozac_feature_prompts_cancel), + this, + ) + } } - val initialUtcTime = initialDate.time + TimeZone.getDefault().getOffset(initialDate.time) - - val datePicker = MaterialDatePicker.Builder.datePicker() - .setSelection(initialUtcTime) - .setPositiveButtonText(R.string.mozac_feature_prompts_set_date) - .setNegativeButtonText(R.string.mozac_feature_prompts_cancel) - .setCalendarConstraints(constraintsBuilder.build()) - .build() - datePicker.addOnPositiveButtonClickListener { selection -> - if (isDateTimePicker) { - // For the date-time picker, we dismiss the date picker first - // and then show the time picker. - datePicker.dismiss() - createMaterialTimePickerDialog(selection) - } else { - // For the date-only picker, we confirm the selection and dismiss everything. - val millis = (selection ?: 0L) - TimeZone.getDefault().getOffset(selection ?: 0L) - - selectedDate = Date(millis) - feature?.onConfirm(sessionId, promptRequestUID, selectedDate) - datePicker.dismiss() - dismissAllowingStateLoss() - } + return if (!shouldShowSecondsPicker(stepSize?.toFloat())) { + createTimePickerDialog() + } else { + stepSize?.let { + createTimeStepPickerDialog(it.toFloat()) + } ?: createTimePickerDialog() } - datePicker.addOnNegativeButtonClickListener { - feature?.onCancel(sessionId, promptRequestUID) - dismiss() - } - datePicker.addOnCancelListener { - feature?.onCancel(sessionId, promptRequestUID) - dismiss() - } - datePicker.show(parentFragmentManager, datePicker.toString()) - } - - private fun createMonthPickerDialog(): AlertDialog { - val view = inflateDateMonthPicker() - return buildDialogWithView(view, titleResId = R.string.mozac_feature_prompts_set_month) } - private fun buildDialogWithView(view: View, titleResId: Int? = null): AlertDialog { - val builder = MaterialAlertDialogBuilder(requireContext()) - .setView(view) - .setPositiveButton(R.string.mozac_feature_prompts_set_date, this) - .setNegativeButton(R.string.mozac_feature_prompts_cancel, this) - .setNeutralButton(R.string.mozac_feature_prompts_clear, this) + @SuppressLint("InflateParams") + private fun inflateDateTimePicker(inflater: LayoutInflater): View { + val view = inflater.inflate(R.layout.mozac_feature_prompts_date_time_picker, null) + val datePicker = view.findViewById<DatePicker>(R.id.date_picker) + val dateTimePicker = view.findViewById<TimePicker>(R.id.datetime_picker) + val cal = initialDate.toCalendar() - titleResId?.let { builder.setTitle(it) } + // Bind date picker + setMinMaxDate(datePicker) + datePicker.init(cal.year, cal.month, cal.day, this) + initTimePicker(dateTimePicker, cal) - return builder.create() + return view } private fun inflateDateMonthPicker(): View { @@ -219,68 +214,72 @@ internal class TimePickerDialogFragment : ) } - fun createTimeStepPickerDialog(stepValue: Float): AlertDialog { - val view = TimePrecisionPicker( - context = requireContext(), - selectedTime = initialDate.toCalendar(), - maxTime = maximumDate?.toCalendar() ?: TimePrecisionPicker.getDefaultMaxTime(), - minTime = minimumDate?.toCalendar() ?: TimePrecisionPicker.getDefaultMinTime(), - stepValue = stepValue, - timeSetListener = this, - ) - return buildDialogWithView(view, titleResId = R.string.mozac_feature_prompts_set_time) + private fun initTimePicker(picker: TimePicker, cal: Calendar) { + picker.hour = cal.hour + picker.minute = cal.minute + picker.setIs24HourView(DateFormat.is24HourFormat(requireContext())) + picker.setOnTimeChangedListener(this) } - override fun onClick(dialog: DialogInterface?, which: Int) { - when (which) { - BUTTON_POSITIVE -> feature?.onConfirm(sessionId, promptRequestUID, selectedDate) - BUTTON_NEGATIVE -> feature?.onCancel(sessionId, promptRequestUID) - BUTTON_NEUTRAL -> feature?.onClear(sessionId, promptRequestUID) + private fun setMinMaxDate(datePicker: DatePicker) { + minimumDate?.let { + datePicker.minDate = it.time + } + maximumDate?.let { + datePicker.maxDate = it.time } } - override fun onTimeChanged(picker: TimePicker?, hourOfDay: Int, minute: Int) { + override fun onTimeSet( + picker: TimePrecisionPicker, + hour: Int, + minute: Int, + second: Int, + millisecond: Int, + ) { val calendar = selectedDate.toCalendar() - calendar.set(Calendar.HOUR_OF_DAY, hourOfDay) - calendar.set(Calendar.MINUTE, minute) + calendar.hour = hour + calendar.minute = minute + calendar.second = second + calendar.millisecond = millisecond selectedDate = calendar.time } - override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { - onTimeChanged(view, hourOfDay, minute) - onClick(null, BUTTON_POSITIVE) - } - override fun onDateChanged(view: DatePicker?, year: Int, monthOfYear: Int, dayOfMonth: Int) { val calendar = Calendar.getInstance() calendar.set(year, monthOfYear, dayOfMonth) selectedDate = calendar.time } + override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { + onDateChanged(view, year, month, dayOfMonth) + onClick(null, BUTTON_POSITIVE) + } + override fun onDateSet(picker: MonthAndYearPicker, month: Int, year: Int) { onDateChanged(null, year, month, 0) } - override fun onTimeSet( - picker: TimePrecisionPicker, - hour: Int, - minute: Int, - second: Int, - millisecond: Int, - ) { + override fun onTimeChanged(picker: TimePicker?, hourOfDay: Int, minute: Int) { val calendar = selectedDate.toCalendar() - calendar.hour = hour - calendar.minute = minute - calendar.second = second - calendar.millisecond = millisecond + calendar.set(Calendar.HOUR_OF_DAY, hourOfDay) + calendar.set(Calendar.MINUTE, minute) selectedDate = calendar.time } - override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { - onDateChanged(view, year, month, dayOfMonth) + override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) { + onTimeChanged(view, hourOfDay, minute) onClick(null, BUTTON_POSITIVE) } + override fun onClick(dialog: DialogInterface?, which: Int) { + when (which) { + BUTTON_POSITIVE -> feature?.onConfirm(sessionId, promptRequestUID, selectedDate) + BUTTON_NEGATIVE -> feature?.onCancel(sessionId, promptRequestUID) + BUTTON_NEUTRAL -> feature?.onClear(sessionId, promptRequestUID) + } + } + companion object { /** * A builder method for creating a [TimePickerDialogFragment] diff --git a/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt b/mobile/android/android-components/components/feature/prompts/src/test/java/mozilla/components/feature/prompts/dialog/TimePickerDialogFragmentTest.kt @@ -4,13 +4,15 @@ package mozilla.components.feature.prompts.dialog +import android.app.AlertDialog +import android.app.DatePickerDialog +import android.app.TimePickerDialog import android.content.DialogInterface.BUTTON_NEUTRAL import android.content.DialogInterface.BUTTON_POSITIVE import android.os.Looper.getMainLooper import android.widget.DatePicker import android.widget.NumberPicker import android.widget.TimePicker -import androidx.appcompat.app.AlertDialog import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.feature.prompts.R import mozilla.components.feature.prompts.dialog.TimePickerDialogFragment.Companion.SELECTION_TYPE_DATE_AND_TIME @@ -42,13 +44,75 @@ import java.util.Date @RunWith(AndroidJUnit4::class) class TimePickerDialogFragmentTest { + @Mock private lateinit var mockFeature: Prompter + @Before fun setup() { - testContext.setTheme(com.google.android.material.R.style.Theme_MaterialComponents_Light) openMocks(this) } @Test + fun `build dialog`() { + val initialDate = "2019-11-29".toDate("yyyy-MM-dd") + val minDate = "2019-11-28".toDate("yyyy-MM-dd") + val maxDate = "2019-11-30".toDate("yyyy-MM-dd") + val fragment = spy( + TimePickerDialogFragment.newInstance("sessionId", "uid", true, initialDate, minDate, maxDate), + ) + + doReturn(appCompatContext).`when`(fragment).requireContext() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val datePicker = (dialog as DatePickerDialog).datePicker + assertEquals("sessionId", fragment.sessionId) + assertEquals("uid", fragment.promptRequestUID) + assertEquals(2019, datePicker.year) + assertEquals(11, datePicker.month + 1) + assertEquals(29, datePicker.dayOfMonth) + assertEquals(minDate, Date(datePicker.minDate)) + assertEquals(maxDate, Date(datePicker.maxDate)) + } + + @Test + fun `Clicking on positive, neutral and negative button notifies the feature`() { + val initialDate = "2019-11-29".toDate("yyyy-MM-dd") + val fragment = spy( + TimePickerDialogFragment.newInstance("sessionId", "uid", false, initialDate, null, null), + ) + fragment.feature = mockFeature + + doReturn(appCompatContext).`when`(fragment).requireContext() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE) + positiveButton.performClick() + shadowOf(getMainLooper()).idle() + + verify(mockFeature).onConfirm(eq("sessionId"), eq("uid"), any()) + + val neutralButton = dialog.getButton(BUTTON_NEUTRAL) + neutralButton.performClick() + shadowOf(getMainLooper()).idle() + + verify(mockFeature).onClear("sessionId", "uid") + } + + @Test + fun `touching outside of the dialog must notify the feature onCancel`() { + val fragment = spy( + TimePickerDialogFragment.newInstance("sessionId", "uid", true, Date(), null, null), + ) + fragment.feature = mockFeature + doReturn(testContext).`when`(fragment).requireContext() + fragment.onCancel(mock()) + verify(mockFeature).onCancel("sessionId", "uid") + } + + @Test fun `onTimeChanged must update the selectedDate`() { val dialogPicker = TimePickerDialogFragment.newInstance("sessionId", "uid", false, Date(), null, null) @@ -61,6 +125,43 @@ class TimePickerDialogFragmentTest { } @Test + fun `building a date and time picker`() { + val initialDate = "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm") + val minDate = "2018-06-07T00:00".toDate("yyyy-MM-dd'T'HH:mm") + val maxDate = "2018-06-14T00:00".toDate("yyyy-MM-dd'T'HH:mm") + val fragment = spy( + TimePickerDialogFragment.newInstance( + "sessionId", + "uid", + true, + initialDate, + minDate, + maxDate, + SELECTION_TYPE_DATE_AND_TIME, + ), + ) + + doReturn(appCompatContext).`when`(fragment).requireContext() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + + val datePicker = dialog.findViewById<DatePicker>(R.id.date_picker) + + assertEquals(2018, datePicker.year) + assertEquals(6, datePicker.month + 1) + assertEquals(12, datePicker.dayOfMonth) + + assertEquals(minDate, Date(datePicker.minDate)) + assertEquals(maxDate, Date(datePicker.maxDate)) + + val timePicker = dialog.findViewById<TimePicker>(R.id.datetime_picker) + + assertEquals(19, timePicker.hour) + assertEquals(30, timePicker.minute) + } + + @Test fun `building a month picker`() { val initialDate = "2018-06".toDate("yyyy-MM") val minDate = "2018-04".toDate("yyyy-MM") @@ -106,6 +207,30 @@ class TimePickerDialogFragmentTest { assertEquals(7, selectedDate.month) } + @Test + fun `building a time picker`() { + val initialDate = "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm") + val minDate = "2018-06-07T00:00".toDate("yyyy-MM-dd'T'HH:mm") + val maxDate = "2018-06-14T00:00".toDate("yyyy-MM-dd'T'HH:mm") + val fragment = spy( + TimePickerDialogFragment.newInstance( + "sessionId", + "uid", + true, + initialDate, + minDate, + maxDate, + SELECTION_TYPE_TIME, + ), + ) + + doReturn(appCompatContext).`when`(fragment).requireContext() + + val dialog = fragment.onCreateDialog(null) + dialog.show() + assertTrue(dialog is TimePickerDialog) + } + @Test(expected = IllegalArgumentException::class) fun `creating a TimePickerDialogFragment with an invalid type selection will throw an exception`() { val initialDate = "2018-06-12T19:30".toDate("yyyy-MM-dd'T'HH:mm") diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/WebControlsTest.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/WebControlsTest.kt @@ -59,10 +59,14 @@ class WebControlsTest : TestSetup() { clickPageObject(itemWithResId("submitDate")) verifyNoDateIsSelected() clickPageObject(itemWithResId("calendar")) - clickPageObject(itemWithDescription("$currentMonth $currentDay")) - clickPageObject(itemContainingText("Set")) + clickPageObject(itemWithDescription("$currentDay $currentMonth $currentYear")) + clickPageObject(itemContainingText("OK")) clickPageObject(itemWithResId("submitDate")) verifySelectedDate() + clickPageObject(itemWithResId("calendar")) + clickPageObject(itemContainingText("CLEAR")) + clickPageObject(itemWithResId("submitDate")) + verifyNoDateIsSelected() } } @@ -74,7 +78,7 @@ class WebControlsTest : TestSetup() { navigationToolbar { }.enterURLAndEnterToBrowser(htmlControlsPage.url) { clickPageObject(itemWithResId("clock")) - clickPageObject(itemContainingText("Cancel")) + clickPageObject(itemContainingText("CANCEL")) clickPageObject(itemWithResId("submitTime")) verifyNoTimeIsSelected(hour, minute) clickPageObject(itemWithResId("clock")) @@ -82,6 +86,10 @@ class WebControlsTest : TestSetup() { clickPageObject(itemContainingText("OK")) clickPageObject(itemWithResId("submitTime")) verifySelectedTime(hour, minute) + clickPageObject(itemWithResId("clock")) + clickPageObject(itemContainingText("CLEAR")) + clickPageObject(itemWithResId("submitTime")) + verifyNoTimeIsSelected(hour, minute) } } diff --git a/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt b/mobile/android/fenix/app/src/androidTest/java/org/mozilla/fenix/ui/robots/BrowserRobot.kt @@ -10,6 +10,7 @@ import android.content.Context import android.net.Uri import android.os.SystemClock import android.util.Log +import android.widget.TimePicker import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.assertAny import androidx.compose.ui.test.assertIsDisplayed @@ -31,8 +32,10 @@ import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.longClick import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.PickerActions import androidx.test.espresso.matcher.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers.Visibility +import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility @@ -767,9 +770,11 @@ class BrowserRobot { fun selectTime(hour: Int, minute: Int) { Log.i(TAG, "selectTime: Trying to select time picker hour: $hour and minute: $minute") - itemWithDescription("$hour o'clock").click() - waitForAppWindowToBeUpdated() - itemWithDescription("$minute minutes").click() + onView( + isAssignableFrom(TimePicker::class.java), + ).inRoot( + isDialog(), + ).perform(PickerActions.setTime(hour, minute)) Log.i(TAG, "selectTime: Selected time picker hour: $hour and minute: $minute") } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/doh/root/DohSettingsScreen.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/settings/doh/root/DohSettingsScreen.kt @@ -332,7 +332,6 @@ private fun DohSelection( if (state.selectedProvider is Provider.Custom && state.isCustomProviderDialogOn) { AlertDialogAddCustomProvider( - customProviderUrl = state.selectedProvider.url, customProviderErrorState = state.customProviderErrorState, onCustomCancelClicked = { onCustomCancelClicked() }, onCustomAddClicked = { url -> @@ -444,12 +443,11 @@ private fun buildProviderMenuItems( @Composable private fun AlertDialogAddCustomProvider( - customProviderUrl: String, customProviderErrorState: CustomProviderErrorState, onCustomCancelClicked: () -> Unit, onCustomAddClicked: (String) -> Unit, ) { - var customProviderInput by remember { mutableStateOf(customProviderUrl) } + var customProviderInput by remember { mutableStateOf("") } val onCustomProviderInputChange: (String) -> Unit = { it -> customProviderInput = it } val nonHttpsString = stringResource(R.string.preference_doh_provider_custom_dialog_error_https) val invalidString = stringResource(R.string.preference_doh_provider_custom_dialog_error_invalid) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/banner/TabsTrayBanner.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/banner/TabsTrayBanner.kt @@ -8,11 +8,8 @@ package org.mozilla.fenix.tabstray.ui.banner import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CenterAlignedTopAppBar @@ -78,7 +75,6 @@ private val TabIndicatorRoundedCornerDp = 100.dp * @param syncedTabCount The total number of open synced tabs. * @param selectionMode [TabsTrayState.Mode] indicating the current selection mode (e.g., normal, multi-select). * @param isInDebugMode True for debug variant or if secret menu is enabled for this session. - * @param statusBarHeight The height of the system status bar. * @param shouldShowTabAutoCloseBanner Whether the tab auto-close banner should be displayed. * @param shouldShowLockPbmBanner Whether the lock private browsing mode banner should be displayed. * @param scrollBehavior Defines how the [TabPageBanner] should behave when the content under it is scrolled. @@ -105,7 +101,6 @@ fun TabsTrayBanner( syncedTabCount: Int, selectionMode: Mode, isInDebugMode: Boolean, - statusBarHeight: Dp, shouldShowTabAutoCloseBanner: Boolean, shouldShowLockPbmBanner: Boolean, scrollBehavior: TopAppBarScrollBehavior, @@ -163,7 +158,6 @@ fun TabsTrayBanner( normalTabCount = normalTabCount, privateTabCount = privateTabCount, syncedTabCount = syncedTabCount, - statusBarHeight = statusBarHeight, scrollBehavior = scrollBehavior, onTabPageIndicatorClicked = onTabPageIndicatorClicked, ) @@ -173,8 +167,6 @@ fun TabsTrayBanner( !hasAcknowledgedAutoCloseBanner && showTabAutoCloseBanner -> { onTabAutoCloseBannerShown() - BannerPadding(scrollBehavior = scrollBehavior, statusBarHeight = statusBarHeight) - HorizontalDivider() Banner( @@ -193,8 +185,6 @@ fun TabsTrayBanner( } !hasAcknowledgedPbmLockBanner && shouldShowLockPbmBanner -> { - BannerPadding(scrollBehavior = scrollBehavior, statusBarHeight = statusBarHeight) - // After this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1965545 // is resolved, we should swap the button 1 and button 2 click actions. Banner( @@ -216,18 +206,6 @@ fun TabsTrayBanner( } } -@Composable -private fun BannerPadding( - scrollBehavior: TopAppBarScrollBehavior, - statusBarHeight: Dp, -) { - val padding by remember(statusBarHeight, scrollBehavior.state.collapsedFraction) { - derivedStateOf { statusBarHeight * scrollBehavior.state.collapsedFraction } - } - - Spacer(modifier = Modifier.height(padding)) -} - /** * Banner displayed when in [Mode.Normal]. * @@ -235,7 +213,6 @@ private fun BannerPadding( * @param normalTabCount The amount of open Normal tabs. * @param privateTabCount The amount of open Private tabs. * @param syncedTabCount The amount of synced tabs. - * @param statusBarHeight The height of the system status bar. * @param scrollBehavior Defines how the [TabPageBanner] should behave when the content under it is scrolled. * @param onTabPageIndicatorClicked Invoked when the user clicks on a tab page button. Passes along the * [Page] that was clicked. @@ -247,7 +224,6 @@ private fun TabPageBanner( normalTabCount: Int, privateTabCount: Int, syncedTabCount: Int, - statusBarHeight: Dp, scrollBehavior: TopAppBarScrollBehavior, onTabPageIndicatorClicked: (Page) -> Unit, ) { @@ -256,104 +232,95 @@ private fun TabPageBanner( CenterAlignedTopAppBar( title = { - Column { - Spacer( - modifier = Modifier - .height(statusBarHeight) - .fillMaxWidth(), + PrimaryTabRow( + selectedTabIndex = selectedTabIndex, + modifier = Modifier.fillMaxWidth(), + contentColor = MaterialTheme.colorScheme.primary, + containerColor = Color.Transparent, + indicator = { + TabRowDefaults.PrimaryIndicator( + modifier = Modifier.tabIndicatorOffset( + selectedTabIndex = selectedTabIndex, + matchContentSize = true, + ), + width = Dp.Unspecified, + shape = RoundedCornerShape( + topStart = TabIndicatorRoundedCornerDp, + topEnd = TabIndicatorRoundedCornerDp, + ), + ) + }, + divider = {}, + ) { + val privateTabDescription = stringResource( + id = R.string.tabs_header_private_tabs_counter_title, + privateTabCount.toString(), ) - PrimaryTabRow( - selectedTabIndex = selectedTabIndex, - modifier = Modifier.fillMaxWidth(), - contentColor = MaterialTheme.colorScheme.primary, - containerColor = Color.Transparent, - indicator = { - TabRowDefaults.PrimaryIndicator( - modifier = Modifier.tabIndicatorOffset( - selectedTabIndex = selectedTabIndex, - matchContentSize = true, - ), - width = Dp.Unspecified, - shape = RoundedCornerShape( - topStart = TabIndicatorRoundedCornerDp, - topEnd = TabIndicatorRoundedCornerDp, - ), - ) - }, - divider = {}, + val normalTabDescription = stringResource( + id = R.string.tabs_header_normal_tabs_counter_title, + normalTabCount.toString(), + ) + val syncedTabDescription = stringResource( + id = R.string.tabs_header_synced_tabs_counter_title, + syncedTabCount.toString(), + ) + + Tab( + selected = selectedPage == Page.PrivateTabs, + onClick = { onTabPageIndicatorClicked(Page.PrivateTabs) }, + modifier = Modifier + .testTag(TabsTrayTestTag.PRIVATE_TABS_PAGE_BUTTON) + .semantics { + contentDescription = privateTabDescription + } + .height(RowHeight), + unselectedContentColor = inactiveColor, ) { - val privateTabDescription = stringResource( - id = R.string.tabs_header_private_tabs_counter_title, - privateTabCount.toString(), - ) - val normalTabDescription = stringResource( - id = R.string.tabs_header_normal_tabs_counter_title, - normalTabCount.toString(), + Text( + text = stringResource(id = R.string.tabs_header_private_tabs_title), + style = FirefoxTheme.typography.button, ) - val syncedTabDescription = stringResource( - id = R.string.tabs_header_synced_tabs_counter_title, - syncedTabCount.toString(), - ) - - Tab( - selected = selectedPage == Page.PrivateTabs, - onClick = { onTabPageIndicatorClicked(Page.PrivateTabs) }, - modifier = Modifier - .testTag(TabsTrayTestTag.PRIVATE_TABS_PAGE_BUTTON) - .semantics { - contentDescription = privateTabDescription - } - .height(RowHeight), - unselectedContentColor = inactiveColor, - ) { - Text( - text = stringResource(id = R.string.tabs_header_private_tabs_title), - style = FirefoxTheme.typography.button, - ) - } + } - Tab( - selected = selectedPage == Page.NormalTabs, - onClick = { onTabPageIndicatorClicked(Page.NormalTabs) }, - modifier = Modifier - .testTag(TabsTrayTestTag.NORMAL_TABS_PAGE_BUTTON) - .semantics { - contentDescription = normalTabDescription - } - .height(RowHeight), - unselectedContentColor = inactiveColor, - ) { - Text( - text = stringResource(R.string.tabs_header_normal_tabs_title), - style = FirefoxTheme.typography.button, - ) - } + Tab( + selected = selectedPage == Page.NormalTabs, + onClick = { onTabPageIndicatorClicked(Page.NormalTabs) }, + modifier = Modifier + .testTag(TabsTrayTestTag.NORMAL_TABS_PAGE_BUTTON) + .semantics { + contentDescription = normalTabDescription + } + .height(RowHeight), + unselectedContentColor = inactiveColor, + ) { + Text( + text = stringResource(R.string.tabs_header_normal_tabs_title), + style = FirefoxTheme.typography.button, + ) + } - Tab( - selected = selectedPage == Page.SyncedTabs, - onClick = { onTabPageIndicatorClicked(Page.SyncedTabs) }, - modifier = Modifier - .testTag(TabsTrayTestTag.SYNCED_TABS_PAGE_BUTTON) - .semantics { - contentDescription = syncedTabDescription - } - .height(RowHeight), - unselectedContentColor = inactiveColor, - ) { - Text( - text = stringResource(id = R.string.tabs_header_synced_tabs_title), - style = FirefoxTheme.typography.button, - ) - } + Tab( + selected = selectedPage == Page.SyncedTabs, + onClick = { onTabPageIndicatorClicked(Page.SyncedTabs) }, + modifier = Modifier + .testTag(TabsTrayTestTag.SYNCED_TABS_PAGE_BUTTON) + .semantics { + contentDescription = syncedTabDescription + } + .height(RowHeight), + unselectedContentColor = inactiveColor, + ) { + Text( + text = stringResource(id = R.string.tabs_header_synced_tabs_title), + style = FirefoxTheme.typography.button, + ) } } }, - expandedHeight = RowHeight + statusBarHeight, + expandedHeight = RowHeight, colors = TopAppBarDefaults.centerAlignedTopAppBarColors( scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh, ), - // Allow this TopAppBar to be drawn behind the status bar instead of stopping at it. - windowInsets = TopAppBarDefaults.windowInsets.only(WindowInsetsSides.Horizontal), scrollBehavior = scrollBehavior, ) } @@ -573,7 +540,6 @@ private fun TabsTrayBannerPreviewRoot( syncedTabCount = 0, selectionMode = state.mode, isInDebugMode = false, - statusBarHeight = 50.dp, shouldShowTabAutoCloseBanner = shouldShowTabAutoCloseBanner, shouldShowLockPbmBanner = shouldShowLockPbmBanner, scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabpage/TabLayout.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabpage/TabLayout.kt @@ -335,6 +335,7 @@ private fun TabList( .padding( start = TabListPadding, end = TabListPadding, + top = TabListPadding, ) .clip(TabListCornerShape) .background(MaterialTheme.colorScheme.surface) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabstray/TabsTray.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabstray/TabsTray.kt @@ -6,17 +6,9 @@ package org.mozilla.fenix.tabstray.ui.tabstray -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.BottomAppBarDefaults @@ -76,7 +68,6 @@ import org.mozilla.fenix.tabstray.ui.syncedtabs.OnTabCloseClick as OnSyncedTabCl * BottomAppBar. */ private val ScaffoldFabOffsetCorrection = 4.dp -private const val SPACER_BACKGROUND_ALPHA = 0.75f /** * Top-level UI for displaying the Tabs Tray feature. @@ -197,11 +188,6 @@ fun TabsTray( .sumOf { deviceSection: SyncedTabsListItem.DeviceSection -> deviceSection.tabs.size } } - val systemBarsInsets = WindowInsets.systemBars.asPaddingValues() - val statusBarHeight = remember(systemBarsInsets) { - systemBarsInsets.calculateTopPadding() - } - val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val bottomAppBarScrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior() @@ -230,7 +216,6 @@ fun TabsTray( syncedTabCount = syncedTabCount, selectionMode = tabsTrayState.mode, isInDebugMode = isInDebugMode, - statusBarHeight = statusBarHeight, shouldShowTabAutoCloseBanner = shouldShowTabAutoCloseBanner, shouldShowLockPbmBanner = shouldShowLockPbmBanner, scrollBehavior = topAppBarScrollBehavior, @@ -276,83 +261,71 @@ fun TabsTray( floatingActionButtonPosition = FabPosition.EndOverlay, containerColor = MaterialTheme.colorScheme.surface, ) { paddingValues -> - Box { - HorizontalPager( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), - state = pagerState, - beyondViewportPageCount = 2, - userScrollEnabled = false, - ) { position -> - when (Page.positionToPage(position)) { - Page.NormalTabs -> { - NormalTabsPage( - normalTabs = tabsTrayState.normalTabs, - inactiveTabs = tabsTrayState.inactiveTabs, - selectedTabId = tabsTrayState.selectedTabId, - selectionMode = tabsTrayState.mode, - inactiveTabsExpanded = tabsTrayState.inactiveTabsExpanded, - displayTabsInGrid = displayTabsInGrid, - onTabClose = onTabClose, - onTabClick = onTabClick, - onTabLongClick = onTabLongClick, - shouldShowInactiveTabsAutoCloseDialog = shouldShowInactiveTabsAutoCloseDialog, - onInactiveTabsHeaderClick = onInactiveTabsHeaderClick, - onDeleteAllInactiveTabsClick = onDeleteAllInactiveTabsClick, - onInactiveTabsAutoCloseDialogShown = onInactiveTabsAutoCloseDialogShown, - onInactiveTabAutoCloseDialogCloseButtonClick = onInactiveTabAutoCloseDialogCloseButtonClick, - onEnableInactiveTabAutoCloseClick = onEnableInactiveTabAutoCloseClick, - onInactiveTabClick = onInactiveTabClick, - onInactiveTabClose = onInactiveTabClose, - onMove = onMove, - shouldShowInactiveTabsCFR = shouldShowInactiveTabsCFR, - onInactiveTabsCFRShown = onInactiveTabsCFRShown, - onInactiveTabsCFRClick = onInactiveTabsCFRClick, - onInactiveTabsCFRDismiss = onInactiveTabsCFRDismiss, - onTabDragStart = { - tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode) - }, - ) - } + HorizontalPager( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + state = pagerState, + beyondViewportPageCount = 2, + userScrollEnabled = false, + ) { position -> + when (Page.positionToPage(position)) { + Page.NormalTabs -> { + NormalTabsPage( + normalTabs = tabsTrayState.normalTabs, + inactiveTabs = tabsTrayState.inactiveTabs, + selectedTabId = tabsTrayState.selectedTabId, + selectionMode = tabsTrayState.mode, + inactiveTabsExpanded = tabsTrayState.inactiveTabsExpanded, + displayTabsInGrid = displayTabsInGrid, + onTabClose = onTabClose, + onTabClick = onTabClick, + onTabLongClick = onTabLongClick, + shouldShowInactiveTabsAutoCloseDialog = shouldShowInactiveTabsAutoCloseDialog, + onInactiveTabsHeaderClick = onInactiveTabsHeaderClick, + onDeleteAllInactiveTabsClick = onDeleteAllInactiveTabsClick, + onInactiveTabsAutoCloseDialogShown = onInactiveTabsAutoCloseDialogShown, + onInactiveTabAutoCloseDialogCloseButtonClick = onInactiveTabAutoCloseDialogCloseButtonClick, + onEnableInactiveTabAutoCloseClick = onEnableInactiveTabAutoCloseClick, + onInactiveTabClick = onInactiveTabClick, + onInactiveTabClose = onInactiveTabClose, + onMove = onMove, + shouldShowInactiveTabsCFR = shouldShowInactiveTabsCFR, + onInactiveTabsCFRShown = onInactiveTabsCFRShown, + onInactiveTabsCFRClick = onInactiveTabsCFRClick, + onInactiveTabsCFRDismiss = onInactiveTabsCFRDismiss, + onTabDragStart = { + tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode) + }, + ) + } - Page.PrivateTabs -> { - PrivateTabsPage( - privateTabs = tabsTrayState.privateTabs, - selectedTabId = tabsTrayState.selectedTabId, - selectionMode = tabsTrayState.mode, - displayTabsInGrid = displayTabsInGrid, - privateTabsLocked = isPbmLocked, - onTabClose = onTabClose, - onTabClick = onTabClick, - onTabLongClick = onTabLongClick, - onMove = onMove, - onUnlockPbmClick = onUnlockPbmClick, - ) - } + Page.PrivateTabs -> { + PrivateTabsPage( + privateTabs = tabsTrayState.privateTabs, + selectedTabId = tabsTrayState.selectedTabId, + selectionMode = tabsTrayState.mode, + displayTabsInGrid = displayTabsInGrid, + privateTabsLocked = isPbmLocked, + onTabClose = onTabClose, + onTabClick = onTabClick, + onTabLongClick = onTabLongClick, + onMove = onMove, + onUnlockPbmClick = onUnlockPbmClick, + ) + } - Page.SyncedTabs -> { - SyncedTabsPage( - isSignedIn = isSignedIn, - syncedTabs = tabsTrayState.syncedTabs, - onTabClick = onSyncedTabClick, - onTabClose = onSyncedTabClose, - onSignInClick = onSignInClick, - ) - } + Page.SyncedTabs -> { + SyncedTabsPage( + isSignedIn = isSignedIn, + syncedTabs = tabsTrayState.syncedTabs, + onTabClick = onSyncedTabClick, + onTabClose = onSyncedTabClose, + onSignInClick = onSignInClick, + ) } } } - Spacer( - Modifier - .height(statusBarHeight) - .fillMaxWidth() - .background( - MaterialTheme.colorScheme.surface.copy( - SPACER_BACKGROUND_ALPHA, - ), - ), - ) } } @@ -553,9 +526,9 @@ private class TabsTrayStateParameterProvider : PreviewParameterProvider<TabsTray showInactiveTabsAutoCloseDialog = true, ), // TabsTray Private Tabs Preview - TabsTrayPreviewModel( - selectedPage = Page.PrivateTabs, - privateTabs = generateFakeTabsList(isPrivate = true), + TabsTrayPreviewModel( + selectedPage = Page.PrivateTabs, + privateTabs = generateFakeTabsList(isPrivate = true), ), // TabsTray Synced Tab Preview TabsTrayPreviewModel( @@ -596,10 +569,7 @@ private data class TabsTrayPreviewModel( val isSignedIn: Boolean = true, ) -private fun generateFakeTabsList( - tabCount: Int = 10, - isPrivate: Boolean = false, -): List<TabSessionState> = +private fun generateFakeTabsList(tabCount: Int = 10, isPrivate: Boolean = false): List<TabSessionState> = List(tabCount) { index -> TabSessionState( id = "tabId$index-$isPrivate", diff --git a/mobile/android/fenix/app/src/main/res/color/time_picker_button_background_tint.xml b/mobile/android/fenix/app/src/main/res/color/time_picker_button_background_tint.xml @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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/. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:color="?attr/colorSecondaryContainer" android:state_checked="true"/> -</selector> diff --git a/mobile/android/fenix/app/src/main/res/values/styles.xml b/mobile/android/fenix/app/src/main/res/values/styles.xml @@ -16,12 +16,13 @@ <style name="NormalThemeBase" parent="Theme.Material3.DayNight.NoActionBar"> <item name="preferenceTheme">@style/PreferenceTheme</item> - <item name="materialTimePickerTheme">@style/Normal.MaterialTimePicker</item> - <item name="materialCalendarTheme">@style/MaterialCalendar</item> + <!-- Android system styling --> <item name="searchViewStyle">@style/SearchViewStyle</item> <item name="autoCompleteTextViewStyle">@style/AutoCompleteTextViewStyle</item> + <item name="android:textAlignment">viewStart</item> <item name="android:windowContentTransitions">true</item> + <item name="android:datePickerDialogTheme">@style/DatePickerDialogStyle</item> <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item> <item name="android:progressBarStyleHorizontal">@style/progressBarStyleHorizontal</item> <item name="android:statusBarColor">@android:color/transparent</item> @@ -199,6 +200,32 @@ <item name="tabCounterTintColor">?attr/textPrimary</item> </style> + <!-- A theme to fix DatePickerDialogs year picker text alignment + https://bugzilla.mozilla.org/show_bug.cgi?id=1986252 --> + <style name="DatePickerDialogStyle" parent="ThemeOverlay.MaterialComponents.Dialog"> + <item name="android:textAlignment">gravity</item> + <item name="android:windowBackground">?attr/layer1</item> + <item name="android:colorEdgeEffect">@color/accent_normal_theme</item> + <item name="android:colorAccent">@color/fx_mobile_text_color_primary</item> + <item name="android:textColorPrimary">@color/state_list_text_color</item> + <item name="android:textColorSecondary">@color/secondary_state_list_text_color</item> + <item name="android:colorForeground">@color/toggle_off_track_normal_theme</item> + </style> + + <style name="PrivateDatePickerDialogStyle" parent="ThemeOverlay.MaterialComponents.Dialog"> + <item name="android:textAlignment">gravity</item> + <!-- For some reason in private mode it was trying to load @drawable/? + as the window background. This resolved to #424242. Hard coding + this until we know what color we want private mode dialogs to be. + see: https://bugzilla.mozilla.org/show_bug.cgi?id=1986252--> + <item name="android:windowBackground">#424242</item> + <item name="android:colorEdgeEffect">@color/accent_private_theme</item> + <item name="android:colorAccent">@color/fx_mobile_private_text_color_primary</item> + <item name="android:textColorPrimary">@color/state_list_text_color</item> + <item name="android:textColorSecondary">@color/secondary_state_list_text_color</item> + <item name="android:colorForeground">@color/toggle_off_track_dark_theme</item> + </style> + <!-- A theme derived from the normal activity theme, but to look and behave like a dialog --> <style name="DialogActivityTheme" parent="NormalTheme"> <item name="android:windowElevation">16dp</item> @@ -234,25 +261,6 @@ <item name="materialAlertDialogBodyTextStyle">@style/MaterialAlertDialog.App.Body.Text.Private</item> </style> - <style name="Normal.MaterialTimePicker" parent="ThemeOverlay.Material3.MaterialTimePicker"> - <item name="materialButtonOutlinedStyle">@style/My.Widget.MaterialComponents.TimePicker.Button</item> - </style> - - <style name="Private.MaterialTimePicker" parent="ThemeOverlay.Material3.MaterialTimePicker"> - <item name="materialClockStyle">@style/Private.MaterialTimePickerClock</item> - <item name="materialButtonOutlinedStyle">@style/My.Widget.MaterialComponents.TimePicker.Button</item> - </style> - - <style name="My.Widget.MaterialComponents.TimePicker.Button" parent="Widget.Material3.MaterialTimePicker.Button"> - <item name="backgroundTint">@color/time_picker_button_background_tint</item> - </style> - - <style name="Private.MaterialTimePickerClock" parent="Widget.MaterialComponents.TimePicker.Clock"> - <item name="clockFaceBackgroundColor">?attr/colorSurfaceContainerHighest</item> - <item name="clockHandColor">?attr/colorPrimary</item> - <item name="clockNumberTextColor">?attr/colorOnSurface</item> - </style> - <style name="MaterialAlertDialog.App.Body.Text.Private" parent="MaterialAlertDialog.App.Body.Text"> <item name="android:textColor">@color/fx_mobile_private_text_color_primary</item> </style> @@ -287,23 +295,6 @@ <item name="android:gravity">center</item> </style> - <style name="MaterialCalendar" parent="ThemeOverlay.Material3.MaterialCalendar"> - <item name="materialCalendarHeaderDivider">@style/MaterialCalendar.HeaderDivider</item> - <item name="materialCalendarHeaderTitle">@style/MaterialCalendar.Title.Text</item> - <item name="buttonBarPositiveButtonStyle">@style/DialogButtonStyle</item> - <item name="buttonBarNegativeButtonStyle">@style/DialogButtonStyle</item> - </style> - - <style name="MaterialCalendar.Title.Text" parent="@style/Widget.Material3.MaterialCalendar.HeaderTitle"> - <item name="android:textColor">?attr/colorOnSurface</item> - <item name="android:titleTextStyle">?attr/textAppearanceHeadlineLarge</item> - </style> - - <style name="MaterialCalendar.HeaderDivider" parent="Widget.Material3.MaterialCalendar.HeaderDivider"> - <item name="android:visibility">visible</item> - <item name="android:background">?attr/colorOutlineVariant</item> - </style> - <style name="MaterialAlertDialogShapeAppearance" parent=""> <item name="cornerFamily">rounded</item> <item name="cornerSize">@dimen/material_dialog_corner_radius</item> @@ -317,13 +308,13 @@ <style name="PrivateThemeBase" parent="Theme.Material3.DayNight.NoActionBar"> <!-- Android system styling --> - <item name="materialTimePickerTheme">@style/Private.MaterialTimePicker</item> - <item name="materialCalendarTheme">@style/MaterialCalendar</item> <item name="searchViewStyle">@style/SearchViewStyle</item> <item name="android:textColorLink">@color/fx_mobile_private_text_color_accent</item> <item name="preferenceTheme">@style/PreferenceTheme</item> <item name="checkboxStyle">@style/App.Widget.CompoundButton.CheckBox</item> <item name="autoCompleteTextViewStyle">@style/AutoCompleteTextViewStyle</item> + <item name="android:textAlignment">viewStart</item> + <item name="android:datePickerDialogTheme">@style/PrivateDatePickerDialogStyle</item> <item name="android:windowContentTransitions">true</item> <item name="android:windowAnimationStyle">@style/WindowAnimationTransition</item> <item name="android:progressBarStyleHorizontal">@style/progressBarStyleHorizontal</item> diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuStoreTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/components/menu/MenuStoreTest.kt @@ -140,9 +140,9 @@ class MenuStoreTest { } assertEquals(firefoxTab, newState.browserMenuState!!.selectedTab) - assertNull(newState.browserMenuState.bookmarkState.guid) - assertFalse(newState.browserMenuState.bookmarkState.isBookmarked) - assertFalse(newState.browserMenuState.isPinned) + assertNull(state.browserMenuState.bookmarkState.guid) + assertFalse(state.browserMenuState.bookmarkState.isBookmarked) + assertFalse(state.browserMenuState.isPinned) val bookmarkState = BookmarkState(guid = "id", isBookmarked = true) val isPinned = true diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/WebControlsTest.kt @@ -169,7 +169,7 @@ class WebControlsTest : TestSetup() { progressBar.waitUntilGone(waitingTime) clickCalendarForm() selectDate() - clickButtonWithText("Set") + clickButtonWithText("OK") clickSubmitDateButton() verifySelectedDate() } diff --git a/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt b/mobile/android/focus-android/app/src/androidTest/java/org/mozilla/focus/activity/robots/BrowserRobot.kt @@ -291,10 +291,12 @@ class BrowserRobot { fun clickCalendarForm() = clickPageObject(webPageItemWithResourceId("calendar")) fun selectDate() { + mDevice.findObject(UiSelector().resourceId("android:id/month_view")).waitForExists(waitingTime) + mDevice.findObject( UiSelector() .textContains("$currentDay") - .descriptionContains("$currentMonth $currentDay"), + .descriptionContains("$currentDay $currentMonth $currentYear"), ).click() } diff --git a/mobile/locales/l10n-changesets.json b/mobile/locales/l10n-changesets.json @@ -6,7 +6,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "an": { "pin": false, @@ -15,7 +15,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ar": { "pin": false, @@ -24,7 +24,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ast": { "pin": false, @@ -33,7 +33,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "az": { "pin": false, @@ -42,7 +42,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "be": { "pin": false, @@ -51,7 +51,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "bg": { "pin": false, @@ -60,7 +60,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "bn": { "pin": false, @@ -69,7 +69,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "br": { "pin": false, @@ -78,7 +78,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "bs": { "pin": false, @@ -87,7 +87,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ca": { "pin": false, @@ -96,7 +96,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "cak": { "pin": false, @@ -105,7 +105,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "cs": { "pin": false, @@ -114,7 +114,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "cy": { "pin": false, @@ -123,7 +123,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "da": { "pin": false, @@ -132,7 +132,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "de": { "pin": false, @@ -141,7 +141,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "dsb": { "pin": false, @@ -150,7 +150,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "el": { "pin": false, @@ -159,7 +159,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "en-CA": { "pin": false, @@ -168,7 +168,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "en-GB": { "pin": false, @@ -177,7 +177,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "eo": { "pin": false, @@ -186,7 +186,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-AR": { "pin": false, @@ -195,7 +195,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-CL": { "pin": false, @@ -204,7 +204,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-ES": { "pin": false, @@ -213,7 +213,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "es-MX": { "pin": false, @@ -222,7 +222,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "et": { "pin": false, @@ -231,7 +231,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "eu": { "pin": false, @@ -240,7 +240,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fa": { "pin": false, @@ -249,7 +249,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ff": { "pin": false, @@ -258,7 +258,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fi": { "pin": false, @@ -267,7 +267,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fr": { "pin": false, @@ -276,7 +276,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "fy-NL": { "pin": false, @@ -285,7 +285,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ga-IE": { "pin": false, @@ -294,7 +294,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gd": { "pin": false, @@ -303,7 +303,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gl": { "pin": false, @@ -312,7 +312,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gn": { "pin": false, @@ -321,7 +321,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "gu-IN": { "pin": false, @@ -330,7 +330,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "he": { "pin": false, @@ -339,7 +339,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hi-IN": { "pin": false, @@ -348,7 +348,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hr": { "pin": false, @@ -357,7 +357,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hsb": { "pin": false, @@ -366,7 +366,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hu": { "pin": false, @@ -375,7 +375,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "hy-AM": { "pin": false, @@ -384,7 +384,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ia": { "pin": false, @@ -393,7 +393,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "id": { "pin": false, @@ -402,7 +402,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "is": { "pin": false, @@ -411,7 +411,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "it": { "pin": false, @@ -420,7 +420,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ja": { "pin": false, @@ -429,7 +429,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ka": { "pin": false, @@ -438,7 +438,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "kab": { "pin": false, @@ -447,7 +447,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "kk": { "pin": false, @@ -456,7 +456,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "km": { "pin": false, @@ -465,7 +465,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "kn": { "pin": false, @@ -474,7 +474,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ko": { "pin": false, @@ -483,7 +483,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lij": { "pin": false, @@ -492,7 +492,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lo": { "pin": false, @@ -501,7 +501,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lt": { "pin": false, @@ -510,7 +510,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ltg": { "pin": false, @@ -519,7 +519,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "lv": { "pin": false, @@ -528,7 +528,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "meh": { "pin": false, @@ -537,7 +537,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "mix": { "pin": false, @@ -546,7 +546,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ml": { "pin": false, @@ -555,7 +555,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "mr": { "pin": false, @@ -564,7 +564,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ms": { "pin": false, @@ -573,7 +573,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "my": { "pin": false, @@ -582,7 +582,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "nb-NO": { "pin": false, @@ -591,7 +591,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ne-NP": { "pin": false, @@ -600,7 +600,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "nl": { "pin": false, @@ -609,7 +609,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "nn-NO": { "pin": false, @@ -618,7 +618,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "oc": { "pin": false, @@ -627,7 +627,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pa-IN": { "pin": false, @@ -636,7 +636,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pl": { "pin": false, @@ -645,7 +645,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pt-BR": { "pin": false, @@ -654,7 +654,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "pt-PT": { "pin": false, @@ -663,7 +663,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "rm": { "pin": false, @@ -672,7 +672,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ro": { "pin": false, @@ -681,7 +681,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ru": { "pin": false, @@ -690,7 +690,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sk": { "pin": false, @@ -699,7 +699,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sl": { "pin": false, @@ -708,7 +708,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "son": { "pin": false, @@ -717,7 +717,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sq": { "pin": false, @@ -726,7 +726,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sr": { "pin": false, @@ -735,7 +735,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "sv-SE": { "pin": false, @@ -744,7 +744,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ta": { "pin": false, @@ -753,7 +753,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "te": { "pin": false, @@ -762,7 +762,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "th": { "pin": false, @@ -771,7 +771,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "tl": { "pin": false, @@ -780,7 +780,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "tr": { "pin": false, @@ -789,7 +789,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "trs": { "pin": false, @@ -798,7 +798,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "uk": { "pin": false, @@ -807,7 +807,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "ur": { "pin": false, @@ -816,7 +816,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "uz": { "pin": false, @@ -825,7 +825,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "vi": { "pin": false, @@ -834,7 +834,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "wo": { "pin": false, @@ -843,7 +843,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "xh": { "pin": false, @@ -852,7 +852,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "zam": { "pin": false, @@ -861,7 +861,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "zh-CN": { "pin": false, @@ -870,7 +870,7 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" }, "zh-TW": { "pin": false, @@ -879,6 +879,6 @@ "android-arm", "android-multilocale" ], - "revision": "e301addeabd26c741eb474a4a17ae29944494fa2" + "revision": "f2a1a7115a5614ee279b545fcbbdb8b33cd76d3e" } } \ No newline at end of file diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml @@ -11105,12 +11105,6 @@ value: true mirror: always -# Whether to remove the accent property from the operator dictionary. -- name: mathml.operator_dictionary_accent.disabled - type: bool - value: false - mirror: always - # Whether to disable the MathML3 support for the mathvariant attribute. For # MathML Core, support is restricted to the <mi> element and to value "normal". # Corresponding automatic italicization on single-char <mi> element is also @@ -17119,13 +17113,6 @@ type: RelaxedAtomicBool value: false mirror: always - - # Whether \Device\KsecDD is closed in content process. We are closing this - # using a pref to give users a work-around if they have issues. -- name: security.sandbox.content.close-ksecdd-handle - type: RelaxedAtomicBool - value: true - mirror: always #endif #if defined(XP_LINUX) && defined(MOZ_SANDBOX) diff --git a/mozglue/baseprofiler/core/platform.cpp b/mozglue/baseprofiler/core/platform.cpp @@ -1618,9 +1618,10 @@ static void MaybeWriteRawStartTimeValue(SpliceableJSONWriter& aWriter, #endif #ifdef XP_WIN - uint64_t startTimeQPC = aStartTime.RawQueryPerformanceCounterValue(); - aWriter.DoubleProperty("startTimeAsQueryPerformanceCounterValue", - static_cast<double>(startTimeQPC)); + Maybe<uint64_t> startTimeQPC = aStartTime.RawQueryPerformanceCounterValue(); + if (startTimeQPC) + aWriter.DoubleProperty("startTimeAsQueryPerformanceCounterValue", + static_cast<double>(*startTimeQPC)); #endif } diff --git a/mozglue/misc/TimeStamp.h b/mozglue/misc/TimeStamp.h @@ -21,9 +21,19 @@ template <typename T> struct ParamTraits; } // namespace IPC +#ifdef XP_WIN +// defines TimeStampValue as a complex value keeping both +// GetTickCount and QueryPerformanceCounter values +# include "TimeStamp_windows.h" + +# include "mozilla/Maybe.h" // For TimeStamp::RawQueryPerformanceCounterValue +#endif + namespace mozilla { -using TimeStampValue = uint64_t; +#ifndef XP_WIN +typedef uint64_t TimeStampValue; +#endif class TimeStamp; class TimeStampTests; @@ -43,7 +53,11 @@ class BaseTimeDurationPlatformUtils { * Instances of this class represent the length of an interval of time. * Negative durations are allowed, meaning the end is before the start. * - * Internally the duration is stored as a system-dependent unit. + * Internally the duration is stored as a int64_t in units of + * PR_TicksPerSecond() when building with NSPR interval timers, or a + * system-dependent unit when building with system clocks. The + * system-dependent unit must be constant, otherwise the semantics of + * this class would be broken. * * The ValueCalculator template parameter determines how arithmetic * operations are performed on the integer count of ticks (mValue). @@ -341,8 +355,11 @@ typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration; * to a TimeStamp to get a new TimeStamp. You can't do something * meaningless like add two TimeStamps. * - * Internally this is implemented as a wrapper around high-resolution, - * monotonic, platform-dependent system clocks. + * Internally this is implemented as either a wrapper around + * - high-resolution, monotonic, system clocks if they exist on this + * platform + * - PRIntervalTime otherwise. We detect wraparounds of + * PRIntervalTime and work around them. * * This class is similar to C++11's time_point, however it is * explicitly nullable and provides an IsNull() method. time_point @@ -354,6 +371,20 @@ typedef BaseTimeDuration<TimeDurationValueCalculator> TimeDuration; * Note that, since TimeStamp objects are small, prefer to pass them by value * unless there is a specific reason not to do so. */ +#if defined(XP_WIN) +// If this static_assert fails then possibly the warning comment below is no +// longer valid and should be removed. +static_assert(sizeof(TimeStampValue) > 8); +#endif +/* + * WARNING: On Windows, each TimeStamp is represented internally by two + * different raw values (one from GTC and one from QPC) and which value gets + * used for a given operation depends on whether both operands have QPC values + * or not. This duality of values can lead to some surprising results when + * mixing TimeStamps with and without QPC values, such as comparisons being + * non-transitive (ie, a > b > c might not imply a > c). See bug 1829983 for + * more details/an example. + */ class TimeStamp { public: using DurationType = TimeDuration; @@ -402,21 +433,13 @@ class TimeStamp { * * Now() is trying to ensure the best possible precision on each platform, * at least one millisecond. - */ - static TimeStamp Now() { return Now(true); } - - /** - * Return a (coarse) timestamp reflecting the current elapsed system time. - * NowLoRes() behaves different depending on the OS: * - * Windows: NowLoRes() == Now(), uses always QueryPerformanceCounter. - * MacOS: NowLoRes() == Now(), uses always mach_absolute_time. - * Posix: If the kernel supports CLOCK_MONOTONIC_COARSE use that, - * CLOCK_MONOTONIC otherwise. - * - * Used to promise better performance, which might still be true only for - * Posix. + * NowLoRes() has been introduced to workaround performance problems of + * QueryPerformanceCounter on the Windows platform. NowLoRes() is giving + * lower precision, usually 15.6 ms, but with very good performance benefit. + * Use it for measurements of longer times, like >200ms timeouts. */ + static TimeStamp Now() { return Now(true); } static TimeStamp NowLoRes() { return Now(false); } /** @@ -458,8 +481,10 @@ class TimeStamp { #endif #ifdef XP_WIN - uint64_t RawQueryPerformanceCounterValue() const { - return static_cast<uint64_t>(mValue); + Maybe<uint64_t> RawQueryPerformanceCounterValue() const { + // mQPC is stored in `mt` i.e. QueryPerformanceCounter * 1000 + // so divide out the 1000 + return mValue.mHasQPC ? Some(mValue.mQPC / 1000ULL) : Nothing(); } #endif diff --git a/mozglue/misc/TimeStamp_darwin.cpp b/mozglue/misc/TimeStamp_darwin.cpp @@ -26,15 +26,17 @@ #include "mozilla/TimeStamp.h" #include "mozilla/Uptime.h" -// Each tick is significant, so we have a resolution of 1. -static constexpr uint64_t kResolution = 1; +// Estimate of the smallest duration of time we can measure. +static uint64_t sResolution; +static uint64_t sResolutionSigDigs; +static const uint64_t kNsPerMs = 1000000; static const uint64_t kUsPerSec = 1000000; static const double kNsPerMsd = 1000000.0; static const double kNsPerSecd = 1000000000.0; static bool gInitialized = false; -static double sNsPerTickd; +static double sNsPerTick; static uint64_t ClockTime() { // mach_absolute_time is it when it comes to ticks on the Mac. Other calls @@ -47,25 +49,53 @@ static uint64_t ClockTime() { return mach_absolute_time(); } +static uint64_t ClockResolutionNs() { + uint64_t start = ClockTime(); + uint64_t end = ClockTime(); + uint64_t minres = (end - start); + + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + for (int i = 0; i < 9; ++i) { + start = ClockTime(); + end = ClockTime(); + + uint64_t candidate = (start - end); + if (candidate < minres) { + minres = candidate; + } + } + + if (0 == minres) { + // measurable resolution is either incredibly low, ~1ns, or very + // high. fall back on NSPR's resolution assumption + minres = 1 * kNsPerMs; + } + + return minres; +} + namespace mozilla { double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) { MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); - return (aTicks * sNsPerTickd) / kNsPerSecd; + return (aTicks * sNsPerTick) / kNsPerSecd; } double BaseTimeDurationPlatformUtils::ToSecondsSigDigits(int64_t aTicks) { MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); - // As we fix the resolution to 1, all digits are significant and there are - // no extra calculations needed. Ensure we do not change this inadvertedly. - static_assert(kResolution == 1); - return ToSeconds(aTicks); + // don't report a value < mResolution ... + int64_t valueSigDigs = sResolution * (aTicks / sResolution); + // and chop off insignificant digits + valueSigDigs = sResolutionSigDigs * (valueSigDigs / sResolutionSigDigs); + return (valueSigDigs * sNsPerTick) / kNsPerSecd; } int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds( double aMilliseconds) { MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); - double result = (aMilliseconds * kNsPerMsd) / sNsPerTickd; + double result = (aMilliseconds * kNsPerMsd) / sNsPerTick; // NOTE: this MUST be a >= test, because int64_t(double(INT64_MAX)) // overflows and gives INT64_MIN. if (result >= double(INT64_MAX)) { @@ -79,7 +109,7 @@ int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds( int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() { MOZ_ASSERT(gInitialized, "calling TimeDuration too early"); - return static_cast<int64_t>(kResolution); + return static_cast<int64_t>(sResolution); } void TimeStamp::Startup() { @@ -96,7 +126,15 @@ void TimeStamp::Startup() { MOZ_RELEASE_ASSERT(false, "mach_timebase_info failed"); } - sNsPerTickd = double(timebaseInfo.numer) / timebaseInfo.denom; + sNsPerTick = double(timebaseInfo.numer) / timebaseInfo.denom; + + sResolution = ClockResolutionNs(); + + // find the number of significant digits in sResolution, for the + // sake of ToSecondsSigDigits() + for (sResolutionSigDigs = 1; !(sResolutionSigDigs == sResolution || + 10 * sResolutionSigDigs > sResolution); + sResolutionSigDigs *= 10); gInitialized = true; } @@ -108,7 +146,7 @@ TimeStamp TimeStamp::Now(bool aHighResolution) { } uint64_t TimeStamp::RawMachAbsoluteTimeNanoseconds() const { - return static_cast<uint64_t>(double(mValue) * sNsPerTickd); + return static_cast<uint64_t>(double(mValue) * sNsPerTick); } // Computes and returns the process uptime in microseconds. diff --git a/mozglue/misc/TimeStamp_windows.cpp b/mozglue/misc/TimeStamp_windows.cpp @@ -4,111 +4,548 @@ * 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/. */ +// Implement TimeStamp::Now() with QueryPerformanceCounter() controlled with +// values of GetTickCount64(). + #include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/MathAlgorithms.h" #include "mozilla/TimeStamp.h" +#include "mozilla/Uptime.h" + +#include <stdio.h> +#include <stdlib.h> #include <intrin.h> #include <windows.h> -// Historical note: We used to sample both QueryPerformanceCounter (QPC) and -// GetTickCount (GTC) timestamps in the past, as very early implementations of -// QPC were buggy. We had heuristics to determine if QPC is unreliable and -// would have switched to GTC in case, which could cause unexpected time -// travels between QPC and GPC values when that occured. +// To enable logging define to your favorite logging API +#define LOG(x) + +class AutoCriticalSection { + public: + explicit AutoCriticalSection(LPCRITICAL_SECTION aSection) + : mSection(aSection) { + ::EnterCriticalSection(mSection); + } + ~AutoCriticalSection() { ::LeaveCriticalSection(mSection); } + + private: + LPCRITICAL_SECTION mSection; +}; + +// Estimate of the smallest duration of time we can measure. +static volatile ULONGLONG sResolution; +static volatile ULONGLONG sResolutionSigDigs; +static const double kNsPerSecd = 1000000000.0; +static const LONGLONG kNsPerMillisec = 1000000; + +// ---------------------------------------------------------------------------- +// Global constants +// ---------------------------------------------------------------------------- + +// Tolerance to failures settings. // -// Since Windows 8 together with the then modern CPUs, QPC became both reliable -// and almost as fast as GTC timestamps and provides a much higher resolution. -// QPC in general exists long enough even on older systems than Windows 8, such -// that we can just always rely on it, as we do in rust. +// What is the interval we want to have failure free. +// in [ms] +static const uint32_t kFailureFreeInterval = 5000; +// How many failures we are willing to tolerate in the interval. +static const uint32_t kMaxFailuresPerInterval = 4; +// What is the threshold to treat fluctuations as actual failures. +// in [ms] +static const uint32_t kFailureThreshold = 50; + +// If we are not able to get the value of GTC time increment, use this value +// which is the most usual increment. +static const DWORD kDefaultTimeIncrement = 156001; // ---------------------------------------------------------------------------- // Global variables, not changing at runtime // ---------------------------------------------------------------------------- -// Result of QueryPerformanceFrequency, set only once on startup. -static double sTicksPerSecd; -static double sTicksPerMsd; +// Result of QueryPerformanceFrequency +// We use default of 1 for the case we can't use QueryPerformanceCounter +// to make mt/ms conversions work despite that. +static uint64_t sFrequencyPerSec = 1; + +namespace mozilla { + +MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec() { + return sFrequencyPerSec; +} + +} // namespace mozilla + +// How much we are tolerant to GTC occasional loose of resoltion. +// This number says how many multiples of the minimal GTC resolution +// detected on the system are acceptable. This number is empirical. +static const LONGLONG kGTCTickLeapTolerance = 4; + +// Base tolerance (more: "inability of detection" range) threshold is calculated +// dynamically, and kept in sGTCResolutionThreshold. +// +// Schematically, QPC worked "100%" correctly if ((GTC_now - GTC_epoch) - +// (QPC_now - QPC_epoch)) was in [-sGTCResolutionThreshold, +// sGTCResolutionThreshold] interval every time we'd compared two time stamps. +// If not, then we check the overflow behind this basic threshold +// is in kFailureThreshold. If not, we condider it as a QPC failure. If too +// many failures in short time are detected, QPC is considered faulty and +// disabled. +// +// Kept in [mt] +static LONGLONG sGTCResolutionThreshold; + +// If QPC is found faulty for two stamps in this interval, we engage +// the fault detection algorithm. For duration larger then this limit +// we bypass using durations calculated from QPC when jitter is detected, +// but don't touch the sUseQPC flag. +// +// Value is in [ms]. +static const uint32_t kHardFailureLimit = 2000; +// Conversion to [mt] +static LONGLONG sHardFailureLimit; + +// Conversion of kFailureFreeInterval and kFailureThreshold to [mt] +static LONGLONG sFailureFreeInterval; +static LONGLONG sFailureThreshold; + +// ---------------------------------------------------------------------------- +// Systemm status flags +// ---------------------------------------------------------------------------- + +// Flag for stable TSC that indicates platform where QPC is stable. +static bool sHasStableTSC = false; + +// ---------------------------------------------------------------------------- +// Global state variables, changing at runtime +// ---------------------------------------------------------------------------- + +// Initially true, set to false when QPC is found unstable and never +// returns back to true since that time. +static bool volatile sUseQPC = true; // ---------------------------------------------------------------------------- -// Useful constants +// Global lock // ---------------------------------------------------------------------------- -static constexpr double kMsPerSecd = 1000.0; -// Note: Resolution used to be sampled based on a loop of QPC calls. -// While it is true that on most systems we cannot expect to subsequently -// sample QPC values as fast as the QPC frequency, we still will get that -// as resolution of the sampled values, that is we have 1 tick resolution. -static constexpr LONGLONG kResolution = 1; +// Thread spin count before entering the full wait state for sTimeStampLock. +// Inspired by Rob Arnold's work on PRMJ_Now(). +static const DWORD kLockSpinCount = 4096; + +// Common mutex (thanks the relative complexity of the logic, this is better +// then using CMPXCHG8B.) +// It is protecting the globals bellow. +static CRITICAL_SECTION sTimeStampLock; + +// ---------------------------------------------------------------------------- +// Global lock protected variables +// ---------------------------------------------------------------------------- + +// Timestamp in future until QPC must behave correctly. +// Set to now + kFailureFreeInterval on first QPC failure detection. +// Set to now + E * kFailureFreeInterval on following errors, +// where E is number of errors detected during last kFailureFreeInterval +// milliseconds, calculated simply as: +// E = (sFaultIntoleranceCheckpoint - now) / kFailureFreeInterval + 1. +// When E > kMaxFailuresPerInterval -> disable QPC. +// +// Kept in [mt] +static ULONGLONG sFaultIntoleranceCheckpoint = 0; namespace mozilla { -// Result is in ticks. +// Result is in [mt] static inline ULONGLONG PerformanceCounter() { LARGE_INTEGER pc; - MOZ_ALWAYS_TRUE(::QueryPerformanceCounter(&pc)); - return pc.QuadPart; + ::QueryPerformanceCounter(&pc); + + // QueryPerformanceCounter may slightly jitter (not be 100% monotonic.) + // This is a simple go-backward protection for such a faulty hardware. + AutoCriticalSection lock(&sTimeStampLock); + + static decltype(LARGE_INTEGER::QuadPart) last; + if (last > pc.QuadPart) { + return last * 1000ULL; + } + last = pc.QuadPart; + return pc.QuadPart * 1000ULL; } -static void InitConstants() { - // Query the frequency from QPC and rely on it for all values. - LARGE_INTEGER freq; - bool hasQPC = ::QueryPerformanceFrequency(&freq); - MOZ_RELEASE_ASSERT(hasQPC); - sTicksPerSecd = double(freq.QuadPart); - sTicksPerMsd = sTicksPerSecd / kMsPerSecd; +static void InitThresholds() { + DWORD timeAdjustment = 0, timeIncrement = 0; + BOOL timeAdjustmentDisabled; + GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, + &timeAdjustmentDisabled); + + LOG(("TimeStamp: timeIncrement=%d [100ns]", timeIncrement)); + + if (!timeIncrement) { + timeIncrement = kDefaultTimeIncrement; + } + + // Ceiling to a millisecond + // Example values: 156001, 210000 + DWORD timeIncrementCeil = timeIncrement; + // Don't want to round up if already rounded, values will be: 156000, 209999 + timeIncrementCeil -= 1; + // Convert to ms, values will be: 15, 20 + timeIncrementCeil /= 10000; + // Round up, values will be: 16, 21 + timeIncrementCeil += 1; + // Convert back to 100ns, values will be: 160000, 210000 + timeIncrementCeil *= 10000; + + // How many milli-ticks has the interval rounded up + LONGLONG ticksPerGetTickCountResolutionCeiling = + (int64_t(timeIncrementCeil) * sFrequencyPerSec) / 10000LL; + + // GTC may jump by 32 (2*16) ms in two steps, therefor use the ceiling value. + sGTCResolutionThreshold = + LONGLONG(kGTCTickLeapTolerance * ticksPerGetTickCountResolutionCeiling); + + sHardFailureLimit = ms2mt(kHardFailureLimit); + sFailureFreeInterval = ms2mt(kFailureFreeInterval); + sFailureThreshold = ms2mt(kFailureThreshold); +} + +static void InitResolution() { + // 10 total trials is arbitrary: what we're trying to avoid by + // looping is getting unlucky and being interrupted by a context + // switch or signal, or being bitten by paging/cache effects + + ULONGLONG minres = ~0ULL; + if (sUseQPC) { + int loops = 10; + do { + ULONGLONG start = PerformanceCounter(); + ULONGLONG end = PerformanceCounter(); + + ULONGLONG candidate = (end - start); + if (candidate < minres) { + minres = candidate; + } + } while (--loops && minres); + + if (0 == minres) { + minres = 1; + } + } else { + // GetTickCount has only ~16ms known resolution + minres = ms2mt(16); + } + + // Converting minres that is in [mt] to nanosecods, multiplicating + // the argument to preserve resolution. + ULONGLONG result = mt2ms(minres * kNsPerMillisec); + if (0 == result) { + result = 1; + } + + sResolution = result; + + // find the number of significant digits in mResolution, for the + // sake of ToSecondsSigDigits() + ULONGLONG sigDigs; + for (sigDigs = 1; !(sigDigs == result || 10 * sigDigs > result); + sigDigs *= 10); + + sResolutionSigDigs = sigDigs; } // ---------------------------------------------------------------------------- +// TimeStampValue implementation +// ---------------------------------------------------------------------------- +MFBT_API TimeStampValue& TimeStampValue::operator+=(const int64_t aOther) { + mGTC += aOther; + mQPC += aOther; + return *this; +} + +MFBT_API TimeStampValue& TimeStampValue::operator-=(const int64_t aOther) { + mGTC -= aOther; + mQPC -= aOther; + return *this; +} + +// If the duration is less then two seconds, perform check of QPC stability +// by comparing both GTC and QPC calculated durations of this and aOther. +MFBT_API uint64_t TimeStampValue::CheckQPC(const TimeStampValue& aOther) const { + uint64_t deltaGTC = mGTC - aOther.mGTC; + + if (!mHasQPC || !aOther.mHasQPC) { // Both not holding QPC + return deltaGTC; + } + + uint64_t deltaQPC = mQPC - aOther.mQPC; + + if (sHasStableTSC) { // For stable TSC there is no need to check + return deltaQPC; + } + + // Check QPC is sane before using it. + int64_t diff = DeprecatedAbs(int64_t(deltaQPC) - int64_t(deltaGTC)); + if (diff <= sGTCResolutionThreshold) { + return deltaQPC; + } + + // Treat absolutely for calibration purposes + int64_t duration = DeprecatedAbs(int64_t(deltaGTC)); + int64_t overflow = diff - sGTCResolutionThreshold; + + LOG(("TimeStamp: QPC check after %llums with overflow %1.4fms", + mt2ms(duration), mt2ms_f(overflow))); + + if (overflow <= sFailureThreshold) { // We are in the limit, let go. + return deltaQPC; + } + + // QPC deviates, don't use it, since now this method may only return deltaGTC. + + if (!sUseQPC) { // QPC already disabled, no need to run the fault tolerance + // algorithm. + return deltaGTC; + } + + LOG(("TimeStamp: QPC jittered over failure threshold")); + + if (duration < sHardFailureLimit) { + // Interval between the two time stamps is very short, consider + // QPC as unstable and record a failure. + uint64_t now = ms2mt(GetTickCount64()); + + AutoCriticalSection lock(&sTimeStampLock); + + if (sFaultIntoleranceCheckpoint && sFaultIntoleranceCheckpoint > now) { + // There's already been an error in the last fault intollerant interval. + // Time since now to the checkpoint actually holds information on how many + // failures there were in the failure free interval we have defined. + uint64_t failureCount = + (sFaultIntoleranceCheckpoint - now + sFailureFreeInterval - 1) / + sFailureFreeInterval; + if (failureCount > kMaxFailuresPerInterval) { + sUseQPC = false; + LOG(("TimeStamp: QPC disabled")); + } else { + // Move the fault intolerance checkpoint more to the future, prolong it + // to reflect the number of detected failures. + ++failureCount; + sFaultIntoleranceCheckpoint = now + failureCount * sFailureFreeInterval; + LOG(("TimeStamp: recording %dth QPC failure", failureCount)); + } + } else { + // Setup fault intolerance checkpoint in the future for first detected + // error. + sFaultIntoleranceCheckpoint = now + sFailureFreeInterval; + LOG(("TimeStamp: recording 1st QPC failure")); + } + } + + return deltaGTC; +} + +MFBT_API uint64_t +TimeStampValue::operator-(const TimeStampValue& aOther) const { + if (IsNull() && aOther.IsNull()) { + return uint64_t(0); + } + + return CheckQPC(aOther); +} + +class TimeStampValueTests { + // Check that nullity is set/not set correctly. + static_assert(TimeStampValue{0}.IsNull()); + static_assert(!TimeStampValue{1}.IsNull()); + + // Check that we ignore GTC when both TimeStampValues have QPC. (In each of + // these tests, looking at GTC would give a different result.) + static_assert(TimeStampValue{1, 2, true} < TimeStampValue{1, 3, true}); + static_assert(!(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, true})); + + static_assert(TimeStampValue{2, 2, true} < TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{2, 2, true} <= TimeStampValue{1, 3, true}); + static_assert(!(TimeStampValue{2, 2, true} > TimeStampValue{1, 3, true})); + + static_assert(TimeStampValue{1, 3, true} > TimeStampValue{1, 2, true}); + static_assert(!(TimeStampValue{1, 3, true} == TimeStampValue{1, 2, true})); + + static_assert(TimeStampValue{1, 3, true} > TimeStampValue{2, 2, true}); + static_assert(TimeStampValue{1, 3, true} >= TimeStampValue{2, 2, true}); + static_assert(!(TimeStampValue{1, 3, true} < TimeStampValue{2, 2, true})); + + static_assert(TimeStampValue{1, 3, true} == TimeStampValue{2, 3, true}); + static_assert(!(TimeStampValue{1, 3, true} < TimeStampValue{2, 3, true})); + + static_assert(TimeStampValue{1, 2, true} != TimeStampValue{1, 3, true}); + static_assert(!(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, true})); + + // Check that, if either TimeStampValue doesn't have QPC, we only look at the + // GTC values. These are the same cases as above, except that we accept the + // opposite results because we turn off QPC on one or both of the + // TimeStampValue's. + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, false}); + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, false}); + + static_assert(TimeStampValue{2, 2, false} > TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{2, 2, true} > TimeStampValue{1, 3, false}); + static_assert(TimeStampValue{2, 2, false} > TimeStampValue{1, 3, false}); + + static_assert(TimeStampValue{1, 3, false} == TimeStampValue{1, 2, true}); + static_assert(TimeStampValue{1, 3, true} == TimeStampValue{1, 2, false}); + static_assert(TimeStampValue{1, 3, false} == TimeStampValue{1, 2, false}); + + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 2, true}); + static_assert(TimeStampValue{1, 3, true} < TimeStampValue{2, 2, false}); + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 2, false}); + + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 3, true}); + static_assert(TimeStampValue{1, 3, true} < TimeStampValue{2, 3, false}); + static_assert(TimeStampValue{1, 3, false} < TimeStampValue{2, 3, false}); + + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, true}); + static_assert(TimeStampValue{1, 2, true} == TimeStampValue{1, 3, false}); + static_assert(TimeStampValue{1, 2, false} == TimeStampValue{1, 3, false}); +}; + +// ---------------------------------------------------------------------------- // TimeDuration and TimeStamp implementation // ---------------------------------------------------------------------------- MFBT_API double BaseTimeDurationPlatformUtils::ToSeconds(int64_t aTicks) { - return double(aTicks) / sTicksPerSecd; + // Converting before arithmetic avoids blocked store forward + return double(aTicks) / (double(sFrequencyPerSec) * 1000.0); } MFBT_API double BaseTimeDurationPlatformUtils::ToSecondsSigDigits( int64_t aTicks) { - // As we fix the resolution to 1, all digits are significant and there are - // no extra calculations needed. Ensure we do not change this inadvertedly. - static_assert(kResolution == 1); - return ToSeconds(aTicks); + // don't report a value < mResolution ... + LONGLONG resolution = sResolution; + LONGLONG resolutionSigDigs = sResolutionSigDigs; + LONGLONG valueSigDigs = resolution * (aTicks / resolution); + // and chop off insignificant digits + valueSigDigs = resolutionSigDigs * (valueSigDigs / resolutionSigDigs); + return double(valueSigDigs) / kNsPerSecd; } MFBT_API int64_t BaseTimeDurationPlatformUtils::TicksFromMilliseconds(double aMilliseconds) { - double result = sTicksPerMsd * aMilliseconds; + double result = ms2mt(aMilliseconds); // NOTE: this MUST be a >= test, because int64_t(double(INT64_MAX)) // overflows and gives INT64_MIN. if (result >= double(INT64_MAX)) { return INT64_MAX; - } - if (result <= double(INT64_MIN)) { + } else if (result <= double(INT64_MIN)) { return INT64_MIN; } - return (int64_t)result; + return result; } MFBT_API int64_t BaseTimeDurationPlatformUtils::ResolutionInTicks() { - return static_cast<int64_t>(kResolution); + return static_cast<int64_t>(sResolution); +} + +static bool HasStableTSC() { +#if defined(_M_ARM64) + // AArch64 defines that its system counter run at a constant rate + // regardless of the current clock frequency of the system. See "The + // Generic Timer", section D7, in the ARMARM for ARMv8. + return true; +#else + union { + int regs[4]; + struct { + int nIds; + char cpuString[12]; + }; + } cpuInfo; + + __cpuid(cpuInfo.regs, 0); + // Only allow Intel or AMD CPUs for now. + // The order of the registers is reg[1], reg[3], reg[2]. We just adjust the + // string so that we can compare in one go. + if (_strnicmp(cpuInfo.cpuString, "GenuntelineI", sizeof(cpuInfo.cpuString)) && + _strnicmp(cpuInfo.cpuString, "AuthcAMDenti", sizeof(cpuInfo.cpuString))) { + return false; + } + + int regs[4]; + + // detect if the Advanced Power Management feature is supported + __cpuid(regs, 0x80000000); + if ((unsigned int)regs[0] < 0x80000007) { + // XXX should we return true here? If there is no APM there may be + // no way how TSC can run out of sync among cores. + return false; + } + + __cpuid(regs, 0x80000007); + // if bit 8 is set than TSC will run at a constant rate + // in all ACPI P-states, C-states and T-states + return regs[3] & (1 << 8); +#endif } -// Note that we init early enough during startup such that we are supposed to -// not yet have started other threads which could try to use us. static bool gInitialized = false; MFBT_API void TimeStamp::Startup() { if (gInitialized) { return; } - InitConstants(); + gInitialized = true; + + // Decide which implementation to use for the high-performance timer. + + InitializeCriticalSectionAndSpinCount(&sTimeStampLock, kLockSpinCount); + + bool forceGTC = false; + bool forceQPC = false; + + char* modevar = getenv("MOZ_TIMESTAMP_MODE"); + if (modevar) { + if (!strcmp(modevar, "QPC")) { + forceQPC = true; + } else if (!strcmp(modevar, "GTC")) { + forceGTC = true; + } + } + + LARGE_INTEGER freq; + sUseQPC = !forceGTC && ::QueryPerformanceFrequency(&freq); + if (!sUseQPC) { + // No Performance Counter. Fall back to use GetTickCount64. + InitResolution(); + + LOG(("TimeStamp: using GetTickCount64")); + return; + } + + sHasStableTSC = forceQPC || HasStableTSC(); + LOG(("TimeStamp: HasStableTSC=%d", sHasStableTSC)); + + sFrequencyPerSec = freq.QuadPart; + LOG(("TimeStamp: QPC frequency=%llu", sFrequencyPerSec)); + + InitThresholds(); + InitResolution(); + + return; } -MFBT_API void TimeStamp::Shutdown() {} +MFBT_API void TimeStamp::Shutdown() { DeleteCriticalSection(&sTimeStampLock); } + +TimeStampValue NowInternal(bool aHighResolution) { + // sUseQPC is volatile + bool useQPC = (aHighResolution && sUseQPC); + + // Both values are in [mt] units. + ULONGLONG QPC = useQPC ? PerformanceCounter() : uint64_t(0); + ULONGLONG GTC = ms2mt(GetTickCount64()); + return TimeStampValue(GTC, QPC, useQPC); +} MFBT_API TimeStamp TimeStamp::Now(bool aHighResolution) { - MOZ_ASSERT(gInitialized); - return TimeStamp((TimeStampValue)PerformanceCounter()); + return TimeStamp(NowInternal(aHighResolution)); } // Computes and returns the process uptime in microseconds. diff --git a/mozglue/misc/TimeStamp_windows.h b/mozglue/misc/TimeStamp_windows.h @@ -0,0 +1,113 @@ +/* -*- 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_TimeStamp_windows_h +#define mozilla_TimeStamp_windows_h + +#include "mozilla/Types.h" + +namespace mozilla { + +/** + * The [mt] unit: + * + * Many values are kept in ticks of the Performance Counter x 1000, + * further just referred as [mt], meaning milli-ticks. + * + * This is needed to preserve maximum precision of the performance frequency + * representation. GetTickCount64 values in milliseconds are multiplied with + * frequency per second. Therefore we need to multiply QPC value by 1000 to + * have the same units to allow simple arithmentic with both QPC and GTC. + */ +#define ms2mt(x) ((x) * mozilla::GetQueryPerformanceFrequencyPerSec()) +#define mt2ms(x) ((x) / mozilla::GetQueryPerformanceFrequencyPerSec()) +#define mt2ms_f(x) (double(x) / mozilla::GetQueryPerformanceFrequencyPerSec()) + +MFBT_API uint64_t GetQueryPerformanceFrequencyPerSec(); + +class TimeStamp; +class TimeStampValue; +class TimeStampValueTests; +class TimeStampTests; + +TimeStampValue NowInternal(bool aHighResolution); + +class TimeStampValue { + friend TimeStampValue NowInternal(bool); + friend bool IsCanonicalTimeStamp(TimeStampValue); + friend struct IPC::ParamTraits<mozilla::TimeStampValue>; + friend class TimeStamp; + friend class TimeStampValueTests; + friend class TimeStampTests; + + // Both QPC and GTC are kept in [mt] units. + uint64_t mGTC; + uint64_t mQPC; + + bool mIsNull; + bool mHasQPC; + + constexpr MFBT_API TimeStampValue(uint64_t aGTC, uint64_t aQPC, bool aHasQPC) + : mGTC(aGTC), + mQPC(aQPC), + mIsNull(aGTC == 0 && aQPC == 0), + mHasQPC(aHasQPC) {} + + // This constructor should be explicit but it is replacing a constructor that + // was MOZ_IMPLICIT and there are many locations that are using the automatic + // conversion. + constexpr MOZ_IMPLICIT MFBT_API TimeStampValue(uint64_t aGTCAndQPC) + : TimeStampValue(aGTCAndQPC, aGTCAndQPC, true) {} + + MFBT_API uint64_t CheckQPC(const TimeStampValue& aOther) const; + + public: + MFBT_API uint64_t operator-(const TimeStampValue& aOther) const; + + TimeStampValue operator+(const int64_t aOther) const { + return TimeStampValue(mGTC + aOther, mQPC + aOther, mHasQPC); + } + TimeStampValue operator-(const int64_t aOther) const { + return TimeStampValue(mGTC - aOther, mQPC - aOther, mHasQPC); + } + MFBT_API TimeStampValue& operator+=(const int64_t aOther); + MFBT_API TimeStampValue& operator-=(const int64_t aOther); + + constexpr bool operator<(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC < aOther.mQPC : mGTC < aOther.mGTC; + } + constexpr bool operator>(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC > aOther.mQPC : mGTC > aOther.mGTC; + } + constexpr bool operator<=(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC <= aOther.mQPC + : mGTC <= aOther.mGTC; + } + constexpr bool operator>=(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC >= aOther.mQPC + : mGTC >= aOther.mGTC; + } + constexpr bool operator==(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC == aOther.mQPC + : mGTC == aOther.mGTC; + } + constexpr bool operator!=(const TimeStampValue& aOther) const { + return mHasQPC && aOther.mHasQPC ? mQPC != aOther.mQPC + : mGTC != aOther.mGTC; + } + constexpr bool IsNull() const { return mIsNull; } + +#if defined(DEBUG) + uint64_t GTC() const { return mGTC; } + uint64_t QPC() const { return mQPC; } + + bool HasQPC() const { return mHasQPC; } +#endif +}; + +} // namespace mozilla + +#endif /* mozilla_TimeStamp_h */ diff --git a/mozglue/misc/moz.build b/mozglue/misc/moz.build @@ -38,6 +38,7 @@ if CONFIG["OS_ARCH"] == "WINNT": "PreXULSkeletonUI.h", "StackWalk_windows.h", "StackWalkThread.h", + "TimeStamp_windows.h", "WindowsDpiAwareness.h", ] diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp @@ -540,6 +540,15 @@ Predictor::PredictNative(nsIURI* targetURI, nsIURI* sourceURI, nsCOMPtr<nsIURI> uriKey = targetURI; nsCOMPtr<nsIURI> originKey; switch (reason) { + case nsINetworkPredictor::PREDICT_LINK: + if (!targetURI || !sourceURI) { + PREDICTOR_LOG((" link invalid URI state")); + return NS_ERROR_INVALID_ARG; + } + // Link hover is a special case where we can predict without hitting the + // db, so let's go ahead and fire off that prediction here. + PredictForLink(targetURI, sourceURI, originAttributes, verifier); + return NS_OK; case nsINetworkPredictor::PREDICT_LOAD: if (!targetURI || sourceURI) { PREDICTOR_LOG((" load invalid URI state")); @@ -652,6 +661,35 @@ bool Predictor::PredictInternal(PredictorPredictReason reason, return rv; } +void Predictor::PredictForLink(nsIURI* targetURI, nsIURI* sourceURI, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier) { + MOZ_ASSERT(NS_IsMainThread()); + + PREDICTOR_LOG(("Predictor::PredictForLink")); + if (!mSpeculativeService) { + PREDICTOR_LOG((" missing speculative service")); + return; + } + + if (!StaticPrefs::network_predictor_enable_hover_on_ssl()) { + if (sourceURI->SchemeIs("https")) { + // We don't want to predict from an HTTPS page, to avoid info leakage + PREDICTOR_LOG((" Not predicting for link hover - on an SSL page")); + return; + } + } + + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateContentPrincipal(targetURI, originAttributes); + + mSpeculativeService->SpeculativeConnect(targetURI, principal, nullptr, false); + if (verifier) { + PREDICTOR_LOG((" sending verification")); + verifier->OnPredictPreconnect(targetURI); + } +} + // This is the driver for prediction based on a new pageload. static const uint8_t MAX_PAGELOAD_DEPTH = 10; bool Predictor::PredictForPageload(nsICacheEntry* entry, nsIURI* targetURI, diff --git a/netwerk/base/Predictor.h b/netwerk/base/Predictor.h @@ -243,6 +243,15 @@ class Predictor final : public nsINetworkPredictor, nsINetworkPredictorVerifier* verifier, uint8_t stackCount); + // Used when predicting because the user's mouse hovered over a link + // * targetURI - the URI target of the link + // * sourceURI - the URI of the page on which the link appears + // * originAttributes - the originAttributes for this prediction + // * verifier - used for testing to verify the expected predictions happen + void PredictForLink(nsIURI* targetURI, nsIURI* sourceURI, + const OriginAttributes& originAttributes, + nsINetworkPredictorVerifier* verifier); + // Used when predicting because a page is being loaded (which may include // being the target of a redirect). All arguments are the same as for // PredictInternal. Returns true if any predictions were queued up. diff --git a/netwerk/base/nsINetworkPredictor.idl b/netwerk/base/nsINetworkPredictor.idl @@ -35,12 +35,16 @@ interface nsINetworkPredictor : nsISupports /** * Prediction reasons * + * PREDICT_LINK - we are being asked to take predictive action because + * the user is hovering over a link. + * * PREDICT_LOAD - we are being asked to take predictive action because * the user has initiated a pageload. * * PREDICT_STARTUP - we are being asked to take predictive action * because the browser is starting up. */ + const PredictorPredictReason PREDICT_LINK = 0; const PredictorPredictReason PREDICT_LOAD = 1; const PredictorPredictReason PREDICT_STARTUP = 2; @@ -58,6 +62,9 @@ interface nsINetworkPredictor : nsISupports * example). * @param reason - The reason we are being asked to take actions. Can be * any of the PREDICT_* values above. + * In the case of PREDICT_LINK, targetURI should be the URI of the link + * that is being hovered over, and sourceURI should be the URI of the page + * on which the link appears. * In the case of PREDICT_LOAD, targetURI should be the URI of the page that * is being loaded and sourceURI should be null. * In the case of PREDICT_STARTUP, both targetURI and sourceURI should be diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -394,18 +394,10 @@ nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps, } } - RefPtr<mozilla::dom::BrowsingContext> browsingContext; - mLoadInfo->GetBrowsingContext(getter_AddRefs(browsingContext)); - - const nsCString& languageOverride = - browsingContext ? browsingContext->Top()->GetLanguageOverride() - : EmptyCString(); - rv = gHttpHandler->AddStandardRequestHeaders( &mRequestHead, aURI, isHTTPS, contentPolicyType, nsContentUtils::ShouldResistFingerprinting(this, - RFPTarget::HttpUserAgent), - languageOverride); + RFPTarget::HttpUserAgent)); if (NS_FAILED(rv)) return rv; nsAutoCString type; diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp @@ -733,8 +733,7 @@ nsresult nsHttpHandler::AddAcceptAndDictionaryHeaders( nsresult nsHttpHandler::AddStandardRequestHeaders( nsHttpRequestHead* request, nsIURI* aURI, bool aIsHTTPS, - ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting, - const nsCString& aLanguageOverride) { + ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting) { nsresult rv; // Add the "User-Agent" header @@ -766,26 +765,18 @@ nsresult nsHttpHandler::AddStandardRequestHeaders( nsHttpHeaderArray::eVarietyRequestOverride); if (NS_FAILED(rv)) return rv; - if (!aLanguageOverride.IsEmpty()) { - nsAutoCString acceptLanguage; - acceptLanguage.Assign(aLanguageOverride.get()); - rv = request->SetHeader(nsHttp::Accept_Language, acceptLanguage, false, + // Add the "Accept-Language" header. This header is also exposed to the + // service worker. + if (mAcceptLanguagesIsDirty) { + rv = SetAcceptLanguages(); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + // Add the "Accept-Language" header + if (!mAcceptLanguages.IsEmpty()) { + rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages, false, nsHttpHeaderArray::eVarietyRequestOverride); if (NS_FAILED(rv)) return rv; - } else { - // Add the "Accept-Language" header. This header is also exposed to the - // service worker. - if (mAcceptLanguagesIsDirty) { - rv = SetAcceptLanguages(); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - } - - // Add the "Accept-Language" header - if (!mAcceptLanguages.IsEmpty()) { - rv = request->SetHeader(nsHttp::Accept_Language, mAcceptLanguages, false, - nsHttpHeaderArray::eVarietyRequestOverride); - if (NS_FAILED(rv)) return rv; - } } // add the "Send Hint" header diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h @@ -123,8 +123,8 @@ class nsHttpHandler final : public nsIHttpProtocolHandler, const std::function<bool(bool, DictionaryCacheEntry*)>& aCallback); [[nodiscard]] nsresult AddStandardRequestHeaders( nsHttpRequestHead*, nsIURI* aURI, bool aIsHTTPS, - ExtContentPolicyType aContentPolicyType, bool aShouldResistFingerprinting, - const nsCString& aLanguageOverride); + ExtContentPolicyType aContentPolicyType, + bool aShouldResistFingerprinting); [[nodiscard]] nsresult AddConnectionHeader(nsHttpRequestHead*, uint32_t caps); bool IsAcceptableEncoding(const char* encoding, bool isSecure); diff --git a/netwerk/test/browser/browser.toml b/netwerk/test/browser/browser.toml @@ -13,7 +13,6 @@ support-files = [ "early_hint_asset_html.sjs", "early_hint_csp_options_html.sjs", "early_hint_preconnect_html.sjs", - "request_accept_language.sjs", "file_channel.html", "post.html", "res.css", @@ -151,8 +150,6 @@ support-files = ["early_hint_preload_test_helper.sys.mjs",] ["browser_about_cache.js"] skip-if = ["os == 'linux' && os_version == '24.04' && processor == 'x86_64' && display == 'x11'"] # Bug 1970244 -["browser_accept_language_override.js"] - ["browser_backgroundtask_purgeHTTPCache.js"] ["browser_bug968273.js"] @@ -194,9 +191,6 @@ support-files = ["file_lnk.lnk",] ["browser_ipAddressSpace_mainpage_unaffected.js"] -["browser_link_hover_speculative_connection.js"] -support-files = ["file_link_hover.sjs"] - ["browser_mock_https_rr.js"] skip-if = [ "http3", diff --git a/netwerk/test/browser/browser_accept_language_override.js b/netwerk/test/browser/browser_accept_language_override.js @@ -1,152 +0,0 @@ -/* 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"; - -const PAGE_URL = "https://example.com/browser/netwerk/test/browser/dummy.html"; -const SCRIPT_URL = - "https://example.com/browser/netwerk/test/browser/request_accept_language.sjs"; -const languages = ["en-US", "es-ES", "fr-FR"]; - -add_task(async function test_set_language_override() { - const tab = BrowserTestUtils.addTab(gBrowser, PAGE_URL); - const browser = gBrowser.getBrowserForTab(tab); - - await BrowserTestUtils.browserLoaded(browser); - - info("Get default language"); - const defaultLanguage = await getAcceptLanguageHeader(browser); - const browsingContext = browser.browsingContext; - const languageOverride = getLanguageToOverride(defaultLanguage); - - info("Set language override"); - browser.browsingContext.languageOverride = languageOverride; - is( - await getAcceptLanguageHeader(browser), - languageOverride, - '"Accept-Language" header is overridden' - ); - - const secondLanguageOverride = getSecondLanguageToOverride( - defaultLanguage, - languageOverride - ); - - info("Set language override again"); - browsingContext.languageOverride = secondLanguageOverride; - is( - await getAcceptLanguageHeader(browser), - secondLanguageOverride, - '"Accept-Language" header is overridden' - ); - - info("Reset language override"); - browsingContext.languageOverride = ""; - is( - await getAcceptLanguageHeader(browser), - defaultLanguage, - '"Accept-Language" header is not overridden' - ); - - BrowserTestUtils.removeTab(tab); -}); - -add_task(async function test_set_language_override_and_navigate() { - const tab = BrowserTestUtils.addTab(gBrowser, PAGE_URL); - const browser = gBrowser.getBrowserForTab(tab); - - await BrowserTestUtils.browserLoaded(browser); - - info("Get default language"); - const defaultLanguage = await getAcceptLanguageHeader(browser); - const browsingContext = browser.browsingContext; - const languageOverride = getLanguageToOverride(defaultLanguage); - - info("Set language override"); - browsingContext.languageOverride = languageOverride; - is( - await getAcceptLanguageHeader(browser), - languageOverride, - '"Accept-Language" header is overridden' - ); - - info("Navigate browsing context"); - const url = "https://example.com/chrome/dom/base/test/dummy.html"; - const loaded = BrowserTestUtils.browserLoaded(browser, false, url, false); - BrowserTestUtils.startLoadingURIString(browser, url); - await loaded; - is( - await getAcceptLanguageHeader(browser), - languageOverride, - '"Accept-Language" header is overridden' - ); - - BrowserTestUtils.removeTab(tab); -}); - -add_task(async function test_set_language_override_in_different_contexts() { - const tab1 = BrowserTestUtils.addTab(gBrowser, PAGE_URL); - const browser1 = gBrowser.getBrowserForTab(tab1); - - await BrowserTestUtils.browserLoaded(browser1); - - const tab2 = BrowserTestUtils.addTab(gBrowser, PAGE_URL); - const browser2 = gBrowser.getBrowserForTab(tab2); - - await BrowserTestUtils.browserLoaded(browser2); - - info("Get default language in the first tab"); - const defaultLanguage1 = await getAcceptLanguageHeader(browser1); - - info("Get default language in the second tab"); - const defaultLanguage2 = await getAcceptLanguageHeader(browser2); - - const browsingContext1 = browser1.browsingContext; - const languageOverride = getLanguageToOverride(defaultLanguage1); - - info("Set language override to the first tab"); - browsingContext1.languageOverride = languageOverride; - is( - await getAcceptLanguageHeader(browser1), - languageOverride, - '"Accept-Language" header is overridden' - ); - - info("Make sure that in the second tab language is not overridden"); - browsingContext1.languageOverride = languageOverride; - is( - await getAcceptLanguageHeader(browser2), - defaultLanguage2, - '"Accept-Language" header is not overridden' - ); - - info("Reset language override"); - browsingContext1.languageOverride = ""; - is( - await getAcceptLanguageHeader(browser1), - defaultLanguage1, - '"Accept-Language" header is not overridden' - ); - - BrowserTestUtils.removeTab(tab1); - BrowserTestUtils.removeTab(tab2); -}); - -function getLanguageToOverride(defaultLanguage) { - return languages.find(lang => lang !== defaultLanguage); -} - -function getSecondLanguageToOverride(defaultLanguage, secondLanguage) { - return languages.find( - lang => lang !== defaultLanguage && lang !== secondLanguage - ); -} - -async function getAcceptLanguageHeader(browser) { - return SpecialPowers.spawn(browser, [SCRIPT_URL], async url => { - const response = await content.fetch(url); - return response.json(); - }); -} diff --git a/netwerk/test/browser/browser_link_hover_speculative_connection.js b/netwerk/test/browser/browser_link_hover_speculative_connection.js @@ -1,362 +0,0 @@ -/* 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"; - -const { HttpServer } = ChromeUtils.importESModule( - "resource://testing-common/httpd.sys.mjs" -); - -const TEST_PATH = "/browser/netwerk/test/browser/"; - -let gServer; -let gServerURL; -let gConnectionNumberWhenRequested = 0; - -add_setup(async function () { - await SpecialPowers.pushPrefEnv({ - set: [ - // Enable speculative connections for hover on HTTPS - ["network.predictor.enable-hover-on-ssl", true], - // Enable network debugging observations - ["network.http.debug-observations", true], - ], - }); - - // Set up local HTTP server for the target page - gServer = new HttpServer(); - gServer.start(-1); - gServerURL = `http://localhost:${gServer.identity.primaryPort}`; - - // Register handler for the target page - gServer.registerPathHandler("/target.html", handleTarget); - - registerCleanupFunction(async () => { - await gServer.stop(); - }); -}); - -function handleTarget(request, response) { - response.setHeader("Content-Type", "text/html", false); - response.setHeader("Cache-Control", "no-cache", false); - - // Record which connection number handled this request - gConnectionNumberWhenRequested = response._connection.number; - - let body = ` - <!DOCTYPE html> - <html> - <head> - <meta charset="UTF-8"> - <title>Target Page</title> - </head> - <body> - <h1>Target Page</h1> - <p>Connection: ${gConnectionNumberWhenRequested}</p> - </body> - </html> - `; - - response.write(body); -} - -/** - * Helper function to simulate hovering over a link element - */ -function hoverOverLink(browser, linkId) { - return SpecialPowers.spawn(browser, [linkId], async id => { - let link = content.document.getElementById(id); - // Dispatch mouseover event which should trigger the speculative connection - let event = new content.MouseEvent("mouseover", { - bubbles: true, - cancelable: true, - view: content, - }); - link.dispatchEvent(event); - }); -} - -/** - * Helper function to click a link and wait for navigation - */ -async function clickLink(browser, linkId) { - let loadedPromise = BrowserTestUtils.browserLoaded(browser); - - await SpecialPowers.spawn(browser, [linkId], async id => { - let link = content.document.getElementById(id); - link.click(); - }); - - await loadedPromise; -} - -add_task(async function test_link_hover_https_page() { - let speculativeConnectObserved = false; - let observer = { - QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), - observe(aSubject, aTopic, aData) { - if ( - aTopic == "speculative-connect-request" && - aData.includes("localhost") - ) { - info("Observed speculative connection request for: " + aData); - speculativeConnectObserved = true; - } - }, - }; - Services.obs.addObserver(observer, "speculative-connect-request"); - - // Load the test page from HTTPS example.com with the target URL pointing to our local server - const targetURL = encodeURIComponent(gServerURL + "/target.html"); - const pageURL = - "https://example.com" + - TEST_PATH + - "file_link_hover.sjs?target=" + - targetURL; - - await BrowserTestUtils.withNewTab( - { - gBrowser, - url: pageURL, - waitForLoad: true, - }, - async function (browser) { - // Record the current connection count before hovering - let connectionCountBeforeHover = gServer.connectionNumber; - info("Connection count before hover: " + connectionCountBeforeHover); - - // Wait for the link element to be available in the page - await SpecialPowers.spawn(browser, [], async () => { - await ContentTaskUtils.waitForCondition( - () => content.document.getElementById("testLink"), - "Waiting for link element to be available" - ); - }); - - // Hover over the link to trigger speculative connection - await hoverOverLink(browser, "testLink"); - - // Wait for the speculative connection to be fully established - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Check connection count after hover - let connectionCountAfterHover = gServer.connectionNumber; - info("Connection count after hover: " + connectionCountAfterHover); - - // Verify that a speculative connection request was observed - Assert.ok( - speculativeConnectObserved, - "Speculative connection should be triggered on link hover" - ); - - // Now click the link - it should use the speculative connection - await clickLink(browser, "testLink"); - - // Check connection count after click - let connectionCountAfterClick = gServer.connectionNumber; - info("Connection count after click: " + connectionCountAfterClick); - info( - "Connection number that handled the request: " + - gConnectionNumberWhenRequested - ); - - // Verify that exactly one NEW connection was established - Assert.equal( - connectionCountAfterClick, - connectionCountBeforeHover + 1, - "Exactly one connection should be established for the HTTP request" - ); - - // Verify that the request was handled by the new connection - Assert.equal( - gConnectionNumberWhenRequested, - connectionCountBeforeHover + 1, - "The HTTP request should be handled by the new connection" - ); - } - ); - - Services.obs.removeObserver(observer, "speculative-connect-request"); -}); - -add_task(async function test_link_hover_http_page() { - let speculativeConnectObserved = false; - let observer = { - QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), - observe(aSubject, aTopic, aData) { - if ( - aTopic == "speculative-connect-request" && - aData.includes("localhost") - ) { - info("Observed speculative connection request for: " + aData); - speculativeConnectObserved = true; - } - }, - }; - Services.obs.addObserver(observer, "speculative-connect-request"); - - // Load the test page from HTTP example.com with the target URL pointing to our local server - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - const targetURL = encodeURIComponent(gServerURL + "/target.html"); - const pageURL = - // eslint-disable-next-line @microsoft/sdl/no-insecure-url - "http://example.com" + - TEST_PATH + - "file_link_hover.sjs?target=" + - targetURL; - - await BrowserTestUtils.withNewTab( - { - gBrowser, - url: pageURL, - waitForLoad: true, - }, - async function (browser) { - // Record the current connection count before hovering - // This should be 0 since we haven't connected to our server yet - let connectionCountBeforeHover = gServer.connectionNumber; - info("Connection count before hover: " + connectionCountBeforeHover); - - // Wait for the link element to be available in the page - await SpecialPowers.spawn(browser, [], async () => { - await ContentTaskUtils.waitForCondition( - () => content.document.getElementById("testLink"), - "Waiting for link element to be available" - ); - }); - - // Hover over the link to trigger speculative connection - await hoverOverLink(browser, "testLink"); - - // Wait for the speculative connection to be fully established - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Check connection count after hover - let connectionCountAfterHover = gServer.connectionNumber; - info("Connection count after hover: " + connectionCountAfterHover); - - // Verify that a speculative connection request was observed - Assert.ok( - speculativeConnectObserved, - "Speculative connection should be triggered on link hover from HTTP page" - ); - - // Now click the link - it should use the speculative connection - await clickLink(browser, "testLink"); - - // Check connection count after click - let connectionCountAfterClick = gServer.connectionNumber; - info("Connection count after click: " + connectionCountAfterClick); - info( - "Connection number that handled the request: " + - gConnectionNumberWhenRequested - ); - - // Verify that exactly one NEW connection was established - Assert.equal( - connectionCountAfterClick, - connectionCountBeforeHover + 1, - "Exactly one connection should be established for the HTTP request" - ); - - // Verify that the request was handled by the new connection - Assert.equal( - gConnectionNumberWhenRequested, - connectionCountBeforeHover + 1, - "The HTTP request should be handled by the new connection" - ); - } - ); - - Services.obs.removeObserver(observer, "speculative-connect-request"); -}); - -add_task(async function test_link_hover_https_page_pref_disabled() { - // Disable the pref for speculative connections on HTTPS - await SpecialPowers.pushPrefEnv({ - set: [["network.predictor.enable-hover-on-ssl", false]], - }); - - let speculativeConnectObserved = false; - let observer = { - QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), - observe(aSubject, aTopic, aData) { - if ( - aTopic == "speculative-connect-request" && - aData.includes("localhost") - ) { - info("Observed speculative connection request for: " + aData); - speculativeConnectObserved = true; - } - }, - }; - Services.obs.addObserver(observer, "speculative-connect-request"); - - // Load the test page from HTTPS example.com with the target URL pointing to our local server - const targetURL = encodeURIComponent(gServerURL + "/target.html"); - const pageURL = - "https://example.com" + - TEST_PATH + - "file_link_hover.sjs?target=" + - targetURL; - - await BrowserTestUtils.withNewTab( - { - gBrowser, - url: pageURL, - waitForLoad: true, - }, - async function (browser) { - // Record the current connection count before hovering - let connectionCountBeforeHover = gServer.connectionNumber; - info("Connection count before hover: " + connectionCountBeforeHover); - - // Wait for the link element to be available in the page - await SpecialPowers.spawn(browser, [], async () => { - await ContentTaskUtils.waitForCondition( - () => content.document.getElementById("testLink"), - "Waiting for link element to be available" - ); - }); - - // Hover over the link - await hoverOverLink(browser, "testLink"); - - // Wait a bit to see if any speculative connection would be triggered - // eslint-disable-next-line mozilla/no-arbitrary-setTimeout - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Check connection count after hover - let connectionCountAfterHover = gServer.connectionNumber; - info("Connection count after hover: " + connectionCountAfterHover); - - // Verify that NO speculative connection request was observed - Assert.ok( - !speculativeConnectObserved, - "Speculative connection should NOT be triggered on link hover from HTTPS page when pref is disabled" - ); - - // Now click the link - it will create a new connection - await clickLink(browser, "testLink"); - - // Check connection count after click - let connectionCountAfterClick = gServer.connectionNumber; - info("Connection count after click: " + connectionCountAfterClick); - - // Verify that exactly one NEW connection was created (by the click, not hover) - Assert.equal( - connectionCountAfterClick, - connectionCountBeforeHover + 1, - "Exactly one connection should be established (from the click, not hover)" - ); - } - ); - - Services.obs.removeObserver(observer, "speculative-connect-request"); - - // Restore the pref - await SpecialPowers.popPrefEnv(); -}); diff --git a/netwerk/test/browser/file_link_hover.sjs b/netwerk/test/browser/file_link_hover.sjs @@ -1,28 +0,0 @@ -/* 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"; - -function handleRequest(request, response) { - response.setHeader("Content-Type", "text/html", false); - response.setHeader("Cache-Control", "no-cache", false); - - // Get the target URL from the query string - const params = new URLSearchParams(request.queryString); - const targetURL = params.get("target") || "about:blank"; - - const html = `<!DOCTYPE html> -<html> -<head> - <meta charset="UTF-8"> - <title>Link Hover Test</title> -</head> -<body> - <h1>Link Hover Speculative Connection Test</h1> - <a id="testLink" href="${targetURL}">Test Link</a> -</body> -</html>`; - - response.write(html); -} diff --git a/netwerk/test/browser/request_accept_language.sjs b/netwerk/test/browser/request_accept_language.sjs @@ -1,7 +0,0 @@ -"use strict"; - -function handleRequest(request, response) { - const headers = request.getHeader("Accept-Language"); - response.setHeader("Content-Type", "text/plain", false); - response.write(JSON.stringify(headers)); -} diff --git a/netwerk/test/unit/test_predictor.js b/netwerk/test/unit/test_predictor.js @@ -184,6 +184,28 @@ function open_and_continue(uris, continueCallback) { } } +function test_link_hover() { + if (!running_single_process && !is_child_process()) { + // This one we can just proxy to the child and be done with, no extra setup + // is necessary. + sendCommand("test_link_hover();"); + return; + } + + var uri = newURI("http://localhost:4444/foo/bar"); + var referrer = newURI("http://localhost:4444/foo"); + var preconns = ["http://localhost:4444"]; + + var verifier = new Verifier("hover", [], preconns, []); + predictor.predict( + uri, + referrer, + predictor.PREDICT_LINK, + origin_attributes, + verifier + ); +} + const pageload_toplevel = newURI("http://localhost:4444/index.html"); function continue_test_pageload() { @@ -743,6 +765,7 @@ function cleanup() { var tests = [ // This must ALWAYS come first, to ensure a clean slate reset_predictor, + test_link_hover, test_pageload, // TODO: These are disabled until the features are re-written //test_redirect, diff --git a/python/mozbuild/mozbuild/repackaging/rpm.py b/python/mozbuild/mozbuild/repackaging/rpm.py @@ -93,7 +93,7 @@ def repackage_rpm( ) as f: f.write("This is a packaged app.\n") - inject_distribution_folder(rpm_dir, "", app_name) + inject_distribution_folder(source_dir, "rpm", app_name) inject_desktop_entry_file( log, rpm_dir, diff --git a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp @@ -1083,12 +1083,6 @@ void SandboxBroker::SetSecurityLevelForContentProcess(int32_t aSandboxLevel, config->SetDesktop(sandbox::Desktop::kAlternateWinstation); } - if (StaticPrefs::security_sandbox_content_close_ksecdd_handle()) { - result = config->AddKernelObjectToClose(L"File", L"\\Device\\KsecDD"); - MOZ_RELEASE_ASSERT(sandbox::SBOX_ALL_OK == result, - "AddKernelObjectToClose should never fail."); - } - sandbox::MitigationFlags mitigations = sandbox::MITIGATION_BOTTOM_UP_ASLR | sandbox::MITIGATION_HEAP_TERMINATE | sandbox::MITIGATION_SEHOP | sandbox::MITIGATION_DEP_NO_ATL_THUNK | diff --git a/testing/gtest/mozilla/WaitFor.h b/testing/gtest/mozilla/WaitFor.h @@ -192,30 +192,6 @@ inline auto TakeN(MediaEventSourceImpl<Lp, Args...>& aEvent, size_t aN) return holder->Ensure(__func__); } -using TakeNVoidPromise = MozPromise<size_t, bool, true>; - -template <ListenerPolicy Lp> -inline auto TakeN(MediaEventSourceImpl<Lp, void>& aEvent, size_t aN) - -> RefPtr<TakeNVoidPromise> { - using Storage = Maybe<size_t>; - using Promise = TakeNVoidPromise; - using Holder = media::Refcountable<MozPromiseHolder<Promise>>; - using Values = media::Refcountable<Storage>; - using Listener = media::Refcountable<MediaEventListener>; - auto values = MakeRefPtr<Values>(); - *values = Some(0); - auto listener = MakeRefPtr<Listener>(); - auto holder = MakeRefPtr<Holder>(); - *listener = aEvent.Connect( - AbstractThread::GetCurrent(), [values, listener, aN, holder]() { - if (++(values->ref()) == aN) { - listener->Disconnect(); - holder->Resolve(**values, "TakeN (void) listener callback"); - } - }); - return holder->Ensure(__func__); -} - /** * Helper that, given that canonicals have just been updated on the current * thread, will block its execution until mirrors and their watchers have diff --git a/testing/web-platform/mozilla/meta/mathml/mathml-console-messages.html.ini b/testing/web-platform/mozilla/meta/mathml/mathml-console-messages.html.ini @@ -1,2 +1,2 @@ [mathml-console-messages.html] - prefs: [dom.use_components_shim:false, mathml.legacy_mathvariant_attribute.disabled: false, mathml.mathspace_names.disabled: false, mathml.operator_dictionary_accent.disabled: false] + prefs: [dom.use_components_shim:false, mathml.legacy_mathvariant_attribute.disabled: false, mathml.mathspace_names.disabled: false] diff --git a/testing/web-platform/mozilla/meta/mediacapture-streams/MediaStreamTrack-independent-clones.https.html.ini b/testing/web-platform/mozilla/meta/mediacapture-streams/MediaStreamTrack-independent-clones.https.html.ini @@ -1,32 +0,0 @@ -[MediaStreamTrack-independent-clones.https.html] - [gDM gets expected modes by {"resizeMode":"none","width":100} + (cloned) {"resizeMode":"none","height":500}] - expected: - if os == "android": FAIL - [gDM gets expected modes by {"resizeMode":"none","frameRate":50} + (cloned) {"resizeMode":"none","frameRate":1}] - expected: - if os == "android": FAIL - [gDM gets expected modes by {"resizeMode":"none"} + (cloned) {"resizeMode":"crop-and-scale"}] - expected: - if os == "android": FAIL - [gDM gets expected modes by {"resizeMode":"crop-and-scale"} + (cloned) {"resizeMode":"crop-and-scale","height":100}] - expected: - if os == "android": FAIL - [gDM gets expected modes by {"resizeMode":"crop-and-scale","height":100} + (cloned) {"resizeMode":"crop-and-scale"}] - expected: - if os == "android": FAIL - [gDM gets expected modes by {"resizeMode":"crop-and-scale","frameRate":5} + (cloned) {"resizeMode":"crop-and-scale","frameRate":50}] - expected: - if os == "android": FAIL - [gDM gets expected modes by {"resizeMode":"crop-and-scale","frameRate":50} + (cloned) {"resizeMode":"crop-and-scale","frameRate":5}] - expected: - if os == "android": FAIL - [gDM gets expected modes by {"resizeMode":"none"} + (cloned) {"resizeMode":"crop-and-scale","frameRate":5000}] - expected: - if os == "android": FAIL - [applyConstraints on gDM clone doesn't crop with only ideal dimensions] - expected: - if os == "android": FAIL - [applyConstraints on gDM clone doesn't crop with ideal and max dimensions] - expected: - if os == "android": FAIL - prefs: [media.getusermedia.camera.fake.force:true, media.navigator.streams.fake:false, media.cubeb.force_mock_context:true] diff --git a/testing/web-platform/mozilla/meta/screen-capture/MediaStreamTrack-independent-clones.https.html.ini b/testing/web-platform/mozilla/meta/screen-capture/MediaStreamTrack-independent-clones.https.html.ini @@ -1,3 +0,0 @@ -[MediaStreamTrack-independent-clones.https.html] - expected: - if os == "android": ERROR diff --git a/testing/web-platform/mozilla/tests/mathml/mathml-console-messages.html b/testing/web-platform/mozilla/tests/mathml/mathml-console-messages.html @@ -39,14 +39,16 @@ function testMessageForMarkup(markup, regexp, level) { promise_test(async function() { let messages = await retrieveConsoleMessagesFor(markup); + + // Sometimes MathML messages are logged several times, so just + // ensure there is at least one. assert_greater_than_equal(messages.length, 1); - // Try to match any element in messages against the provided regexp. - let found = messages.find(m => regexp.test(m.errorMessage)); - assert_true(!!found, `regexp ${regexp} not found in array ${messages.map(m => m.errorMessage)}`); + // Compare against the regexp. + assert_regexp_match(messages[0].errorMessage, regexp); // Check whether this is a warning or an error. - assert_equals(found.isWarning, level == MessageLevel.WARNING); + assert_equals(messages[0].isWarning, level == MessageLevel.WARNING); }, `Message for ${markup}`); } @@ -239,49 +241,5 @@ new RegExp(`mathvariant='${value}'” .* deprecated`), MessageLevel.WARNING); }); - - - // MathML_DeprecatedMoExplicitAccent - testNoMessageForMarkup(`<math><mrow><mo accent="true">x</mo></mrow></math>`); - [ - ["mover", "accent", `<mover><mi>A</mi><mo accent="true">x</mo></mover>`], - ["munder", "accentunder", `<munder><mi>A</mi><mo accent="true">x</mo></munder>`], - ["munderover", "accent/accentunder", `<munderover><mi>A</mi><mo accent="true">x</mo><mo>y</mo></munderover>`], - ["munderover", "accent/accentunder", `<munderover><mi>A</mi><mo>x</mo><mo accent="true">y</mo></munderover>`], - ["munderover", "accent/accentunder", `<munderover><mi>A</mi><mo accent="true">x</mo><mo accent="true">y</mo></munderover>`], - ].forEach(([name, accent, markup]) => { - testMessageForMarkup( - `<math><${markup}></math>`, - new RegExp(`Setting the accent attribute on mo elements is deprecated. Use the “${accent}” attribute on “${name}” instead.`), - MessageLevel.WARNING); - }); - - - // MathML_DeprecatedMoverNonExplicitAccent - // MathML_DeprecatedMunderNonExplicitAccentunder - [ "mover", "munder" ].forEach((name) => { - testNoMessageForMarkup(`<math><${name}><mi>A</mi><mo>x</mo></${name}></math>`); - }); - testNoMessageForMarkup(`<math><munderover><mi>A</mi><mo>x</mo><mo>y</mo></munderover></math>`); - [ - // Inferring the accent from the operator dictionary. - ["mover", "accent", `<mover><mi>B</mi><mo>^</mo></mover>`], - ["munder", "accentunder", `<munder><mi>B</mi><mo>^</mo></munder>`], - ["munderover", "accent", `<munderover><mi>B</mi><mo>x</mo><mo>^</mo></munderover>`], - ["munderover", "accentunder", `<munderover><mi>B</mi><mo>^</mo><mo>x</mo></munderover>`], - // Inferring the accent from `accent="true"`. - // The last test triggers both accent and accentunder warnings, so we check it twice. - ["mover", "accent", `<mover><mi>C</mi><mo accent="true">x</mo></mover>`], - ["munder", "accentunder", `<munder><mi>C</mi><mo accent="true">x</mo></munder>`], - ["munderover", "accent", `<munderover><mi>C</mi><mo>x</mo><mo accent="true">y</mo></munderover>`], - ["munderover", "accentunder", `<munderover><mi>C</mi><mo accent="true">x</mo><mo>y</mo></munderover>`], - ["munderover", "accent", `<munderover><mi>C</mi><mo accent="true">x</mo><mo accent="true">y</mo></munderover>`], - ["munderover", "accentunder", `<munderover><mi>D</mi><mo accent="true">x</mo><mo accent="true">y</mo></munderover>`], - ].forEach(([name, accent, markup]) => { - testMessageForMarkup( - `<math><${markup}></math>`, - new RegExp(`Inferring the ${accent} property from the core operator is deprecated. Consider adding an explicit ${accent} attribute to “${name}”.`), - MessageLevel.WARNING); - }); </script> </body> diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/MediaStreamTrack-independent-clones.https.html b/testing/web-platform/mozilla/tests/mediacapture-streams/MediaStreamTrack-independent-clones.https.html @@ -1,209 +0,0 @@ -<!doctype html> -<title>MediaStreamTrack clones have independent settings. Assumes Mozilla's fake camera source with 480p and 720p capabilities.</title> -<meta name="timeout" content="long"> -<p class="instructions">When prompted, accept to share your video stream.</p> -<script src=/resources/testharness.js></script> -<script src=/resources/testharnessreport.js></script> -<script src=/resources/testdriver.js></script> -<script src=/resources/testdriver-vendor.js></script> -<script src=video-test-helper.js></script> -<script> - "use strict" - - // Native capabilities supported by the fake camera. - const nativeLow = {width: 640, height: 480, frameRate: 30, resizeMode: "none"}; - const nativeHigh = {width: 1280, height: 720, frameRate: 10, resizeMode: "none"}; - - [ - [ - [{resizeMode: "none", width: 1000}, {resizeMode: "none", width: 500}], - [nativeHigh, nativeLow], - ], - [ - [{resizeMode: "none", height: 500}, {resizeMode: "none", width: 1000}], - [nativeLow, nativeHigh], - ], - [ - [{resizeMode: "none", width: 500, height: 500}, {resizeMode: "crop-and-scale", width: 1000, height: 750}], - [nativeLow, {resizeMode: "crop-and-scale", width: 1000, height: 720, frameRate: 10}], - ], - [ - [{resizeMode: "crop-and-scale", width: 800, height: 600}, {resizeMode: "none"}], - [{resizeMode: "crop-and-scale", width: 800, height: 600, frameRate: 10}, nativeLow], - ], - [ - [{resizeMode: "crop-and-scale", height: 500}, {resizeMode: "crop-and-scale", height: 300}], - [ - {resizeMode: "crop-and-scale", width: 889, height: 500, frameRate: 10}, - {resizeMode: "crop-and-scale", width: 400, height: 300, frameRate: 30} - ], - ], - [ - [ - {resizeMode: "crop-and-scale", frameRate: {exact: 5}}, - {resizeMode: "crop-and-scale", frameRate: {exact: 1}}, - ], - [ - {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 5}, - {resizeMode: "crop-and-scale", width: 640, height: 480, frameRate: 1}, - ], - [[2, 7], [0.5, 3]], - ], - ].forEach( - ([ - [video, cloneVideo], - [expected, cloneExpected], - [testFramerate, cloneTestFramerate] = [null, null] - ]) => promise_test(async t => { - const stream = await navigator.mediaDevices.getUserMedia({video}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - let settings = track.getSettings(); - let cloneSettings = clone.getSettings(); - for (const key of Object.keys(expected)) { - assert_equals(settings[key], expected[key], `original: ${key}`); - assert_equals(cloneSettings[key], expected[key], `clone: ${key}`); - } - await clone.applyConstraints(cloneVideo); - settings = track.getSettings(); - cloneSettings = clone.getSettings(); - for (const key of Object.keys(expected)) { - assert_equals(settings[key], expected[key], `original-post: ${key}`); - } - for (const key of Object.keys(cloneExpected)) { - assert_equals(cloneSettings[key], cloneExpected[key], `clone-post: ${key}`); - } - await test_resolution_equals(t, track, settings.width, settings.height); - await test_resolution_equals(t, clone, cloneSettings.width, cloneSettings.height); - if (testFramerate) { - const [low, high] = testFramerate; - await test_framerate_between_exclusive(t, track, low, high); - } - if (cloneTestFramerate) { - const [low, high] = cloneTestFramerate; - await test_framerate_between_exclusive(t, clone, low, high); - } - }, `gUM gets ${JSON.stringify(expected)} + clone ` + - `${JSON.stringify(cloneExpected)} by ${JSON.stringify(video)} ` + - `and ${JSON.stringify(cloneVideo)}`)); - - [ - [ - [{echoCancellation: true}, {echoCancellation: false, noiseSuppression: true, autoGainControl: true}], - [ - {echoCancellation: true, noiseSuppression: true, autoGainControl: true}, - {echoCancellation: false, noiseSuppression: true, autoGainControl: true}, - ], - ], - [ - [{echoCancellation: false, noiseSuppression: false, autoGainControl: false}, {}], - [ - {echoCancellation: false, noiseSuppression: false, autoGainControl: false}, - {echoCancellation: true, noiseSuppression: true, autoGainControl: true}, - ], - ], - ].forEach( - ([ - [audio, cloneAudio], - [expected, cloneExpected], - ]) => promise_test(async t => { - const stream = await navigator.mediaDevices.getUserMedia({audio}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - let settings = track.getSettings(); - let cloneSettings = clone.getSettings(); - for (const key of Object.keys(expected)) { - assert_equals(settings[key], expected[key], `original: ${key}`); - assert_equals(cloneSettings[key], expected[key], `clone: ${key}`); - } - await clone.applyConstraints(cloneAudio); - settings = track.getSettings(); - cloneSettings = clone.getSettings(); - for (const key of Object.keys(expected)) { - assert_equals(settings[key], expected[key], `original-post: ${key}`); - } - for (const key of Object.keys(cloneExpected)) { - assert_equals(cloneSettings[key], cloneExpected[key], `clone-post: ${key}`); - } - }, `gUM gets ${JSON.stringify(expected)} + clone ` + - `${JSON.stringify(cloneExpected)} by ${JSON.stringify(audio)} ` + - `and ${JSON.stringify(cloneAudio)}`)); - - - - promise_test(async t => { - const stream = await navigator.mediaDevices.getUserMedia({video: true}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - try { - await clone.applyConstraints({resizeMode: "none", width: {min: 2000}}) - } catch(e) { - assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`); - return; - } - assert_unreached("applyConstraints is rejected with impossible width"); - }, "applyConstraints on gUM clone is rejected by resizeMode none and impossible min-width"); - - promise_test(async t => { - const stream = await navigator.mediaDevices.getUserMedia({video: true}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - try { - await clone.applyConstraints({resizeMode: "none", width: {max: 200}}) - } catch(e) { - assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`); - return; - } - assert_unreached("applyConstraints is rejected with impossible width"); - }, "applyConstraints on gUM clone is rejected by resizeMode none and impossible max-width"); - - promise_test(async t => { - const stream = await navigator.mediaDevices.getUserMedia({video: true}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - try { - await clone.applyConstraints({resizeMode: "crop-and-scale", width: {min: 2000}}) - } catch(e) { - assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`); - return; - } - assert_unreached("applyConstraints is rejected with impossible width"); - }, "applyConstraints on gUM clone is rejected by resizeMode crop-and-scale and impossible width"); - - promise_test(async t => { - const stream = await navigator.mediaDevices.getUserMedia({video: true}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - try { - await clone.applyConstraints({resizeMode: "crop-and-scale", frameRate: {min: 50}}); - } catch(e) { - assert_equals(e.name, "OverconstrainedError", `got error ${e.message}`); - return; - } - assert_unreached("applyConstraints is rejected with impossible fps"); - }, "applyConstraints on gUM clone is rejected by resizeMode crop-and-scale impossible fps"); -</script> diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/MediaStreamTrack-resizeMode.https.html b/testing/web-platform/mozilla/tests/mediacapture-streams/MediaStreamTrack-resizeMode.https.html @@ -6,11 +6,77 @@ <script src=/resources/testharnessreport.js></script> <script src=/resources/testdriver.js></script> <script src=/resources/testdriver-vendor.js></script> -<script src=settings-helper.js></script> -<script src=video-test-helper.js></script> <script> "use strict" + async function test_framerate_between_exclusive(t, track, lower, upper) { + const video = document.createElement("video"); + document.body.appendChild(video); + t.add_cleanup(async () => document.body.removeChild(video)); + + video.srcObject = new MediaStream([track]); + await video.play(); + + const numSeconds = 2; + await new Promise(r => setTimeout(r, numSeconds * 1000)); + const totalVideoFrames = video.mozPaintedFrames; + assert_between_exclusive(totalVideoFrames / numSeconds, lower, upper, "totalVideoFrames"); + } + + function createSettingsDicts(width, height, step = 1) { + const settingsDicts = [], aspect = width / height; + do { + settingsDicts.push({ width, height }); + if (width > height) { + height = Math.round((width - step) / aspect); + width -= step; + } else { + width = Math.round((height - step) * aspect); + height -= step; + } + } while (width > 2 && height > 2); + return settingsDicts; + } + + function integerFitness(actual, ideal) { + if (actual == ideal) { + return 0; + } + return Math.abs(actual - ideal) / Math.max(Math.abs(actual), Math.abs(ideal)); + } + + function findFittestResolutionSetting(width, height, constraints) { + const widthIsNumber = typeof constraints.width == "number"; + const heightIsNumber = typeof constraints.height == "number"; + const c = { + width: { + ideal: widthIsNumber ? constraints.width : constraints?.width?.ideal, + max: constraints?.width?.max ?? 1000000, + }, + height: { + ideal: heightIsNumber ? constraints.height : constraints?.height?.ideal, + max: constraints?.height?.max ?? 1000000, + }, + }; + const dicts = createSettingsDicts(width, height) + .filter(s => s.width <= c.width.max && s.height <= c.height.max); + for (const dict of dicts) { + dict.distance = + integerFitness(dict.width, c.width.ideal) + + integerFitness(dict.height, c.height.ideal); + } + + const filteredDicts = dicts.filter(s => { + return (!c.width.ideal || s.width <= c.width.ideal) && + (!c.height.ideal || s.height <= c.height.ideal); + }); + + return filteredDicts.reduce( + (a, b) => (a.distance < b.distance ? a : b), + filteredDicts[0], + ); + } + // Native capabilities supported by the fake camera. const nativeLow = {width: 640, height: 480, frameRate: 30, resizeMode: "none"}; const nativeHigh = {width: 1280, height: 720, frameRate: 10, resizeMode: "none"}; diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/settings-helper.js b/testing/web-platform/mozilla/tests/mediacapture-streams/settings-helper.js @@ -1,54 +0,0 @@ - -function createSettingsDicts(width, height, step = 1) { - const settingsDicts = [], aspect = width / height; - do { - settingsDicts.push({ width, height }); - if (width > height) { - height = Math.round((width - step) / aspect); - width -= step; - } else { - width = Math.round((height - step) * aspect); - height -= step; - } - } while (width > 2 && height > 2); - return settingsDicts; -} - -function integerFitness(actual, ideal) { - if (actual == ideal) { - return 0; - } - return Math.abs(actual - ideal) / Math.max(Math.abs(actual), Math.abs(ideal)); -} - -function findFittestResolutionSetting(width, height, constraints) { - const widthIsNumber = typeof constraints.width == "number"; - const heightIsNumber = typeof constraints.height == "number"; - const c = { - width: { - ideal: widthIsNumber ? constraints.width : constraints?.width?.ideal, - max: constraints?.width?.max ?? 1000000, - }, - height: { - ideal: heightIsNumber ? constraints.height : constraints?.height?.ideal, - max: constraints?.height?.max ?? 1000000, - }, - }; - const dicts = createSettingsDicts(width, height) - .filter(s => s.width <= c.width.max && s.height <= c.height.max); - for (const dict of dicts) { - dict.distance = - integerFitness(dict.width, c.width.ideal) + - integerFitness(dict.height, c.height.ideal); - } - - const filteredDicts = dicts.filter(s => { - return (!c.width.ideal || s.width <= c.width.ideal) && - (!c.height.ideal || s.height <= c.height.ideal); - }); - - return filteredDicts.reduce( - (a, b) => (a.distance < b.distance ? a : b), - filteredDicts[0], - ); -} diff --git a/testing/web-platform/mozilla/tests/mediacapture-streams/video-test-helper.js b/testing/web-platform/mozilla/tests/mediacapture-streams/video-test-helper.js @@ -1,38 +0,0 @@ -// Helper functions checking video flow using HTMLVideoElement. - -async function test_resolution_equals(t, track, width, height) { - const video = document.createElement("video"); - const timeout = new Promise(r => t.step_timeout(r, 1000)); - const waitForResize = () => Promise.race([ - new Promise(r => video.onresize = r), - timeout.then(() => { throw new Error("Timeout waiting for resize"); }), - ]); - video.srcObject = new MediaStream([track]); - video.play(); - // Wait for the first frame. - await waitForResize(); - // There's a potential race with applyConstraints, where a frame of the old - // resolution is in flight and renders after applyConstraints has resolved. - // In that case, wait for another frame. - if (video.videoWidth != width || video.videoHeight != height) { - await waitForResize(); - } - - assert_equals(video.videoWidth, width, "videoWidth"); - assert_equals(video.videoHeight, height, "videoHeight"); -} - -async function test_framerate_between_exclusive(t, track, lower, upper) { - const video = document.createElement("video"); - document.body.appendChild(video); - video.style = "width:320px;height:240px;" - t.add_cleanup(async () => document.body.removeChild(video)); - - video.srcObject = new MediaStream([track]); - await video.play(); - - const numSeconds = 2; - await new Promise(r => setTimeout(r, numSeconds * 1000)); - const totalVideoFrames = video.mozPaintedFrames; - assert_between_exclusive(totalVideoFrames / numSeconds, lower, upper, "totalVideoFrames"); -} diff --git a/testing/web-platform/mozilla/tests/screen-capture/MediaStreamTrack-independent-clones.https.html b/testing/web-platform/mozilla/tests/screen-capture/MediaStreamTrack-independent-clones.https.html @@ -1,232 +0,0 @@ -<!doctype html> -<title>MediaStreamTrack clones have independent settings. Assumes Mozilla's fake camera source with 480p and 720p capabilities.</title> -<meta name="timeout" content="long"> -<p class="instructions">When prompted, accept to share your video stream.</p> -<script src=/resources/testharness.js></script> -<script src=/resources/testharnessreport.js></script> -<script src=/resources/testdriver.js></script> -<script src=/resources/testdriver-vendor.js></script> -<script src=../mediacapture-streams/settings-helper.js></script> -<script src=../mediacapture-streams/video-test-helper.js></script> -<script> - "use strict" - - setup(() => assert_implements("getDisplayMedia" in navigator.mediaDevices, "getDisplayMedia() not supported")); - - // Note these gDM tests will fail if our own window is on a screen different - // than the system's first screen. They're functions in case the browser - // window needs to be moved to the first screen during the test in order to - // pass. - function screenPixelRatio() { return SpecialPowers.wrap(window).desktopToDeviceScale; } - function screenWidth() { return window.screen.width * window.devicePixelRatio; } - function screenHeight() { return window.screen.height * window.devicePixelRatio; } - function desktopWidth() { - // TODO: Bug 1965499 - scale down by screenPixelRatio by default in resizeMode: crop-and-scale. - // return screenWidth() / screenPixelRatio(); - return screenWidth(); - } - function desktopHeight() { - // TODO: Bug 1965499 - scale down by screenPixelRatio by default in resizeMode: crop-and-scale. - // return screenHeight() / screenPixelRatio(); - return screenHeight(); - } - - // TODO: By default we shouldn't be multiplying with window.devicePixelRatio (bug 1703991). - function defaultScreen() { - return { - resizeMode: "crop-and-scale", - width: screenWidth(), - height: screenHeight(), - frameRate: 30, - }; - } - // TODO: Should get the source's real refresh rate for frameRate (bug 1984363). - function nativeScreen() { - return { - resizeMode: "none", - width: screenWidth(), - height: screenHeight(), - frameRate: 60 - }; - } - - [ - [ - [{resizeMode: "none", width: 100}, {resizeMode: "none", height: 500}], - [nativeScreen, nativeScreen], - ], - [ - [{resizeMode: "none", frameRate: 50}, {resizeMode: "none", frameRate: 1}], - [nativeScreen, nativeScreen], - ], - [ - [{resizeMode: "none"}, {resizeMode: "crop-and-scale"}], - [nativeScreen, defaultScreen], - ], - [ - [{resizeMode: "crop-and-scale"}, {resizeMode: "crop-and-scale", height: 100}], - [ - defaultScreen, - () => ({ - resizeMode: "crop-and-scale", - width: Math.round(screenWidth() / screenHeight() * 100), - height: 100, - frameRate: 30 - }), - ], - ], - [ - [{resizeMode: "crop-and-scale", height: 100}, {resizeMode: "crop-and-scale"}], - [ - () => ({ - resizeMode: "crop-and-scale", - width: Math.round(screenWidth() / screenHeight() * 100), - height: 100, - frameRate: 30 - }), - defaultScreen, - ], - ], - [ - [{resizeMode: "crop-and-scale", frameRate: 5}, {resizeMode: "crop-and-scale", frameRate: 50}], - [ - () => { - const { width, height } = defaultScreen(); - return { width, height, frameRate: 5}; - }, - () => { - const { width, height } = defaultScreen(); - return { width, height, frameRate: 50}; - }, - ], - [[2, 7], [2, 50]], - ], - [ - [{resizeMode: "crop-and-scale", frameRate: 50}, {resizeMode: "crop-and-scale", frameRate: 5}], - [ - () => { - const { width, height } = defaultScreen(); - return { width, height, frameRate: 50}; - }, - () => { - const { width, height } = defaultScreen(); - return { width, height, frameRate: 5}; - }, - [[2, 50], [2, 7]] - ], - ], - [ - [{resizeMode: "none"}, {resizeMode: "crop-and-scale", frameRate: 5000}, {}], - [ - nativeScreen, - () => { - const { width, height } = defaultScreen(); - return { width, height, frameRate: 120}; - }, - ] - ], - ].forEach( - ([ - [video, cloneVideo], - [expectedFunc, cloneExpectedFunc], - [testFramerate, cloneTestFramerate] = [null, null] - ]) => { - let expected, cloneExpected; - promise_test(async t => { - expected = expectedFunc(); - cloneExpected = cloneExpectedFunc(); - await test_driver.bless('getDisplayMedia()'); - const stream = await navigator.mediaDevices.getDisplayMedia({video}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - let settings = track.getSettings(); - let cloneSettings = clone.getSettings(); - for (const key of Object.keys(expected)) { - assert_equals(settings[key], expected[key], `original: ${key}`); - assert_equals(cloneSettings[key], expected[key], `clone: ${key}`); - } - await clone.applyConstraints(cloneVideo); - settings = track.getSettings(); - cloneSettings = clone.getSettings(); - for (const key of Object.keys(expected)) { - assert_equals(settings[key], expected[key], `original-post: ${key}`); - } - for (const key of Object.keys(cloneExpected)) { - assert_equals(cloneSettings[key], cloneExpected[key], `clone-post: ${key}`); - } - await test_resolution_equals(t, track, settings.width, settings.height); - await test_resolution_equals(t, clone, cloneSettings.width, cloneSettings.height); - if (testFramerate) { - const [low, high] = testFramerate; - await test_framerate_between_exclusive(t, track, low, high); - } - if (cloneTestFramerate) { - const [low, high] = cloneTestFramerate; - await test_framerate_between_exclusive(t, clone, low, high); - } - }, `gDM gets expected modes by ${JSON.stringify(video)} + (cloned) ${JSON.stringify(cloneVideo)}`); - }); - - promise_test(async t => { - await test_driver.bless('getDisplayMedia()'); - const stream = await navigator.mediaDevices.getDisplayMedia({video: {resizeMode: "none"}}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - await clone.applyConstraints( - { - resizeMode: "crop-and-scale", - width: 400, - height: 400 - } - ); - const expected = findFittestResolutionSetting( - screenWidth(), - screenHeight(), - clone.getConstraints() - ); - assert_equals(clone.getSettings().width, expected.width, "width"); - assert_equals(clone.getSettings().height, expected.height, "height"); - await test_resolution_equals(t, clone, clone.getSettings().width, clone.getSettings().height); - assert_approx_equals( - clone.getSettings().width / clone.getSettings().height, - desktopWidth() / desktopHeight(), - 0.01, - "aspect ratio" - ); - assert_equals(clone.getSettings().resizeMode, "crop-and-scale", "resizeMode"); - }, "applyConstraints on gDM clone doesn't crop with only ideal dimensions"); - - promise_test(async t => { - await test_driver.bless('getDisplayMedia()'); - const stream = await navigator.mediaDevices.getDisplayMedia({video: {resizeMode: "none"}}); - const [track] = stream.getTracks(); - const clone = track.clone(); - t.add_cleanup(() => { - track.stop(); - clone.stop(); - }); - await clone.applyConstraints( - { - resizeMode: "crop-and-scale", - width: {max: 400}, - height: {ideal: 400} - } - ); - assert_equals(clone.getSettings().width, 400, "width"); - assert_equals( - clone.getSettings().height, - Math.round(screenHeight() / screenWidth() * 400), - "height" - ); - await test_resolution_equals(t, clone, clone.getSettings().width, clone.getSettings().height); - assert_equals(clone.getSettings().resizeMode, "crop-and-scale", "resizeMode"); - }, "applyConstraints on gDM clone doesn't crop with ideal and max dimensions"); -</script> diff --git a/testing/web-platform/mozilla/tests/webdriver/bidi/emulation/set_locale_override/contexts.py b/testing/web-platform/mozilla/tests/webdriver/bidi/emulation/set_locale_override/contexts.py @@ -15,7 +15,7 @@ pytest_plugins = "tests.bidi.emulation.conftest" } ) async def test_locale_override_isolated_in_browsing_context( - bidi_session, another_locale, assert_locale_against_value, some_locale + bidi_session, get_current_locale, some_locale, another_locale ): context_in_process_1 = await bidi_session.browsing_context.create(type_hint="tab") @@ -31,5 +31,5 @@ async def test_locale_override_isolated_in_browsing_context( ) # Make sure that the locale override didn't override inappropriate context. - await assert_locale_against_value(some_locale, context_in_process_1) - await assert_locale_against_value(another_locale, context_in_process_2) + assert await get_current_locale(context_in_process_1) == some_locale + assert await get_current_locale(context_in_process_2) == another_locale diff --git a/testing/web-platform/tests/svg/text/reftests/text-path-transformed-002-ref.html b/testing/web-platform/tests/svg/text/reftests/text-path-transformed-002-ref.html @@ -1,7 +0,0 @@ -<!doctype html> -<svg width="400" height="200" viewBox="-150 0 600 200"> - <path id="rotated-anchor" d="M 20 200 A 125 125 0 0 1 289 175" transform="rotate(36 150 190)" fill="none" stroke="black"/> - <text> - <textPath href="#rotated-anchor">XXXX</textPath> - </text> -</svg> diff --git a/testing/web-platform/tests/svg/text/reftests/text-path-transformed-002.html b/testing/web-platform/tests/svg/text/reftests/text-path-transformed-002.html @@ -1,18 +0,0 @@ -<!doctype html> -<meta charset="utf-8"> -<style> -#rotated-anchor { - transform: rotate(36deg); - transform-origin: 150px 190px; -} -</style> -<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io"> -<link rel="author" title="Mozilla" href="https://mozilla.org"> -<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1998071"> -<link rel="match" href="text-path-transformed-002-ref.html"> -<svg width="400" height="200" viewBox="-150 0 600 200"> - <path id="rotated-anchor" d="M 20 200 A 125 125 0 0 1 289 175" fill="none" stroke="black"/> - <text> - <textPath href="#rotated-anchor">XXXX</textPath> - </text> -</svg> diff --git a/testing/web-platform/tests/webdriver/tests/bidi/emulation/conftest.py b/testing/web-platform/tests/webdriver/tests/bidi/emulation/conftest.py @@ -99,46 +99,14 @@ async def default_navigator_languages(get_current_navigator_languages, top_conte @pytest_asyncio.fixture -async def default_accept_language(bidi_session, get_fetch_headers, top_context, url): - """ - Returns default value of `Accept-Language` header. - """ - await bidi_session.browsing_context.navigate( - context=top_context["context"], - url=url("/webdriver/tests/bidi/browsing_context/support/empty.html"), - wait="complete" - ) - headers = await get_fetch_headers(top_context) - return headers["accept-language"][0] if "accept-language" in headers else None - - -@pytest_asyncio.fixture -async def assert_accept_language(bidi_session, assert_header_present, url): - """ - Assert value of `Accept-Language` header. - """ - async def assert_accept_language(context, value, sandbox_name=None): - await bidi_session.browsing_context.navigate( - context=context["context"], - url=url("/webdriver/tests/bidi/browsing_context/support/empty.html"), - wait="complete" - ) - await assert_header_present(context, "accept-language", value, sandbox_name) - - return assert_accept_language - - -@pytest_asyncio.fixture async def assert_locale_against_default( top_context, - assert_accept_language, - default_locale, - default_navigator_language, - default_navigator_languages, - default_accept_language, get_current_locale, get_current_navigator_language, get_current_navigator_languages, + default_locale, + default_navigator_language, + default_navigator_languages, ): """ Assert JS locale and navigator.language/s against default values. @@ -162,7 +130,6 @@ async def assert_locale_against_default( await get_current_navigator_languages(context, sandbox_name) == default_navigator_languages ) - await assert_accept_language(context, default_accept_language, sandbox_name) return assert_locale_against_default @@ -170,7 +137,6 @@ async def assert_locale_against_default( @pytest_asyncio.fixture async def assert_locale_against_value( top_context, - assert_accept_language, get_current_locale, get_current_navigator_language, get_current_navigator_languages, @@ -187,7 +153,6 @@ async def assert_locale_against_value( assert await get_current_locale(context, sandbox_name) == value assert await get_current_navigator_language(context, sandbox_name) == value assert await get_current_navigator_languages(context, sandbox_name) == [value] - await assert_accept_language(context, value) return assert_locale_against_value diff --git a/testing/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/contexts.py b/testing/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/contexts.py @@ -6,9 +6,9 @@ pytestmark = pytest.mark.asyncio async def test_contexts( bidi_session, new_tab, + some_locale, assert_locale_against_default, assert_locale_against_value, - some_locale, ): new_context = await bidi_session.browsing_context.create(type_hint="tab") @@ -34,8 +34,8 @@ async def test_contexts( async def test_multiple_contexts( bidi_session, new_tab, - assert_locale_against_default, assert_locale_against_value, + assert_locale_against_default, some_locale, ): new_context = await bidi_session.browsing_context.create(type_hint="tab") @@ -63,11 +63,11 @@ async def test_multiple_contexts( async def test_iframe( bidi_session, new_tab, + some_locale, + domain, inline, another_locale, assert_locale_against_value, - some_locale, - domain, ): # Set locale override. await bidi_session.emulation.set_locale_override( @@ -123,8 +123,8 @@ async def test_locale_override_applies_to_existing_sandbox( bidi_session, new_tab, another_locale, - assert_locale_against_default, assert_locale_against_value, + assert_locale_against_default, ): sandbox_name = "test" diff --git a/testing/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/locale.py b/testing/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/locale.py @@ -6,10 +6,10 @@ pytestmark = pytest.mark.asyncio async def test_locale_set_override_and_reset( bidi_session, new_tab, + some_locale, another_locale, assert_locale_against_default, assert_locale_against_value, - some_locale, ): await assert_locale_against_default(new_tab) diff --git a/testing/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/user_contexts.py b/testing/web-platform/tests/webdriver/tests/bidi/emulation/set_locale_override/user_contexts.py @@ -7,9 +7,9 @@ async def test_user_contexts( bidi_session, create_user_context, new_tab, + some_locale, assert_locale_against_default, assert_locale_against_value, - some_locale, ): user_context = await create_user_context() context_in_user_context = await bidi_session.browsing_context.create( @@ -41,9 +41,9 @@ async def test_set_to_default_user_context( bidi_session, new_tab, create_user_context, + some_locale, assert_locale_against_default, assert_locale_against_value, - some_locale, ): user_context = await create_user_context() context_in_user_context = await bidi_session.browsing_context.create( @@ -76,8 +76,8 @@ async def test_set_to_default_user_context( async def test_set_to_multiple_user_contexts( bidi_session, create_user_context, - assert_locale_against_value, some_locale, + assert_locale_against_value, ): user_context_1 = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( @@ -98,10 +98,10 @@ async def test_set_to_multiple_user_contexts( async def test_set_to_user_context_and_then_to_context( bidi_session, create_user_context, + some_locale, another_locale, assert_locale_against_default, assert_locale_against_value, - some_locale, ): user_context = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( @@ -155,10 +155,10 @@ async def test_set_to_user_context_and_then_to_context( async def test_set_to_context_and_then_to_user_context( bidi_session, create_user_context, + some_locale, another_locale, assert_locale_against_default, assert_locale_against_value, - some_locale, ): user_context = await create_user_context() context_in_user_context_1 = await bidi_session.browsing_context.create( diff --git a/testing/web-platform/tests/webdriver/tests/bidi/network/set_extra_headers/conftest.py b/testing/web-platform/tests/webdriver/tests/bidi/network/set_extra_headers/conftest.py @@ -62,6 +62,21 @@ async def get_navigation_headers(bidi_session, url): return get_navigation_headers +@pytest_asyncio.fixture +async def get_fetch_headers(bidi_session, url): + async def get_fetch_headers(context): + echo_link = url("webdriver/tests/support/http_handlers/headers_echo.py") + result = await bidi_session.script.evaluate( + expression=f"fetch('{echo_link}').then(r => r.text())", + target=ContextTarget(context["context"]), + await_promise=True, + ) + + return (json.JSONDecoder().decode(result["value"]))["headers"] + + return get_fetch_headers + + @pytest_asyncio.fixture(params=["fetch", "navigation"]) def get_headers_methods_invariant(request, get_fetch_headers, get_navigation_headers): @@ -73,6 +88,18 @@ def get_headers_methods_invariant(request, get_fetch_headers, @pytest_asyncio.fixture +def assert_header_present(get_fetch_headers): + async def assert_header_present(context, header_name, header_value): + actual_headers = await get_fetch_headers(context) + assert header_name in actual_headers, \ + f"header '{header_name}' should be present" + assert [header_value] == actual_headers[header_name], \ + f"header '{header_name}' should have value '{header_value}'" + + return assert_header_present + + +@pytest_asyncio.fixture def assert_header_not_present(get_fetch_headers): async def assert_header_not_present(context, header_name): actual_headers = await get_fetch_headers(context) diff --git a/testing/web-platform/tests/webdriver/tests/support/fixtures_bidi.py b/testing/web-platform/tests/webdriver/tests/support/fixtures_bidi.py @@ -644,7 +644,6 @@ def fetch(bidi_session, top_context, configuration): post_data=None, context=top_context, timeout_in_seconds=3, - sandbox_name=None ): if method is None: method = "GET" if post_data is None else "POST" @@ -684,7 +683,7 @@ def fetch(bidi_session, top_context, configuration): # Wait for fetch() to resolve a response and for response.text() to # resolve as well to make sure the request/response is completed when # the helper returns. - return await bidi_session.script.evaluate( + await bidi_session.script.evaluate( expression=f""" {{ const controller = new AbortController(); @@ -696,7 +695,7 @@ def fetch(bidi_session, top_context, configuration): signal: controller.signal, }}).then(response => response.text()); }}""", - target=ContextTarget(context["context"], sandbox=sandbox_name), + target=ContextTarget(context["context"]), await_promise=True, ) @@ -1153,29 +1152,3 @@ async def assert_file_dialog_not_canceled(bidi_session, inline, top_context, cancel_event_future.cancel() yield assert_file_dialog_not_canceled - - -@pytest_asyncio.fixture -async def get_fetch_headers(url, fetch): - async def get_fetch_headers(context, sandbox_name=None): - result = await fetch( - url=url("webdriver/tests/support/http_handlers/headers_echo.py"), - context=context, - sandbox_name=sandbox_name - ) - - return (json.JSONDecoder().decode(result["value"]))["headers"] - - return get_fetch_headers - - -@pytest_asyncio.fixture -async def assert_header_present(get_fetch_headers): - async def assert_header_present(context, header_name, header_value, sandbox_name=None): - actual_headers = await get_fetch_headers(context, sandbox_name) - assert header_name in actual_headers, \ - f"header '{header_name}' should be present" - assert [header_value] == actual_headers[header_name], \ - f"header '{header_name}' should have value '{header_value}'" - - return assert_header_present diff --git a/toolkit/components/formautofill/shared/HeuristicsRegExp.sys.mjs b/toolkit/components/formautofill/shared/HeuristicsRegExp.sys.mjs @@ -555,7 +555,7 @@ export const HeuristicsRegExp = { "|vorname" + // de-DE "|nombre" + // es "|forename|prénom|prenom" + // fr-FR - "|(^|[^\\p{L}\\p{N}])名([^\\p{L}\\p{N}]|$)" + // ja-JP + "|名" + // ja-JP "|nome" + // pt-BR, pt-PT "|Имя" + // ru "|نام" + // fa @@ -692,14 +692,7 @@ export const HeuristicsRegExp = { // lower-cased field name and get a rough equivalent of a case-insensitive // match. This avoids a performance cliff with the "iu" flag on regular // expressions. - let pattern = `(${set[name].toLowerCase()})`.normalize("NFKC"); - - // We should not lower case the \p{L} & \p{N} parts of the pattern, - // revert them back. - pattern = pattern.replaceAll("\\p{l}", "\\p{L}"); - pattern = pattern.replaceAll("\\p{n}", "\\p{N}"); - - regexps.push(pattern); + regexps.push(`(${set[name].toLowerCase()})`.normalize("NFKC")); } }); diff --git a/tools/profiler/core/platform.cpp b/tools/profiler/core/platform.cpp @@ -3363,9 +3363,10 @@ static void MaybeWriteRawStartTimeValue(SpliceableJSONWriter& aWriter, #endif #ifdef XP_WIN - uint64_t startTimeQPC = aStartTime.RawQueryPerformanceCounterValue(); - aWriter.DoubleProperty("startTimeAsQueryPerformanceCounterValue", - static_cast<double>(startTimeQPC)); + Maybe<uint64_t> startTimeQPC = aStartTime.RawQueryPerformanceCounterValue(); + if (startTimeQPC) + aWriter.DoubleProperty("startTimeAsQueryPerformanceCounterValue", + static_cast<double>(*startTimeQPC)); #endif } diff --git a/tools/profiler/public/ETWTools.h b/tools/profiler/public/ETWTools.h @@ -247,8 +247,14 @@ static inline void CreateDataDescForPayloadNonPOD( static inline void CreateDataDescForPayloadNonPOD( PayloadBuffer& aBuffer, EVENT_DATA_DESCRIPTOR& aDescriptor, const mozilla::TimeStamp& aPayload) { - CreateDataDescForPayloadPOD(aBuffer, aDescriptor, - aPayload.RawQueryPerformanceCounterValue()); + if (aPayload.RawQueryPerformanceCounterValue().isNothing()) { + // This should never happen? + EventDataDescCreate(&aDescriptor, nullptr, 0); + return; + } + + CreateDataDescForPayloadPOD( + aBuffer, aDescriptor, aPayload.RawQueryPerformanceCounterValue().value()); } static inline void CreateDataDescForPayloadNonPOD( @@ -302,13 +308,13 @@ static inline void StoreBaseEventDataDesc( const mozilla::MarkerOptions& aOptions) { if (aOptions.IsTimingUnspecified()) { aStorage.mStartTime = - mozilla::TimeStamp::Now().RawQueryPerformanceCounterValue(); + mozilla::TimeStamp::Now().RawQueryPerformanceCounterValue().value(); aStorage.mPhase = 0; } else { aStorage.mStartTime = - aOptions.Timing().StartTime().RawQueryPerformanceCounterValue(); + aOptions.Timing().StartTime().RawQueryPerformanceCounterValue().value(); aStorage.mEndTime = - aOptions.Timing().EndTime().RawQueryPerformanceCounterValue(); + aOptions.Timing().EndTime().RawQueryPerformanceCounterValue().value(); aStorage.mPhase = uint8_t(aOptions.Timing().MarkerPhase()); } if (!aOptions.InnerWindowId().IsUnspecified()) { diff --git a/view/nsView.cpp b/view/nsView.cpp @@ -187,16 +187,14 @@ struct WidgetViewBounds { int32_t mRoundTo = 1; }; -static WidgetViewBounds CalcWidgetViewBounds(const nsRect& aBounds, - int32_t aAppUnitsPerDevPixel, - nsIFrame* aParentFrame, - nsIWidget* aThisWidget, - WindowType aType) { +static WidgetViewBounds CalcWidgetViewBounds( + const nsRect& aBounds, int32_t aAppUnitsPerDevPixel, nsView* aParentView, + nsIWidget* aThisWidget, WindowType aType, TransparencyMode aTransparency) { nsRect viewBounds(aBounds); nsIWidget* parentWidget = nullptr; - if (aParentFrame) { + if (aParentView) { nsPoint offset; - parentWidget = aParentFrame->GetNearestWidget(offset); + parentWidget = aParentView->GetNearestWidget(&offset, aAppUnitsPerDevPixel); // make viewBounds be relative to the parent widget, in appunits viewBounds += offset; @@ -239,9 +237,8 @@ static LayoutDeviceIntRect WidgetViewBoundsToDevicePixels( LayoutDeviceIntRect nsView::CalcWidgetBounds(WindowType aType, TransparencyMode aTransparency) { int32_t p2a = mViewManager->AppUnitsPerDevPixel(); - auto viewBounds = CalcWidgetViewBounds( - mDimBounds, p2a, GetParent() ? GetParent()->GetFrame() : nullptr, - mWindow.get(), aType); + auto viewBounds = CalcWidgetViewBounds(mDimBounds, p2a, GetParent(), + mWindow.get(), aType, aTransparency); auto newBounds = WidgetViewBoundsToDevicePixels(viewBounds, p2a, aType, aTransparency); @@ -261,10 +258,11 @@ LayoutDeviceIntRect nsView::CalcWidgetBounds(WindowType aType, } LayoutDeviceIntRect nsView::CalcWidgetBounds( - const nsRect& aBounds, int32_t aAppUnitsPerDevPixel, nsIFrame* aParentFrame, + const nsRect& aBounds, int32_t aAppUnitsPerDevPixel, nsView* aParentView, nsIWidget* aThisWidget, WindowType aType, TransparencyMode aTransparency) { - auto viewBounds = CalcWidgetViewBounds(aBounds, aAppUnitsPerDevPixel, - aParentFrame, aThisWidget, aType); + auto viewBounds = + CalcWidgetViewBounds(aBounds, aAppUnitsPerDevPixel, aParentView, + aThisWidget, aType, aTransparency); return WidgetViewBoundsToDevicePixels(viewBounds, aAppUnitsPerDevPixel, aType, aTransparency); } @@ -312,6 +310,16 @@ void nsView::SetVisibility(ViewVisibility aVisibility) { NotifyEffectiveVisibilityChanged(IsEffectivelyVisible()); } +void nsView::InvalidateHierarchy() { + if (mViewManager->GetRootView() == this) { + mViewManager->InvalidateHierarchy(); + } + + for (nsView* child = mFirstChild; child; child = child->GetNextSibling()) { + child->InvalidateHierarchy(); + } +} + void nsView::InsertChild(nsView* aChild, nsView* aSibling) { MOZ_ASSERT(nullptr != aChild, "null ptr"); @@ -329,6 +337,14 @@ void nsView::InsertChild(nsView* aChild, nsView* aSibling) { mFirstChild = aChild; } aChild->SetParent(this); + + // If we just inserted a root view, then update the RootViewManager + // on all view managers in the new subtree. + + nsViewManager* vm = aChild->GetViewManager(); + if (vm->GetRootView() == aChild) { + aChild->InvalidateHierarchy(); + } } } @@ -354,6 +370,14 @@ void nsView::RemoveChild(nsView* child) { kid = kid->GetNextSibling(); } NS_ASSERTION(found, "tried to remove non child"); + + // If we just removed a root view, then update the RootViewManager + // on all view managers in the removed subtree. + + nsViewManager* vm = child->GetViewManager(); + if (vm->GetRootView() == child) { + child->InvalidateHierarchy(); + } } } @@ -522,6 +546,32 @@ nsPoint nsView::GetOffsetTo(const nsView* aOther, const int32_t aAPD) const { return offset; } +nsPoint nsView::GetOffsetToWidget(nsIWidget* aWidget) const { + nsPoint pt; + // Get the view for widget + nsView* widgetView = GetViewFor(aWidget); + if (!widgetView) { + return pt; + } + + // Get the offset to the widget view in the widget view's APD + // We get the offset in the widget view's APD first and then convert to our + // APD afterwards so that we can include the widget view's ViewToWidgetOffset + // in the sum in its native APD, and then convert the whole thing to our APD + // so that we don't have to convert the APD of the relatively small + // ViewToWidgetOffset by itself with a potentially large relative rounding + // error. + pt = -widgetView->GetOffsetTo(this); + // Add in the offset to the widget. + pt += widgetView->ViewToWidgetOffset(); + + // Convert to our appunits. + int32_t widgetAPD = widgetView->GetViewManager()->AppUnitsPerDevPixel(); + int32_t ourAPD = GetViewManager()->AppUnitsPerDevPixel(); + pt = pt.ScaleToOtherAppUnits(widgetAPD, ourAPD); + return pt; +} + nsIWidget* nsView::GetNearestWidget(nsPoint* aOffset) const { return GetNearestWidget(aOffset, GetViewManager()->AppUnitsPerDevPixel()); } diff --git a/view/nsView.h b/view/nsView.h @@ -205,6 +205,15 @@ class nsView final : public nsIWidgetListener { nsPoint GetOffsetTo(const nsView* aOther) const; /** + * Get the offset between the origin of |this| and the origin of aWidget. + * Adding the return value to a point in the coordinate system of |this| + * will transform the point to the coordinate system of aWidget. + * + * The offset is expressed in appunits of |this|. + */ + nsPoint GetOffsetToWidget(nsIWidget* aWidget) const; + + /** * Called to query the visibility state of a view. * @result current visibility state */ @@ -331,9 +340,9 @@ class nsView final : public nsIWidgetListener { bool IsRoot() const; static LayoutDeviceIntRect CalcWidgetBounds( - const nsRect& aBounds, int32_t aAppUnitsPerDevPixel, - nsIFrame* aParentFrame, nsIWidget* aThisWidget, - mozilla::widget::WindowType, mozilla::widget::TransparencyMode); + const nsRect& aBounds, int32_t aAppUnitsPerDevPixel, nsView* aParentView, + nsIWidget* aThisWidget, mozilla::widget::WindowType, + mozilla::widget::TransparencyMode); LayoutDeviceIntRect CalcWidgetBounds(mozilla::widget::WindowType, mozilla::widget::TransparencyMode); @@ -431,6 +440,9 @@ class nsView final : public nsIWidgetListener { void NotifyEffectiveVisibilityChanged(bool aEffectivelyVisible); + // Update the cached RootViewManager for all view manager descendents. + void InvalidateHierarchy(); + void CallOnAllRemoteChildren( const std::function<mozilla::CallState(mozilla::dom::BrowserParent*)>& aCallback); diff --git a/view/nsViewManager.cpp b/view/nsViewManager.cpp @@ -69,10 +69,13 @@ nsViewManager::~nsViewManager() { mRootView = nullptr; } + mRootViewManager = nullptr; + MOZ_RELEASE_ASSERT(!mPresShell, "Releasing nsViewManager without having called Destroy on " "the PresShell!"); } + nsView* nsViewManager::CreateView(const nsRect& aBounds, nsView* aParent, ViewVisibility aVisibilityFlag) { auto* v = new nsView(this, aVisibilityFlag); @@ -90,6 +93,18 @@ void nsViewManager::SetRootView(nsView* aView) { // Do NOT destroy the current root view. It's the caller's responsibility // to destroy it mRootView = aView; + + if (mRootView) { + nsView* parent = mRootView->GetParent(); + if (parent) { + // Calling InsertChild on |parent| will InvalidateHierarchy() on us, so + // no need to set mRootViewManager ourselves here. + parent->InsertChild(mRootView, nullptr); + } else { + InvalidateHierarchy(); + } + } + // Else don't touch mRootViewManager } void nsViewManager::GetWindowDimensions(nscoord* aWidth, nscoord* aHeight) { @@ -199,25 +214,6 @@ nsView* nsViewManager::GetDisplayRootFor(nsView* aView) { } } -nsViewManager* nsViewManager::RootViewManager() const { - const auto* cur = this; - while (auto* parent = cur->GetParentViewManager()) { - cur = parent; - } - return const_cast<nsViewManager*>(cur); -} - -nsViewManager* nsViewManager::GetParentViewManager() const { - if (!mPresShell) { - return nullptr; - } - auto* pc = mPresShell->GetPresContext(); - if (auto* parent = pc->GetParentPresContext()) { - return parent->PresShell()->GetViewManager(); - } - return nullptr; -} - /** aRegion is given in device coordinates!! aContext may be null, in which case layers should be used for @@ -533,6 +529,7 @@ bool nsViewManager::PaintWindow(nsIWidget* aWidget, if (!aWidget) { return false; } + // Get the view pointer here since NS_WILL_PAINT might have // destroyed it during CallWillPaintOnObservers (bug 378273). nsView* view = nsView::GetViewFor(aWidget); @@ -709,6 +706,19 @@ bool nsViewManager::IsViewInserted(nsView* aView) { return false; } +nsIWidget* nsViewManager::GetRootWidget() const { + if (!mRootView) { + return nullptr; + } + if (mRootView->HasWidget()) { + return mRootView->GetWidget(); + } + if (mRootView->GetParent()) { + return mRootView->GetParent()->GetViewManager()->GetRootWidget(); + } + return nullptr; +} + LayoutDeviceIntRect nsViewManager::ViewToWidget(nsView* aView, const nsRect& aRect) const { NS_ASSERTION(aView->GetViewManager() == this, "wrong view manager"); @@ -785,3 +795,16 @@ void nsViewManager::CallWillPaintOnObservers() { } } } + +void nsViewManager::InvalidateHierarchy() { + if (mRootView) { + mRootViewManager = nullptr; + nsView* parent = mRootView->GetParent(); + if (parent) { + mRootViewManager = parent->GetViewManager()->RootViewManager(); + NS_ASSERTION(mRootViewManager != this, + "Root view had a parent, but it has the same view manager"); + } + // else, we are implicitly our own root view manager. + } +} diff --git a/view/nsViewManager.h b/view/nsViewManager.h @@ -204,6 +204,12 @@ class nsViewManager final { public: /** + * Retrieve the widget at the root of the nearest enclosing + * view manager whose root view has a widget. + */ + nsIWidget* GetRootWidget() const; + + /** * Indicate whether the viewmanager is currently painting * * @param aPainting true if the viewmanager is painting @@ -242,6 +248,8 @@ class nsViewManager final { private: static uint32_t gLastUserEventTime; + /* Update the cached RootViewManager pointer on this view manager. */ + void InvalidateHierarchy(); void FlushPendingInvalidates(); MOZ_CAN_RUN_SCRIPT @@ -288,9 +296,11 @@ class nsViewManager final { void SetPainting(bool aPainting) { RootViewManager()->mPainting = aPainting; } - nsViewManager* RootViewManager() const; - nsViewManager* GetParentViewManager() const; - bool IsRootVM() const { return !GetParentViewManager(); } + nsViewManager* RootViewManager() const { + return mRootViewManager ? mRootViewManager.get() + : const_cast<nsViewManager*>(this); + } + bool IsRootVM() const { return !mRootViewManager; } MOZ_CAN_RUN_SCRIPT void WillPaintWindow(nsIWidget* aWidget); MOZ_CAN_RUN_SCRIPT @@ -310,6 +320,12 @@ class nsViewManager final { nsView* mRootView; + // mRootViewManager is a strong reference to the root view manager, unless + // |this| is the root, in which case mRootViewManager is null. Callers + // should use RootViewManager() (which handles that case) rather than using + // mRootViewManager directly. + RefPtr<nsViewManager> mRootViewManager; + // The following members should not be accessed directly except by // the root view manager. Some have accessor functions to enforce // this, as noted. diff --git a/widget/PuppetWidget.cpp b/widget/PuppetWidget.cpp @@ -26,9 +26,9 @@ #include "mozilla/TextEvents.h" #include "PuppetWidget.h" #include "nsContentUtils.h" -#include "nsView.h" #include "nsIWidgetListener.h" #include "imgIContainer.h" +#include "nsView.h" #include "nsXPLookAndFeel.h" #include "nsPrintfCString.h" diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp @@ -4330,11 +4330,7 @@ gboolean nsWindow::OnShellConfigureEvent(GdkEventConfigure* aEvent) { return FALSE; } - // X11 calc bounds from outer window while Wayland uses - // container size after container allocation event. - if (GdkIsX11Display()) { - SchedulePendingBounds(MayChangeCsdMargin::No); - } + SchedulePendingBounds(MayChangeCsdMargin::No); return FALSE; } diff --git a/xpcom/ds/nsTArray.h b/xpcom/ds/nsTArray.h @@ -767,17 +767,17 @@ namespace detail { // called as a function with two instances of our element type, returns an int, // we treat it as a tri-state comparator. // -// T is the type of the comparator object we want to check. L and R are the -// types that we'll be comparing. +// T is the type of the comparator object we want to check. U is the array +// element type that we'll be comparing. // // V is never passed, and is only used to allow us to specialize on the return // value of the comparator function. -template <typename T, typename L, typename R, typename V = int> +template <typename T, typename U, typename V = int> struct IsCompareMethod : std::false_type {}; -template <typename T, typename L, typename R> +template <typename T, typename U> struct IsCompareMethod< - T, L, R, decltype(std::declval<T>()(std::declval<L>(), std::declval<R>()))> + T, U, decltype(std::declval<T>()(std::declval<U>(), std::declval<U>()))> : std::true_type {}; // These two wrappers allow us to use either a tri-state comparator, or an @@ -792,8 +792,7 @@ struct IsCompareMethod< // purpose. // Comparator wrapper for a tri-state comparator function -template <typename T, typename L, typename R, - bool IsCompare = IsCompareMethod<T, L, R>::value> +template <typename T, typename U, bool IsCompare = IsCompareMethod<T, U>::value> struct CompareWrapper { #ifdef _MSC_VER # pragma warning(push) @@ -825,8 +824,8 @@ struct CompareWrapper { }; // Comparator wrapper for a class with Equals() and LessThan() methods. -template <typename T, typename L, typename R> -struct CompareWrapper<T, L, R, false> { +template <typename T, typename U> +struct CompareWrapper<T, U, false> { MOZ_IMPLICIT CompareWrapper(const T& aComparator) : mComparator(aComparator) {} @@ -1222,7 +1221,7 @@ class nsTArray_Impl template <class Item, class Comparator> [[nodiscard]] index_type IndexOf(const Item& aItem, index_type aStart, const Comparator& aComp) const { - ::detail::CompareWrapper<Comparator, value_type, Item> comp(aComp); + ::detail::CompareWrapper<Comparator, Item> comp(aComp); const value_type* iter = Elements() + aStart; const value_type* iend = Elements() + Length(); @@ -1256,7 +1255,7 @@ class nsTArray_Impl template <class Item, class Comparator> [[nodiscard]] index_type LastIndexOf(const Item& aItem, index_type aStart, const Comparator& aComp) const { - ::detail::CompareWrapper<Comparator, value_type, Item> comp(aComp); + ::detail::CompareWrapper<Comparator, Item> comp(aComp); size_type endOffset = aStart >= Length() ? Length() : aStart + 1; const value_type* iend = Elements() - 1; @@ -1293,7 +1292,7 @@ class nsTArray_Impl [[nodiscard]] index_type BinaryIndexOf(const Item& aItem, const Comparator& aComp) const { using mozilla::BinarySearchIf; - ::detail::CompareWrapper<Comparator, value_type, Item> comp(aComp); + ::detail::CompareWrapper<Comparator, Item> comp(aComp); size_t index; bool found = BinarySearchIf( @@ -1538,7 +1537,7 @@ class nsTArray_Impl [[nodiscard]] index_type IndexOfFirstElementGt( const Item& aItem, const Comparator& aComp) const { using mozilla::BinarySearchIf; - ::detail::CompareWrapper<Comparator, value_type, Item> comp(aComp); + ::detail::CompareWrapper<Comparator, Item> comp(aComp); size_t index; BinarySearchIf( @@ -2015,7 +2014,7 @@ class nsTArray_Impl typename mozilla::FunctionTypeTraits<FunctionElse>::ReturnType>, "ApplyIf's `Function` and `FunctionElse` must return the same type."); - ::detail::CompareWrapper<Comparator, value_type, Item> comp(aComp); + ::detail::CompareWrapper<Comparator, Item> comp(aComp); const value_type* const elements = Elements(); const value_type* const iend = elements + Length(); @@ -2036,7 +2035,7 @@ class nsTArray_Impl typename mozilla::FunctionTypeTraits<FunctionElse>::ReturnType>, "ApplyIf's `Function` and `FunctionElse` must return the same type."); - ::detail::CompareWrapper<Comparator, value_type, Item> comp(aComp); + ::detail::CompareWrapper<Comparator, Item> comp(aComp); value_type* const elements = Elements(); value_type* const iend = elements + Length(); @@ -2246,7 +2245,7 @@ class nsTArray_Impl static_assert(std::is_move_assignable_v<value_type>); static_assert(std::is_move_constructible_v<value_type>); - ::detail::CompareWrapper<Comparator, value_type, value_type> comp(aComp); + ::detail::CompareWrapper<Comparator, value_type> comp(aComp); auto compFn = [&comp](const auto& left, const auto& right) { return comp.LessThan(left, right); }; @@ -2272,8 +2271,7 @@ class nsTArray_Impl static_assert(std::is_move_assignable_v<value_type>); static_assert(std::is_move_constructible_v<value_type>); - const ::detail::CompareWrapper<Comparator, value_type, value_type> comp( - aComp); + const ::detail::CompareWrapper<Comparator, value_type> comp(aComp); auto compFn = [&comp](const auto& lhs, const auto& rhs) { return comp.LessThan(lhs, rhs); }; diff --git a/xpcom/tests/gtest/TestTArray2.cpp b/xpcom/tests/gtest/TestTArray2.cpp @@ -1448,9 +1448,8 @@ TEST(TArray, test_SetLengthAndRetainStorage_no_ctor) #undef RPAREN } -template <typename F, typename Comparator, typename ItemComparator> -bool TestCompareMethodsImpl(F aItemCreator, const Comparator& aComp, - const ItemComparator& aItemComp) { +template <typename Comparator> +bool TestCompareMethods(const Comparator& aComp) { nsTArray<int> ary({57, 4, 16, 17, 3, 5, 96, 12}); ary.Sort(aComp); @@ -1462,43 +1461,31 @@ bool TestCompareMethodsImpl(F aItemCreator, const Comparator& aComp, } } - if (!ary.ContainsSorted(aItemCreator(5), aItemComp)) { + if (!ary.ContainsSorted(5, aComp)) { return false; } - if (ary.ContainsSorted(aItemCreator(42), aItemComp)) { + if (ary.ContainsSorted(42, aComp)) { return false; } - if (ary.BinaryIndexOf(aItemCreator(16), aItemComp) != 4) { + if (ary.BinaryIndexOf(16, aComp) != 4) { return false; } return true; } -template <typename Comparator> -bool TestCompareMethods(const Comparator& aComp) { - return TestCompareMethodsImpl([](int aI) { return aI; }, aComp, aComp); -} - struct IntComparator { bool Equals(int aLeft, int aRight) const { return aLeft == aRight; } bool LessThan(int aLeft, int aRight) const { return aLeft < aRight; } }; -struct IntWrapper { - int mI; -}; - TEST(TArray, test_comparator_objects) { ASSERT_TRUE(TestCompareMethods(IntComparator())); ASSERT_TRUE( TestCompareMethods([](int aLeft, int aRight) { return aLeft - aRight; })); - ASSERT_TRUE(TestCompareMethodsImpl( - [](int aI) { return IntWrapper{.mI = aI}; }, IntComparator(), - [](int aElem, const IntWrapper& aItem) { return aElem - aItem.mI; })); } struct Big { diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp @@ -1230,25 +1230,25 @@ void TimerThread::PostTimerEvent(Entry& aPostMe) { // event, so we can avoid firing a timer that was re-initialized after being // canceled. + nsCOMPtr<nsIEventTarget> target = timer->mEventTarget; + void* p = nsTimerEvent::operator new(sizeof(nsTimerEvent)); if (!p) { return; } - - // We need to release mMonitor around the Dispatch because if the Dispatch - // or any Release of our objects interacts with the timer API we'll deadlock. - - nsCOMPtr<nsIEventTarget> lockedTargetPtr = timer->mEventTarget; - RefPtr<nsTimerEvent> lockedEventPtr = ::new (KnownNotNull, p) + RefPtr<nsTimerEvent> event = ::new (KnownNotNull, p) nsTimerEvent(timer.forget(), aPostMe.mTimerSeq, mProfilerThreadId); + { + // We release mMonitor around the Dispatch because if the Dispatch interacts + // with the timer API we'll deadlock. MonitorAutoUnlock unlock(mMonitor); - // Ensure references are released while we're unlocked. - nsCOMPtr<nsIEventTarget> target = lockedTargetPtr.forget(); - RefPtr<nsTimerEvent> event = lockedEventPtr.forget(); - // If we fail we have no way to report an error, but fallible dispatch - // will take care of releasing our event and timer. - target->Dispatch(event.forget(), NS_DISPATCH_FALLIBLE); + if (NS_WARN_IF(NS_FAILED(target->Dispatch(event, NS_DISPATCH_NORMAL)))) { + // Dispatch may fail for an already shut down target. In that case + // we can't do much about it but drop the timer. We already removed + // its reference from our book-keeping, anyways. + RefPtr<nsTimerImpl> dropMe = event->ForgetTimer(); + } } }