From 7adb8133f52696aab281d7dbbb58f13b94d395e0 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 22 Jun 2023 10:38:22 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - missing bit of 989499 and some other files (23b0597ba6) - Bug 1233666 - Remove hacks for getting frame pointer for x86/x64 gcc. r=glandium (bfe8f59916) - Bug 1176266: In TimeStamp_posix.cpp, check for XP_LINUX instead of LINUX, and add missing #include, to allow strrchr usage. r=BenWa (56c725cffa) - Bug 1167230 - Don't pack ProfileEntry on ARM. r=shu (89f880e0cb) - Bug 1209779 - Ensure that all null elements are written when streaming profiler JSON; r=shu (2bae5addc6) - missing bit of Bug 1141712 - Make LUL (55f1276545) - Bug 1061800 - Add breakpad ids to profiler in Linux. r=BenWa (994fd1a941) - Bug 829621 - Compute the breakpad-id for OS X. r=BenWa. (e129580174) - missing of Bug 938157 - Lightweight CFI/EXIDX (b355dc3140) - Bug 1193838 - Allow ProfileGatherer to gather profiles from exiting processes. r=BenWa (5ab1a6a3c9) - align some missing stuff (5ebecd2364) - align some missing stuff (b8ff7aa361) - Bug 1164315 - Update key fingerprint for bitbucket.org; r=me (c1a3fbd930) - Bug 1178955 - Refactor config path selection; r=smacleod (89552bb0ac) - Bug 1195445 - Update host key fingerprint for bugzilla.mozilla.org (3783541088) - Bug 1218903 - Update bmo fingerprint. r=fubar, a=Tomcat (5b836fc585) - Bug 1178955 - Print config path on failure; r=smacleod (f5499f3771) - Bug 1185113 - Support setting more secure file permissions; r=smacleod (7dbf6b22fd) - Bug 1184229 - Detect multiple version-control-tools repos in Mercurial config; r=smacleod (16c24072a9) - Bug 978514 - mach mercurial-setup: Use mqext from the version-control-tools repo (1fa5765e8a) - Bug 1178955 - Don't pass config paths to updater; r=smacleod (80fcb05121) - Bug 1197527 - Don't unnecessarily attempt to create extensions directory in MercurialUpdater; r=gps (9b049c3ff8) - Bug 1164812 - mach mercurial-setup: Always mark the v-c-t repo as needing update (bd631208bd) - Bug 1197527 - Always clone version-control-tools in MercurialSetupWizard; r=gps (6990e8f589) - Bug 1197527 - Consolidate obtaining hg path into mozversioncontrol.get_hg_path; r=gps (e0b029a8e9) - Bug 1200458 - Skip permission check for .hgrc on Windows in hgsetup wizard. r=gps DONTBUILD (cc5b0d6daf) - Bug 1168466 - Bump minimum Mercurial version; r=smacleod (7fde47cfbe) - Bug 1185113 - Clarify language around Bugzilla credentials; r=smacleod (9166fdfbf9) - Bug 1185112 - Don't prompt for Bugzilla username/password if cookies defined; r=smacleod (cd87c96823) - Bug 1188931 - Fix hgsetup wizard. r=gps (68a6b46be4) - Bug 1200461 - Prompt for Bugzilla API Key instead of password; r=smacleod (780fb5d85d) - Bug 1228580 - ./mach mercurial-setup should use ~ to set up extension paths, not my literal home directory. r=gps (7a0c839880) - Bug 1231192 - Mark Mercurial 3.5.2 as oldest non-legacy version; r=smacleod (8f69483333) - Bug 1231192 - Bump some minimum Mercurial version; r=smacleod (916c56a852) - Bug 1162093 - Add "push-to-try" from version-control-tools to the mercurial setup wizard prompt.;r=gps (d29c7cf63a) - Bug 1168466 - Prompt to install bundleclone extension; r=smacleod (780ce90a08) - Bug 1185557 - Print relevant config options; r=smacleod (2f3f7e0161) - Bug 1231192 - Support clonebundles feature; r=smacleod (dcba1ccd34) - Bug 1231192 - Offer to install hg wip; r=smacleod (c42ebce5c8) - Bug 1231192 - Only install host fingerprints if not running secure Python+hg; r=smacleod (3154a2497b) - Bug 1178955 - Error when semicolon comments are seen; r=smacleod (e1f7081bb6) - Bug 1231989 - Prompt to install hgwatchman extension; r=ahal (0eddf0c1c8) - Bug 1178955 - Print line number for parse errors; r=smacleod (5369468cf1) - Bug 1185557 - Only prompt to install progress on Mercurial <3.5; r=smacleod (401f362265) - Bug 1232747 - Check for ssl.SSLContext existence; r=dminor (d505b07c5c) - Bug 1144629 - UnicodeDecodeError in ./mach mercurial-setup. r=gps, r=glandium (611d3ec83e) - Bug 1216970 - Make the copying more obvious in ProfilerImpl::GetStacktrace. r=froydnj (085625e113) - Bug 1190466 - tools/rb/find-leakers.pl re-written in Python r=mccr8 (4bfdcad13e) - Bug 1116478 - Open web content handlers in the proper tab in e10s. r=billm (ff8e11f45e) - Bug 1213437 - Less data copying when handling structured clones in MessageManager, r=baku (c4e2a13253) - const-var (69d17f312d) - Bug 1203090 - Ensure we always use '/' as the starting path separator for the DOM path of the Directorys initially returned by HTMLInputElement.getFilesAndDirectories. r=baku (1325bbc40c) - Bug 1209975 - Stop using dom::Promise::MaybeRejectBrokenly() in GetDirectoryListingTask. r=baku (2106790950) - Bug 1209924 - Implement a general filtering mechanism for Directory::GetFilesAndDirectories, and add filtering of sensitive files/directories. r=baku (27b4a26262) - Force a repaint after DXGI device resets. (bug 1188019, r=bas) (09c999e6e5) - Bug 1163911 - Make responsive images block the document load event while the load task is queued. r=jst (0ee0e3db79) - Bug 1166138 - Make img srcset react to resize/viewport changes, r=jdm (91674519e6) - Bug 1194893 - Pref for default file upload directory. r=smaug (ec6d33d983) - bug 1116409: switch update server to sha2 cert; update in-tree pinning. r=rstrong,snorp,mfinkle,dkeeler (7c8f631f27) - bug 1116409: fix cert pinning on backup cert for aus5.mozilla.org. r=typofix (3c690cbc6d) - Bug 1167048 - Change default font for Thai script from serif to sans-serif. r=smontagu (15dc86c389) - Bug 1205570 - fix up font prefs for x-math lang group. r=heycam (03f1820752) - Bug 1071769: Use DrawTargetTiled on B2G. r=Bas (b80ce768f1) - fix misspatch of 1149343 (541dd7aac8) - Bug 1199766 - Disable ICE TCP SO gathering via user pref. r=bwc (80cdc9c662) - Bug 1187472 - only log UDP and TCP candidate gathering failures. r=bwc (bc3dcb02d0) - Bug 1190615 - Skip non-UDP STUN servers for UDP sockets. r=bwc (a2d1d914b5) - Bug 1187775 - skip host and reflexive ICE candidates if relay-only. r=bwc (7e2cba1685) - Bug 1185198 - use port 9 for TCP active candidates. r=bwc (0a89cb199d) - Bug 1177921 - Fix typo in STUN server name. r=drno (1ad43ced6b) - Bug 1178349 - Enable ice_unittests on desktop linux on CI. r=bwc (ce5ece8264) - Bug 1189041 - Add option to only gather addresses for default route. r=bwc (3651f2ff06) - Bug 1189040: add a whitelist for network interfaces to use with ICE/webrtc r=ekr (6f693af72c) - Bug 1189198 - don't start STUN transactions with a protocol mis-match. r=mtseng (a3b410e2a8) - Bug 1208096 - Handle various failure cases for TURN gathering better. r=drno (1d8e173448) - Bug 1211389 - Make absolutely sure the relay->srflx pointer doesn't dangle. r=drno (d59b0bf08d) - Bug 1215616: use base address for server rflx ICE candidates r=bwc (89d07331ac) - Bug 1207451 - removed framing from multi_tcp API. r=bwc (317f40f490) - Bug 1186590 - Part 1 - Enable interface prioritizer on all platforms. r=drno (036a69fdb3) - Bug 1194019 - New defaults for gather tests. r=bwc (8343ceab56) - Bug 1144933: Only check that remote candidate is loopback in TestLoopbackOnlySortOf. r=drno (1f53d824e4) - Bug 1186590 - Part 2 - Move hard-coded interface priority list into nrinterfaceprioritizer, and simplify some functions. r=drno (9f20fad21b) - Bug 1152137 - Part 1: Test case. r=ekr (6b50f06d90) - Bug 1152137 - Part 2: Remove attributes that could not be initted properly instead of just freeing them. r=ekr (ccdf81294a) - Bug 1200763 - Remove hard-coded STUN IP address from ice_unittest, and do a DNS lookup instead. r=drno (ae54a83363) - Bug 1208176 - Part 1: Add a couple of interface names. r=drno (b7ead0b476) - Bug 1208176 - Part 2: Add a one-sided trickle test case to ice_unittest. r=drno (ad6afedb1c) - Bug 1037618 - Relax candidate verification for TCP. r=bwc (0cad14c89e) - Bug 1208176 - Part 3: Be forgiving when we see prflx instead of host candidates in ice_unittest. r=drno (50bdec2ba3) - Bug 1035428: Re-register writeable callback after partially servicing the send queue. r=drno (2fdb7880fa) - Bug 1135753 - Mark some overridden virtual functions in WebRTC as MOZ_OVERRIDE; r=mt (97f451c97d) - Bug 950660: Part 4: Bridge TCPSocketChild to nr_socket r=bwc,jdm (654587b321) - Bug 971357: Log STUN responses at INFO instead of DEBUG. r=ekr (81b500df17) - Bug 1006809 - update triggered check behavior to RFC 5245. r+bwc r=mjf (31b718b5e5) - Bug 1208278 - improved STUN request timeout handling. r=bwc (cf470fb12f) - Bug 1142964 - Fix ICE tiebreaker on Windows. r=bwc (0d2fd78252) - Bug 1219557 - don't pair candidates from different reserved networks. r=mt r=bwc (24d3e5106c) - Bug 1220441 - Improve gather trickle ice unit tests. r=bwc r=mjf (96f76c6c8c) - Bug 1205421 - fix DNS resolution of STUN server in ice_unittest. r=bwc (5d5b153358) - Bug 1206465 - removed ice_ctx from TestStunTcpServer. r=bwc (9a0df03894) - ug 1008792 - Check for valid pointer before using. r=bwc (7660fd0a71) - Bug 1233101 - Use MOZ_LIKELY in js_new etc to help branch prediction; r=terrence (31fb244734) - Bug 1225565 - Fix module import cycle detection r=shu (370dc26ee8) - Bug 1225558 - Improve module error messages r=shu (83b6038bb3) - Bug 1225561 - Don't allow a module to export non-existent local bindings r=shu (41f065891a) - Bug 1233124 - Remove mis-named duplicate typedefs for rooted import and export entries r=terrence (17a60bdb39) - Bug 1208464 - Implement proposed ES7 functions Object.values and Object.entries. r=evilpie (615193d0fb) - Bug 1226549 - added assert check for matches pointer in for prevent null dereference. r=hv1989 (d321ad0385) - Bug 1232113 - "Make the format specifiers in JS_snprintf() invocations more portable". r=jcoppeard (7c58b79a53) - Bug 1232446 - Re-enable method calls in SelfHosted code using new anti-content checks. (r=till) (ba7dc22ff8) - Bug 1232159 - Stop using pseudo-Uint32Array in SelfHosted code. (r=till) (c325f8ff58) - Bug 1226235 - Print file and line info for failing assert in self-hosted code. r=efaust (4a8d54d38b) - fix misspatch (fca2efc1f1) - Bug 1186003 - Switch automated builds to Gtk+3. r=mshal (658ad843b7) - Bug 1181342 - tooltool manifests and build-clang config for clang 3.6 r=rail (6264b4df68) - Bug 1181342 - Follow up to use the unpack feature of tooltool instead of setup.sh r=glandium (96bb3b2062) - Bug 1181255 - Mozconfigs for tsan builds. r=glandium (19250f4cc1) - Bug 1181255 - Get tsan builds on gtk3. r=glandium (e5ffd1c02f) - Bug 1187664 - Create a fontconfig cache so that Firefox doesn't have to do it itself when run on build automation. r=mshal (8ce567bd4c) - Bug 1188780 - Include debug symbols in gtk3 tooltool package. r=mshal (a5b573aa58) - Bug 1188780: remove setup.sh invocations, as they fail outside the mock environment; r=glandium a=RyanVM (058e306cac) - Bug 1178513 - Fix non-unified bustage. r=wchen (543d1e5497) - Bug 1162789 - Add a comment explaining why mForm is not set to null during unlink (eaa2a82048) - Bug 1189655 - Define MOZ_HAVE_CXX11_CONSTEXPR on VS2015 or later. r=Waldo (2d134e3b41) - Bug 1231758 - Fix bogus assertion in BCE for Annex B function assignment. (r=jorendorff) (701b2530b9) - Bug 1233100 - Ensure that derived constructor bad return value errors are thrown before leaving the containing block. (r=shu) (52f5bcf0a5) - Bug 1232022, 1232449 - Address forgotten review nits and fix bogus error message. (rs=Waldo) (dfd9d5e388) - Bug 1233121 - Refactor ObjectBox tracing r=terrence (876a140535) - Bug 1231647 - Check for duplicate exported let and const in modules r=shu (99f53ad443) --- b2g/app/b2g.js | 2 +- browser/config/mozconfigs/linux64/opt-tsan | 11 + .../linux32/releng.manifest | 7 + .../linux64/releng.manifest | 7 + .../tooltool-manifests/linux64/tsan.manifest | 19 + build/unix/build-clang/clang-3.6.json | 8 + build/unix/build-gtk3/build-gtk3.sh | 146 ++++ build/unix/mozconfig.tsan | 34 + dom/base/moz.build | 1 + dom/base/nsIRemoteWindowContext.idl | 15 + dom/events/DataTransfer.cpp | 4 +- dom/filesystem/Directory.cpp | 8 +- dom/filesystem/Directory.h | 26 + dom/filesystem/GetDirectoryListingTask.cpp | 48 +- dom/filesystem/GetDirectoryListingTask.h | 2 + dom/html/HTMLExtAppElement.cpp | 6 +- dom/html/HTMLFormControlsCollection.cpp | 3 + dom/html/HTMLImageElement.cpp | 93 ++- dom/html/HTMLImageElement.h | 9 +- dom/html/HTMLInputElement.cpp | 42 +- dom/html/HTMLTrackElement.cpp | 2 +- dom/html/test/file_bug1166138_1x.png | Bin 0 -> 91 bytes dom/html/test/file_bug1166138_2x.png | Bin 0 -> 100 bytes dom/html/test/file_bug1166138_def.png | Bin 0 -> 85 bytes dom/html/test/mochitest.ini | 50 +- dom/html/test/test_bug1166138.html | 130 ++++ .../test_filepicker_default_directory.html | 83 ++ dom/ipc/ContentChild.cpp | 9 + dom/ipc/ContentParent.cpp | 56 +- dom/ipc/ContentParent.h | 3 +- dom/ipc/PBrowser.ipdl | 2 + dom/ipc/PContent.ipdl | 12 +- dom/ipc/StructuredCloneData.cpp | 73 +- dom/ipc/StructuredCloneData.h | 83 +- dom/ipc/TabChild.cpp | 27 + dom/ipc/TabChild.h | 2 + dom/ipc/preload.js | 4 +- dom/media/tests/mochitest/mochitest.ini | 2 + dom/media/tests/mochitest/pc.js | 19 +- .../test_peerConnection_relayOnly.html | 61 ++ dom/plugins/ipc/PluginModuleParent.cpp | 3 +- .../mochitest/general/test_img_mutations.html | 33 +- gfx/thebes/gfxFontPrefLangList.h | 1 + gfx/thebes/gfxPlatform.h | 6 + gfx/thebes/gfxWindowsPlatform.cpp | 24 + gfx/thebes/gfxWindowsPlatform.h | 3 +- js/public/Utility.h | 8 +- js/src/builtin/Module.js | 14 +- js/src/builtin/ModuleObject.cpp | 59 +- js/src/builtin/ModuleObject.h | 20 +- js/src/builtin/Object.cpp | 4 + js/src/builtin/Object.js | 45 ++ js/src/builtin/RegExp.cpp | 1 + js/src/builtin/TestingFunctions.cpp | 4 +- js/src/builtin/TypedObject.js | 14 +- js/src/builtin/Utilities.js | 2 +- js/src/devtools/rootAnalysis/annotations.js | 3 - .../devtools/rootAnalysis/build/gcc.manifest | 4 +- js/src/frontend/BytecodeCompiler.cpp | 4 +- js/src/frontend/BytecodeEmitter.cpp | 79 +- js/src/frontend/BytecodeEmitter.h | 1 + js/src/frontend/ParseNode.cpp | 40 +- js/src/frontend/ParseNode.h | 4 +- js/src/frontend/Parser.cpp | 48 +- js/src/frontend/Parser.h | 1 + js/src/frontend/SharedContext.h | 4 + js/src/jit-test/modules/ambiguous.js | 2 + js/src/jit-test/modules/module1a.js | 1 + .../jit-test/modules/recursiveStarExport.js | 1 + .../jit-test/tests/TypedObject/bug1232159.js | 13 + .../tests/modules/ambiguous-import.js | 2 + .../modules/ambiguous-indirect-export.js | 2 + .../tests/modules/duplicate-exports.js | 25 +- .../tests/modules/import-not-found.js | 2 + .../tests/modules/missing-indirect-export.js | 2 + .../tests/modules/recursive-star-export.js | 2 + .../jit-test/tests/modules/unbound-export.js | 2 + js/src/js.msg | 10 +- js/src/shell/js.cpp | 3 +- .../Class/derivedConstructorTDZReturnTry.js | 16 + js/src/tests/ecma_7/Object/browser.js | 0 js/src/tests/ecma_7/Object/entries.js | 94 +++ js/src/tests/ecma_7/Object/shell.js | 0 js/src/tests/ecma_7/Object/values.js | 94 +++ js/src/vm/GlobalObject.cpp | 1 - js/src/vm/Interpreter.cpp | 2 +- js/src/vm/Xdr.h | 2 +- layout/base/nsRefreshDriver.cpp | 2 + media/mtransport/build/moz.build | 2 + media/mtransport/common.build | 4 - media/mtransport/moz.build | 2 + media/mtransport/nr_socket_prsock.cpp | 714 +++++++++++++++--- media/mtransport/nr_socket_prsock.h | 136 +++- media/mtransport/nricectx.cpp | 59 +- media/mtransport/nricectx.h | 3 +- media/mtransport/nricemediastream.cpp | 33 +- media/mtransport/nriceresolver.cpp | 2 +- media/mtransport/nriceresolver.h | 8 +- media/mtransport/nrinterfaceprioritizer.cpp | 79 +- media/mtransport/standalone/moz.build | 1 + media/mtransport/test/ice_unittest.cpp | 546 ++++++++++++-- media/mtransport/test/moz.build | 1 - .../test/multi_tcp_socket_unittest.cpp | 155 ++-- media/mtransport/test/stunserver.cpp | 163 ++-- media/mtransport/test/stunserver.h | 36 +- media/mtransport/test_nr_socket.cpp | 6 +- media/mtransport/testlib/moz.build | 1 + .../third_party/nICEr/src/ice/ice_candidate.c | 120 +-- .../third_party/nICEr/src/ice/ice_candidate.h | 3 + .../nICEr/src/ice/ice_candidate_pair.c | 127 +++- .../nICEr/src/ice/ice_candidate_pair.h | 8 +- .../third_party/nICEr/src/ice/ice_component.c | 435 ++++++----- .../third_party/nICEr/src/ice/ice_ctx.c | 160 +++- .../third_party/nICEr/src/ice/ice_ctx.h | 5 + .../nICEr/src/ice/ice_media_stream.c | 109 ++- .../nICEr/src/ice/ice_media_stream.h | 1 + .../third_party/nICEr/src/ice/ice_reg.h | 2 + .../third_party/nICEr/src/net/nr_resolver.h | 2 +- .../nICEr/src/net/nr_socket_multi_tcp.c | 11 +- .../nICEr/src/net/nr_socket_multi_tcp.h | 3 +- .../nICEr/src/net/transport_addr.c | 56 +- .../nICEr/src/net/transport_addr.h | 10 +- .../nICEr/src/stun/nr_socket_buffered_stun.c | 95 ++- .../third_party/nICEr/src/stun/stun_build.c | 2 +- .../third_party/nICEr/src/stun/stun_build.h | 12 +- .../nICEr/src/stun/stun_client_ctx.c | 138 ++-- .../nICEr/src/stun/stun_client_ctx.h | 6 +- .../third_party/nICEr/src/stun/stun_codec.c | 5 +- .../third_party/nICEr/src/stun/stun_msg.c | 4 +- .../nICEr/src/stun/turn_client_ctx.c | 21 +- .../nICEr/src/stun/turn_client_ctx.h | 1 + .../src/peerconnection/PeerConnectionImpl.cpp | 6 - .../peerconnection/PeerConnectionMedia.cpp | 6 +- modules/libpref/init/all.js | 29 +- mozglue/misc/StackWalk.cpp | 10 +- mozglue/misc/TimeStamp_posix.cpp | 3 +- .../mozversioncontrol/__init__.py | 21 + security/manager/tools/PreloadedHPKPins.json | 236 ++++++ tools/memory-profiler/MemoryProfiler.cpp | 2 +- tools/mercurial/hgsetup/config.py | 128 +++- tools/mercurial/hgsetup/update.py | 69 +- tools/mercurial/hgsetup/wizard.py | 339 +++++++-- tools/mercurial/mach_commands.py | 2 +- tools/profiler/core/EHABIStackWalk.cpp | 678 +++++++++++++++++ tools/profiler/core/EHABIStackWalk.h | 28 + tools/profiler/core/GeckoSampler.cpp | 75 +- tools/profiler/core/GeckoSampler.h | 1 + tools/profiler/core/ProfileEntry.cpp | 235 +++--- tools/profiler/core/ProfileEntry.h | 5 + tools/profiler/core/ProfileJSONWriter.cpp | 7 +- tools/profiler/core/StackTop.cpp | 48 ++ tools/profiler/core/SyncProfile.cpp | 1 - tools/profiler/core/SyncProfile.h | 1 + tools/profiler/core/platform-linux.cc | 9 +- tools/profiler/core/platform-win32.cc | 1 + tools/profiler/core/platform.cpp | 54 +- tools/profiler/core/platform.h | 27 +- tools/profiler/core/shared-libraries-linux.cc | 42 +- tools/profiler/core/shared-libraries-macos.cc | 29 +- tools/profiler/gecko/ProfileGatherer.cpp | 62 +- .../gecko/ProfilerIOInterposeObserver.h | 20 + tools/profiler/gecko/ThreadResponsiveness.cpp | 1 + tools/profiler/public/ProfileGatherer.h | 5 +- tools/rb/find-leakers.pl | 62 -- tools/rb/find_leakers.py | 100 +++ .../exthandler/nsExternalHelperAppService.cpp | 4 +- uriloader/exthandler/nsWebHandlerApp.js | 13 + 167 files changed, 5997 insertions(+), 1465 deletions(-) create mode 100644 browser/config/mozconfigs/linux64/opt-tsan create mode 100644 browser/config/tooltool-manifests/linux64/tsan.manifest create mode 100644 build/unix/build-clang/clang-3.6.json create mode 100644 build/unix/build-gtk3/build-gtk3.sh create mode 100644 build/unix/mozconfig.tsan create mode 100644 dom/base/nsIRemoteWindowContext.idl create mode 100644 dom/html/test/file_bug1166138_1x.png create mode 100644 dom/html/test/file_bug1166138_2x.png create mode 100644 dom/html/test/file_bug1166138_def.png create mode 100644 dom/html/test/test_bug1166138.html create mode 100644 dom/html/test/test_filepicker_default_directory.html create mode 100644 dom/media/tests/mochitest/test_peerConnection_relayOnly.html create mode 100644 js/src/jit-test/modules/ambiguous.js create mode 100644 js/src/jit-test/modules/module1a.js create mode 100644 js/src/jit-test/modules/recursiveStarExport.js create mode 100644 js/src/jit-test/tests/TypedObject/bug1232159.js create mode 100644 js/src/jit-test/tests/modules/ambiguous-import.js create mode 100644 js/src/jit-test/tests/modules/ambiguous-indirect-export.js create mode 100644 js/src/jit-test/tests/modules/import-not-found.js create mode 100644 js/src/jit-test/tests/modules/missing-indirect-export.js create mode 100644 js/src/jit-test/tests/modules/recursive-star-export.js create mode 100644 js/src/jit-test/tests/modules/unbound-export.js create mode 100644 js/src/tests/ecma_6/Class/derivedConstructorTDZReturnTry.js create mode 100644 js/src/tests/ecma_7/Object/browser.js create mode 100644 js/src/tests/ecma_7/Object/entries.js create mode 100644 js/src/tests/ecma_7/Object/shell.js create mode 100644 js/src/tests/ecma_7/Object/values.js create mode 100644 security/manager/tools/PreloadedHPKPins.json create mode 100644 tools/profiler/core/EHABIStackWalk.cpp create mode 100644 tools/profiler/core/EHABIStackWalk.h create mode 100644 tools/profiler/core/StackTop.cpp delete mode 100755 tools/rb/find-leakers.pl create mode 100644 tools/rb/find_leakers.py diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 11d95f37d2..d3680543d4 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -562,7 +562,7 @@ pref("app.update.incompatible.mode", 0); pref("app.update.staging.enabled", true); pref("app.update.service.enabled", true); -pref("app.update.url", "https://aus4.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%PRODUCT_DEVICE%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml"); +pref("app.update.url", "https://aus5.mozilla.org/update/3/%PRODUCT%/%VERSION%/%BUILD_ID%/%PRODUCT_DEVICE%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml"); pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@"); // Interval at which update manifest is fetched. In units of seconds. diff --git a/browser/config/mozconfigs/linux64/opt-tsan b/browser/config/mozconfigs/linux64/opt-tsan new file mode 100644 index 0000000000..872a0ca69f --- /dev/null +++ b/browser/config/mozconfigs/linux64/opt-tsan @@ -0,0 +1,11 @@ +ac_add_options --with-google-oauth-api-keyfile=/builds/google-oauth-api.key + +. $topsrcdir/build/unix/mozconfig.tsan + +export PKG_CONFIG_LIBDIR=/usr/lib64/pkgconfig:/usr/share/pkgconfig +. $topsrcdir/build/unix/mozconfig.gtk + +# Need this to prevent name conflicts with the normal nightly build packages +export MOZ_PKG_SPECIAL=tsan + +. "$topsrcdir/build/mozconfig.common.override" diff --git a/browser/config/tooltool-manifests/linux32/releng.manifest b/browser/config/tooltool-manifests/linux32/releng.manifest index 80c50dfaea..47465cfa08 100644 --- a/browser/config/tooltool-manifests/linux32/releng.manifest +++ b/browser/config/tooltool-manifests/linux32/releng.manifest @@ -7,6 +7,13 @@ "unpack": true }, { +"size": 11179576, +"digest": "91567ce8e2bb8ab0ebc60c31e90731d88a1ea889fb71bcf55c735746a60fa7610b7e040ea3d8f727b6f692ae3ee703d6f3b30cdbd76fdf5617f77d9c38aa20ed", +"algorithm": "sha512", +"filename": "gtk3.tar.xz", +"unpack": true +}, +{ "size": 167175, "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831", "algorithm": "sha512", diff --git a/browser/config/tooltool-manifests/linux64/releng.manifest b/browser/config/tooltool-manifests/linux64/releng.manifest index 80c50dfaea..bb7b72d7cd 100644 --- a/browser/config/tooltool-manifests/linux64/releng.manifest +++ b/browser/config/tooltool-manifests/linux64/releng.manifest @@ -7,6 +7,13 @@ "unpack": true }, { +"size": 12057960, +"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e", +"algorithm": "sha512", +"filename": "gtk3.tar.xz", +"unpack": true +}, +{ "size": 167175, "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831", "algorithm": "sha512", diff --git a/browser/config/tooltool-manifests/linux64/tsan.manifest b/browser/config/tooltool-manifests/linux64/tsan.manifest new file mode 100644 index 0000000000..4bb5c3fd37 --- /dev/null +++ b/browser/config/tooltool-manifests/linux64/tsan.manifest @@ -0,0 +1,19 @@ +[ +{ +"clang_version": "r241773" +}, +{ +"size": 89690541, +"digest": "470d258d9785a120fcba65eee90daa632a42affa0f97f57d70fc8285bd76bcc27d4d0d70b6c37577ab271a04c843b6269425391a8d6df1967718dba26dd3a73d", +"algorithm": "sha512", +"filename": "clang.tar.bz2", +"unpack": true +}, +{ +"size": 12057960, +"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e", +"algorithm": "sha512", +"filename": "gtk3.tar.xz", +"unpack": true +} +] diff --git a/build/unix/build-clang/clang-3.6.json b/build/unix/build-clang/clang-3.6.json new file mode 100644 index 0000000000..66a3b51103 --- /dev/null +++ b/build/unix/build-clang/clang-3.6.json @@ -0,0 +1,8 @@ +{ + "llvm_revision": "241773", + "llvm_repo": "http://llvm.org/svn/llvm-project/llvm/branches/release_36", + "clang_repo": "http://llvm.org/svn/llvm-project/cfe/branches/release_36", + "compiler_repo": "http://llvm.org/svn/llvm-project/compiler-rt/branches/release_36", + "patches": { + } +} diff --git a/build/unix/build-gtk3/build-gtk3.sh b/build/unix/build-gtk3/build-gtk3.sh new file mode 100644 index 0000000000..9e8d5c0c8f --- /dev/null +++ b/build/unix/build-gtk3/build-gtk3.sh @@ -0,0 +1,146 @@ +#!/bin/bash + +# Use "build-gtk.sh" or "build-gtk.sh 64" to build a 64-bits tarball for tooltool. +# Use "build-gtk.sh 32" to build a 32-bits tarball for tooltool. + +# Mock environments used: +# - 64-bits: +# https://s3.amazonaws.com/mozilla-releng-mock-archive/67b65e51eb091fba7941a04d249343924a3ee653 +# + libxml2-devel.x86_64 gettext.x86_64 libjpeg-devel.x86_64 +# - 32-bits: +# https://s3.amazonaws.com/mozilla-releng-mock-archive/58d76c6acca148a1aedcbec7fc1b8212e12807b4 +# + libxml2-devel.i686 gettext.i686 libjpeg-devel.i686 + +set -e + +pkg_config_version=0.28 +fontconfig_version=2.8.0 +libffi_version=3.0.13 +glib_version=2.34.3 +gdk_pixbuf_version=2.26.5 +pixman_version=0.20.2 +cairo_version=1.10.2 +pango_version=1.30.1 +atk_version=2.2.0 +gtk__version=3.4.4 + +pkg_config_url=http://pkgconfig.freedesktop.org/releases/pkg-config-${pkg_config_version}.tar.gz +fontconfig_url=http://www.freedesktop.org/software/fontconfig/release/fontconfig-${fontconfig_version}.tar.gz +libffi_url=ftp://sourceware.org/pub/libffi/libffi-${libffi_version}.tar.gz +glib_url=http://ftp.gnome.org/pub/gnome/sources/glib/${glib_version%.*}/glib-${glib_version}.tar.xz +gdk_pixbuf_url=http://ftp.gnome.org/pub/gnome/sources/gdk-pixbuf/${gdk_pixbuf_version%.*}/gdk-pixbuf-${gdk_pixbuf_version}.tar.xz +pixman_url=http://cairographics.org/releases/pixman-${pixman_version}.tar.gz +cairo_url=http://cairographics.org/releases/cairo-${cairo_version}.tar.gz +pango_url=http://ftp.gnome.org/pub/GNOME/sources/pango/${pango_version%.*}/pango-${pango_version}.tar.xz +atk_url=http://ftp.gnome.org/pub/GNOME/sources/atk/${atk_version%.*}/atk-${atk_version}.tar.xz +gtk__url=http://ftp.gnome.org/pub/gnome/sources/gtk+/${gtk__version%.*}/gtk+-${gtk__version}.tar.xz + +cwd=$(pwd) +root_dir=$(mktemp -d) +cd $root_dir + +if test -z $TMPDIR; then + TMPDIR=/tmp/ +fi + +make_flags=-j12 + +build() { + name=$1 + shift + pkg=$(echo $name | tr '+-' '__') + version=$(eval echo \$${pkg}_version) + url=$(eval echo \$${pkg}_url) + wget -c -P $TMPDIR $url + tar -axf $TMPDIR/$name-$version.tar.* + mkdir -p build/$name + cd build/$name + eval ../../$name-$version/configure --disable-static $* $configure_args + make $make_flags + make install DESTDIR=$root_dir/gtk3 + find $root_dir/gtk3 -name \*.la -delete + cd ../.. +} + +case "$1" in +32) + configure_args='--host=i686-pc-linux --build=i686-pc-linux CC="gcc -m32" CXX="g++ -m32"' + lib=lib + ;; +*) + configure_args= + lib=lib64 + ;; +esac + +export PKG_CONFIG_LIBDIR=/usr/$lib/pkgconfig:/usr/share/pkgconfig + +# The pkg-config version in the mock images is buggy is how it handles +# PKG_CONFIG_SYSROOT_DIR. So we need our own. +build pkg-config + +ln -sf /usr/include $root_dir/gtk3/usr/ +ln -sf /usr/$lib $root_dir/gtk3/usr/ +if [ "$lib" = lib64 ]; then + ln -s lib $root_dir/gtk3/usr/local/lib64 +fi +export PKG_CONFIG_PATH=$root_dir/gtk3/usr/local/lib/pkgconfig +export PKG_CONFIG_SYSROOT_DIR=$root_dir/gtk3 +export LD_LIBRARY_PATH=$root_dir/gtk3/usr/local/lib +export PATH=$root_dir/gtk3/usr/local/bin:${PATH} + +build fontconfig +build libffi +build glib +build gdk-pixbuf --without-libtiff +build pixman --disable-gtk +build cairo --enable-tee +build pango +build atk +make_flags="$make_flags GLIB_COMPILE_SCHEMAS=glib-compile-schemas" +build gtk+ + +rm -rf $root_dir/gtk3/usr/local/share/gtk-doc +rm -rf $root_dir/gtk3/usr/local/share/locale + +# mock build environment doesn't have fonts in /usr/share/fonts, but +# has some in /usr/share/X11/fonts. Add this directory to the +# fontconfig configuration without changing the gtk3 tooltool package. +cat << EOF > $root_dir/gtk3/usr/local/etc/fonts/local.conf + + + + /usr/share/X11/fonts + +EOF + +cat < $root_dir/gtk3/setup.sh +#!/bin/sh + +cd \$(dirname \$0) + +# pango expects absolute paths in pango.modules, and TOOLTOOL_DIR may vary... +LD_LIBRARY_PATH=./usr/local/lib \ +PANGO_SYSCONFDIR=./usr/local/etc \ +PANGO_LIBDIR=./usr/local/lib \ +./usr/local/bin/pango-querymodules > ./usr/local/etc/pango/pango.modules + +# same with gdb-pixbuf and loaders.cache +LD_LIBRARY_PATH=./usr/local/lib \ +GDK_PIXBUF_MODULE_FILE=./usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache \ +GDK_PIXBUF_MODULEDIR=./usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders \ +./usr/local/bin/gdk-pixbuf-query-loaders > ./usr/local/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache + +# The fontconfig version in the tooltool package has known uses of +# uninitialized memory when creating its cache, and while most users +# will already have an existing cache, running Firefox on automation +# will create it. Combined with valgrind, this generates irrelevant +# errors. +# So create the fontconfig cache beforehand. +./usr/local/bin/fc-cache +EOF + +chmod +x $root_dir/gtk3/setup.sh + +cd $cwd +tar -C $root_dir -Jcf gtk3.tar.xz gtk3 diff --git a/build/unix/mozconfig.tsan b/build/unix/mozconfig.tsan new file mode 100644 index 0000000000..689f8fc188 --- /dev/null +++ b/build/unix/mozconfig.tsan @@ -0,0 +1,34 @@ +MOZ_AUTOMATION_L10N_CHECK=0 + +. "$topsrcdir/build/mozconfig.common" + +# Use Clang as specified in manifest +export CC="$topsrcdir/clang/bin/clang" +export CXX="$topsrcdir/clang/bin/clang++" +export LLVM_SYMBOLIZER="$topsrcdir/clang/bin/llvm-symbolizer" + +# Mandatory flag for TSan +export CFLAGS="-fsanitize=thread" +export CXXFLAGS="-fsanitize=thread" +export LDFLAGS="-fsanitize=thread" + +# Enable TSan specific code and build workarounds +ac_add_options --enable-thread-sanitizer + +# The ThreadSanitizer is not compatible with sanboxing +# (see bug 1182565) +ac_add_options --disable-sandbox + +# These are required by TSan +ac_add_options --disable-jemalloc +ac_add_options --disable-crashreporter +ac_add_options --disable-elf-hack +ac_add_options --enable-pie + +# Keep symbols to symbolize TSan traces +ac_add_options --disable-install-strip +# -gline-tables-only results in significantly smaller binaries. +ac_add_options --enable-debug-symbols="-gline-tables-only" + +# Avoid dependency on libstdc++ 4.7 +ac_add_options --enable-stdcxx-compat diff --git a/dom/base/moz.build b/dom/base/moz.build index a4b1a5bddc..3c5ba1d8d7 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -25,6 +25,7 @@ XPIDL_SOURCES += [ 'nsIImageLoadingContent.idl', 'nsIMessageManager.idl', 'nsIObjectLoadingContent.idl', + 'nsIRemoteWindowContext.idl', 'nsIScriptChannel.idl', 'nsIScriptLoaderObserver.idl', 'nsISelection.idl', diff --git a/dom/base/nsIRemoteWindowContext.idl b/dom/base/nsIRemoteWindowContext.idl new file mode 100644 index 0000000000..0ef63754de --- /dev/null +++ b/dom/base/nsIRemoteWindowContext.idl @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +[scriptable, builtinclass, uuid(94f4a92b-752e-4fd9-8345-11b069ca19f3)] +interface nsIRemoteWindowContext : nsISupports +{ + void openURI(in nsIURI aURI, in uint32_t aFlags); +}; diff --git a/dom/events/DataTransfer.cpp b/dom/events/DataTransfer.cpp index c827d11a46..cb487269e5 100644 --- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -930,7 +930,9 @@ DataTransfer::GetFilesAndDirectories(ErrorResult& aRv) nsDependentSubstring dirname = Substring(path, 0, leafSeparatorIndex); nsDependentSubstring basename = Substring(path, leafSeparatorIndex); fs = MakeOrReuseFileSystem(dirname, fs, window); - filesAndDirsSeq[i].SetAsDirectory() = new Directory(fs, basename); + RefPtr directory = new Directory(fs, basename); + directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive")); + filesAndDirsSeq[i].SetAsDirectory() = directory; } else { filesAndDirsSeq[i].SetAsFile() = mFiles->Item(i); } diff --git a/dom/filesystem/Directory.cpp b/dom/filesystem/Directory.cpp index 1427e8ba44..ec02ba3626 100644 --- a/dom/filesystem/Directory.cpp +++ b/dom/filesystem/Directory.cpp @@ -242,7 +242,7 @@ Directory::GetFilesAndDirectories() nsString realPath; ErrorResult rv; RefPtr task = - new GetDirectoryListingTask(mFileSystem, mPath, rv); + new GetDirectoryListingTask(mFileSystem, mPath, mFilters, rv); if (NS_WARN_IF(rv.Failed())) { return nullptr; } @@ -251,6 +251,12 @@ Directory::GetFilesAndDirectories() return task->GetPromise(); } +void +Directory::SetContentFilters(const nsAString& aFilters) +{ + mFilters = aFilters; +} + FileSystemBase* Directory::GetFileSystem() const { diff --git a/dom/filesystem/Directory.h b/dom/filesystem/Directory.h index 564da1401f..de31bba6f8 100644 --- a/dom/filesystem/Directory.h +++ b/dom/filesystem/Directory.h @@ -87,6 +87,31 @@ public: // =========== End WebIDL bindings.============ + /** + * Sets a semi-colon separated list of filters to filter-in or filter-out + * certain types of files when the contents of this directory are requested + * via a GetFilesAndDirectories() call. + * + * Currently supported keywords: + * + * * filter-out-sensitive + * This keyword filters out files or directories that we don't wish to + * make available to Web content because we are concerned that there is + * a risk that users may unwittingly give Web content access to them + * and suffer undesirable consequences. The details of what is + * filtered out can be found in GetDirectoryListingTask::Work. + * + * In future, we will likely support filtering based on filename extensions + * (for example, aFilters could be "*.jpg; *.jpeg; *.gif"), but that isn't + * supported yet. Once supported, files that don't match a specified + * extension (if any are specified) would be filtered out. This + * functionality would allow us to apply the 'accept' attribute from + * to the results of a directory + * picker operation. + */ + void + SetContentFilters(const nsAString& aFilters); + FileSystemBase* GetFileSystem() const; private: @@ -108,6 +133,7 @@ private: RefPtr mFileSystem; nsString mPath; + nsString mFilters; }; } // namespace dom diff --git a/dom/filesystem/GetDirectoryListingTask.cpp b/dom/filesystem/GetDirectoryListingTask.cpp index 7fe6dff16f..04c3fd5b28 100644 --- a/dom/filesystem/GetDirectoryListingTask.cpp +++ b/dom/filesystem/GetDirectoryListingTask.cpp @@ -6,9 +6,9 @@ #include "GetDirectoryListingTask.h" +#include "HTMLSplitOnSpacesTokenizer.h" #include "js/Value.h" #include "mozilla/dom/Directory.h" -#include "mozilla/dom/DOMError.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" @@ -23,9 +23,11 @@ namespace dom { GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem, const nsAString& aTargetPath, + const nsAString& aFilters, ErrorResult& aRv) : FileSystemTaskBase(aFileSystem) , mTargetRealPath(aTargetPath) + , mFilters(aFilters) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); @@ -41,12 +43,13 @@ GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem, const FileSystemGetDirectoryListingParams& aParam, FileSystemRequestParent* aParent) : FileSystemTaskBase(aFileSystem, aParam, aParent) + , mTargetRealPath(aParam.realPath()) + , mFilters(aParam.filters()) { MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - mTargetRealPath = aParam.realPath(); } GetDirectoryListingTask::~GetDirectoryListingTask() @@ -66,7 +69,8 @@ FileSystemParams GetDirectoryListingTask::GetRequestParams(const nsString& aFileSystem) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - return FileSystemGetDirectoryListingParams(aFileSystem, mTargetRealPath); + return FileSystemGetDirectoryListingParams(aFileSystem, mTargetRealPath, + mFilters); } FileSystemResponseValue @@ -156,6 +160,20 @@ GetDirectoryListingTask::Work() return rv; } + bool filterOutSensitive = false; + { + HTMLSplitOnSpacesTokenizer tokenizer(mFilters, ';'); + nsAutoString token; + while (tokenizer.hasMoreTokens()) { + token = tokenizer.nextToken(); + if (token.EqualsLiteral("filter-out-sensitive")) { + filterOutSensitive = true; + } else { + MOZ_CRASH("Unrecognized filter"); + } + } + } + for (;;) { bool hasMore = false; if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) { @@ -180,6 +198,21 @@ GetDirectoryListingTask::Work() !(isFile || isDir)) { continue; } + + if (filterOutSensitive) { + bool isHidden; + if (NS_WARN_IF(NS_FAILED(currFile->IsHidden(&isHidden))) || isHidden) { + continue; + } + nsAutoString leafName; + if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) { + continue; + } + if (leafName[0] == char16_t('.')) { + continue; + } + } + BlobImplFile* impl = new BlobImplFile(currFile); impl->LookupAndCacheIsDirectory(); mTargetBlobImpls.AppendElement(impl); @@ -197,9 +230,7 @@ GetDirectoryListingTask::HandlerCallback() } if (HasError()) { - RefPtr domError = new DOMError(mFileSystem->GetWindow(), - mErrorValue); - mPromise->MaybeRejectBrokenly(domError); + mPromise->MaybeReject(mErrorValue); mPromise = nullptr; return; } @@ -229,7 +260,10 @@ GetDirectoryListingTask::HandlerCallback() MOZ_ASSERT(exist); } #endif - listing[i].SetAsDirectory() = new Directory(mFileSystem, path); + RefPtr directory = new Directory(mFileSystem, path); + // Propogate mFilter onto sub-Directory object: + directory->SetContentFilters(mFilters); + listing[i].SetAsDirectory() = directory; } else { listing[i].SetAsFile() = File::Create(mFileSystem->GetWindow(), mTargetBlobImpls[i]); } diff --git a/dom/filesystem/GetDirectoryListingTask.h b/dom/filesystem/GetDirectoryListingTask.h index 9e97bbdce7..4a6dc7b045 100644 --- a/dom/filesystem/GetDirectoryListingTask.h +++ b/dom/filesystem/GetDirectoryListingTask.h @@ -23,6 +23,7 @@ public: // If aDirectoryOnly is set, we should ensure that the target is a directory. GetDirectoryListingTask(FileSystemBase* aFileSystem, const nsAString& aTargetPath, + const nsAString& aFilters, ErrorResult& aRv); GetDirectoryListingTask(FileSystemBase* aFileSystem, const FileSystemGetDirectoryListingParams& aParam, @@ -55,6 +56,7 @@ protected: private: RefPtr mPromise; nsString mTargetRealPath; + nsString mFilters; // We cannot store File or Directory objects bacause this object is created // on a different thread and File and Directory are not thread-safe. diff --git a/dom/html/HTMLExtAppElement.cpp b/dom/html/HTMLExtAppElement.cpp index 0373a178ce..3291d612ac 100644 --- a/dom/html/HTMLExtAppElement.cpp +++ b/dom/html/HTMLExtAppElement.cpp @@ -27,7 +27,7 @@ NS_NewHTMLExtAppElement(already_AddRefed&& aNodeInfo, already_AddRefed aarni = ni.forget(); if (!permissionManager) { - return new HTMLUnknownElement(aarni); + return new mozilla::dom::HTMLUnknownElement(aarni); } uint32_t perm = nsIPermissionManager::UNKNOWN_ACTION; @@ -35,10 +35,10 @@ NS_NewHTMLExtAppElement(already_AddRefed&& aNodeInfo, "external-app", &perm); if (perm != nsIPermissionManager::ALLOW_ACTION) { - return new HTMLUnknownElement(aarni); + return new mozilla::dom::HTMLUnknownElement(aarni); } - return new HTMLExtAppElement(aarni); + return new mozilla::dom::HTMLExtAppElement(aarni); } namespace mozilla { diff --git a/dom/html/HTMLFormControlsCollection.cpp b/dom/html/HTMLFormControlsCollection.cpp index 5a54dfb78f..f420a6f755 100644 --- a/dom/html/HTMLFormControlsCollection.cpp +++ b/dom/html/HTMLFormControlsCollection.cpp @@ -121,6 +121,9 @@ HTMLFormControlsCollection::FlushPendingNotifications() NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormControlsCollection) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(HTMLFormControlsCollection) + // Note: We intentionally don't set tmp->mForm to nullptr here, since doing + // so may result in crashes because of inconsistent null-checking after the + // object gets unlinked. tmp->Clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp index 37980360fa..3336b6812d 100644 --- a/dom/html/HTMLImageElement.cpp +++ b/dom/html/HTMLImageElement.cpp @@ -80,27 +80,35 @@ namespace dom { class ImageLoadTask : public nsRunnable { public: - explicit ImageLoadTask(HTMLImageElement *aElement) : - mElement(aElement) - {} + ImageLoadTask(HTMLImageElement *aElement, bool aAlwaysLoad) + : mElement(aElement) + , mAlwaysLoad(aAlwaysLoad) + { + mDocument = aElement->OwnerDoc(); + mDocument->BlockOnload(); + } NS_IMETHOD Run() { if (mElement->mPendingImageLoadTask == this) { mElement->mPendingImageLoadTask = nullptr; - mElement->LoadSelectedImage(true, true); + mElement->LoadSelectedImage(true, true, mAlwaysLoad); } + mDocument->UnblockOnload(false); return NS_OK; } private: ~ImageLoadTask() {} RefPtr mElement; + nsCOMPtr mDocument; + bool mAlwaysLoad; }; HTMLImageElement::HTMLImageElement(already_AddRefed& aNodeInfo) : nsGenericHTMLElement(aNodeInfo) , mForm(nullptr) + , mInDocResponsiveContent(false) { // We start out broken AddStatesSilently(NS_EVENT_STATE_BROKEN); @@ -422,7 +430,7 @@ HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, mResponsiveSelector->Content() == this) { mResponsiveSelector->SetDefaultSource(NullString()); } - QueueImageLoadTask(); + QueueImageLoadTask(true); } else { // Bug 1076583 - We still behave synchronously in the non-responsive case CancelImageRequests(aNotify); @@ -442,7 +450,7 @@ HTMLImageElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, if (InResponsiveMode()) { // per spec, full selection runs when this changes, even though // it doesn't directly affect the source selection - QueueImageLoadTask(); + QueueImageLoadTask(true); } else { // Bug 1076583 - We still use the older synchronous algorithm in // non-responsive mode. Force a new load of the image with the @@ -542,7 +550,7 @@ HTMLImageElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName, mResponsiveSelector->Content() == this) { mResponsiveSelector->SetDefaultSource(aValue); } - QueueImageLoadTask(); + QueueImageLoadTask(true); } else if (aNotify) { // If aNotify is false, we are coming from the parser or some such place; // we'll get bound after all the attributes have been set, so we'll do the @@ -585,13 +593,15 @@ HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, UpdateFormOwner(); } - bool addedToPicture = aParent && aParent->IsHTMLElement(nsGkAtoms::picture) && - HTMLPictureElement::IsPictureEnabled(); - if (addedToPicture) { - if (aDocument) { + if (HaveSrcsetOrInPicture()) { + if (aDocument && !mInDocResponsiveContent) { aDocument->AddResponsiveContent(this); + mInDocResponsiveContent = true; } - QueueImageLoadTask(); + + bool forceLoadEvent = HTMLPictureElement::IsPictureEnabled() && + aParent && aParent->IsHTMLElement(nsGkAtoms::picture); + QueueImageLoadTask(forceLoadEvent); } else if (!InResponsiveMode() && HasAttr(kNameSpaceID_None, nsGkAtoms::src)) { // We skip loading when our attributes were set from parser land, @@ -632,17 +642,22 @@ HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent) } } + if (mInDocResponsiveContent) { + nsIDocument* doc = GetOurOwnerDoc(); + MOZ_ASSERT(doc); + if (doc) { + doc->RemoveResponsiveContent(this); + mInDocResponsiveContent = false; + } + } + if (GetParent() && GetParent()->IsHTMLElement(nsGkAtoms::picture) && HTMLPictureElement::IsPictureEnabled()) { - nsIDocument* doc = GetOurOwnerDoc(); - if (doc) { - doc->RemoveResponsiveContent(this); - } // Being removed from picture re-triggers selection, even if we // weren't using a peer if (aNullParent) { - QueueImageLoadTask(); + QueueImageLoadTask(true); } } @@ -686,7 +701,7 @@ HTMLImageElement::MaybeLoadImage() // Note, check LoadingEnabled() after LoadImage call. - LoadSelectedImage(false, true); + LoadSelectedImage(false, true, false); if (!LoadingEnabled()) { CancelImageRequests(true); @@ -886,7 +901,7 @@ HTMLImageElement::ClearForm(bool aRemoveFromForm) } void -HTMLImageElement::QueueImageLoadTask() +HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad) { // If loading is temporarily disabled, we don't want to queue tasks // that may then run when loading is re-enabled. @@ -894,7 +909,7 @@ HTMLImageElement::QueueImageLoadTask() return; } - nsCOMPtr task = new ImageLoadTask(this); + nsCOMPtr task = new ImageLoadTask(this, aAlwaysLoad); // The task checks this to determine if it was the last // queued event, and so earlier tasks are implicitly canceled. mPendingImageLoadTask = task; @@ -928,7 +943,7 @@ HTMLImageElement::InResponsiveMode() } nsresult -HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify) +HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad) { nsresult rv = NS_ERROR_FAILURE; @@ -936,7 +951,9 @@ HTMLImageElement::LoadSelectedImage(bool aForce, bool aNotify) // In responsive mode we generally want to re-run the full // selection algorithm whenever starting a new load, per // spec. This also causes us to re-resolve the URI as appropriate. - UpdateResponsiveSource(); + if (!UpdateResponsiveSource() && !aAlwaysLoad) { + return NS_OK; + } } if (mResponsiveSelector) { @@ -989,9 +1006,17 @@ HTMLImageElement::PictureSourceSrcsetChanged(nsIContent *aSourceNode, mResponsiveSelector->SetCandidatesFromSourceSet(aNewValue); } + if (!mInDocResponsiveContent) { + nsIDocument* doc = GetOurOwnerDoc(); + if (doc) { + doc->AddResponsiveContent(this); + mInDocResponsiveContent = true; + } + } + // This always triggers the image update steps per the spec, even if // we are not using this source. - QueueImageLoadTask(); + QueueImageLoadTask(true); } void @@ -1018,7 +1043,7 @@ HTMLImageElement::PictureSourceSizesChanged(nsIContent *aSourceNode, // This always triggers the image update steps per the spec, even if // we are not using this source. - QueueImageLoadTask(); + QueueImageLoadTask(true); } void @@ -1034,7 +1059,7 @@ HTMLImageElement::PictureSourceMediaOrTypeChanged(nsIContent *aSourceNode, // This always triggers the image update steps per the spec, even if // we are not switching to/from this source - QueueImageLoadTask(); + QueueImageLoadTask(true); } void @@ -1044,7 +1069,7 @@ HTMLImageElement::PictureSourceAdded(nsIContent *aSourceNode) return; } - QueueImageLoadTask(); + QueueImageLoadTask(true); } void @@ -1054,15 +1079,17 @@ HTMLImageElement::PictureSourceRemoved(nsIContent *aSourceNode) return; } - QueueImageLoadTask(); + QueueImageLoadTask(true); } -void +bool HTMLImageElement::UpdateResponsiveSource() { + bool hadSelector = !!mResponsiveSelector; + if (!IsSrcsetEnabled()) { mResponsiveSelector = nullptr; - return; + return hadSelector; } nsIContent *currentSource = @@ -1082,7 +1109,7 @@ HTMLImageElement::UpdateResponsiveSource() if (candidateSource == currentSource) { // found no better source before current, re-run selection on // that and keep it if it's still usable. - mResponsiveSelector->SelectImage(true); + bool changed = mResponsiveSelector->SelectImage(true); if (mResponsiveSelector->NumCandidates()) { bool isUsableCandidate = true; @@ -1094,7 +1121,7 @@ HTMLImageElement::UpdateResponsiveSource() } if (isUsableCandidate) { - break; + return changed; } } @@ -1123,6 +1150,8 @@ HTMLImageElement::UpdateResponsiveSource() // Ran out of siblings without finding ourself, e.g. XBL magic. mResponsiveSelector = nullptr; } + + return !hadSelector || mResponsiveSelector; } /*static */ bool @@ -1295,7 +1324,7 @@ HTMLImageElement::DestroyContent() void HTMLImageElement::MediaFeatureValuesChanged() { - QueueImageLoadTask(); + QueueImageLoadTask(false); } void diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h index 8d04945746..4add7ccb95 100644 --- a/dom/html/HTMLImageElement.h +++ b/dom/html/HTMLImageElement.h @@ -280,7 +280,7 @@ protected: // algorithm (InResponsiveMode()) -- synchronous actions when just // using img.src will bypass this, and update source and kick off // image load synchronously. - void QueueImageLoadTask(); + void QueueImageLoadTask(bool aAlwaysLoad); // True if we have a srcset attribute or a parent, regardless of if // any valid responsive sources were parsed from either. @@ -292,7 +292,7 @@ protected: // Resolve and load the current mResponsiveSelector (responsive mode) or src // attr image. - nsresult LoadSelectedImage(bool aForce, bool aNotify); + nsresult LoadSelectedImage(bool aForce, bool aNotify, bool aAlwaysLoad); // True if this string represents a type we would support on static bool SupportedPictureSourceType(const nsAString& aType); @@ -321,7 +321,9 @@ protected: // the existing mResponsiveSelector, meaning you need to update its // parameters as appropriate before calling (or null it out to force // recreation) - void UpdateResponsiveSource(); + // + // Returns true if the source has changed, and false otherwise. + bool UpdateResponsiveSource(); // Given a node that is a previous sibling *or* ourselves, try to // create a ResponsiveSelector. @@ -362,6 +364,7 @@ private: static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes, nsRuleData* aData); + bool mInDocResponsiveContent; nsCOMPtr mPendingImageLoadTask; }; diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 4c03e0a730..3665dbdc23 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -295,20 +295,25 @@ NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2) NS_IMETHODIMP UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason) { - nsCOMPtr localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); - NS_ENSURE_STATE(localFile); + nsCOMPtr localFile; + nsAutoString prefStr; - if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || - !mResult) { - // Default to "desktop" directory for each platform - nsCOMPtr homeDir; - NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir)); - localFile = do_QueryInterface(homeDir); - } else { - nsAutoString prefStr; - nsCOMPtr pref; - mResult->GetValue(getter_AddRefs(pref)); - pref->GetAsAString(prefStr); + if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) { + prefStr = Preferences::GetString("dom.input.fallbackUploadDir"); + if (prefStr.IsEmpty()) { + // If no custom directory was set through the pref, default to + // "desktop" directory for each platform. + NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(localFile)); + } + } + + if (!localFile) { + if (prefStr.IsEmpty() && mResult) { + nsCOMPtr pref; + mResult->GetValue(getter_AddRefs(pref)); + pref->GetAsAString(prefStr); + } + localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); localFile->InitWithPath(prefStr); } @@ -4899,10 +4904,17 @@ HTMLInputElement::GetFilesAndDirectories(ErrorResult& aRv) } int32_t leafSeparatorIndex = path.RFind(FILE_PATH_SEPARATOR); nsDependentSubstring dirname = Substring(path, 0, leafSeparatorIndex); - nsDependentSubstring basename = Substring(path, leafSeparatorIndex); fs = MakeOrReuseFileSystem(dirname, fs, window); - filesAndDirsSeq[i].SetAsDirectory() = new Directory(fs, basename); + nsAutoString dompath(NS_LITERAL_STRING(FILESYSTEM_DOM_PATH_SEPARATOR)); + dompath.Append(Substring(path, leafSeparatorIndex + 1)); + RefPtr directory = new Directory(fs, dompath); + // In future we could refactor SetFilePickerFiltersFromAccept to return a + // semicolon separated list of file extensions and include that in the + // filter string passed here. + directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive")); + filesAndDirsSeq[i].SetAsDirectory() = directory; } else { + // This file was directly selected by the user, so don't filter it. filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i]; } } diff --git a/dom/html/HTMLTrackElement.cpp b/dom/html/HTMLTrackElement.cpp index 6e997434fc..6a47128dfd 100644 --- a/dom/html/HTMLTrackElement.cpp +++ b/dom/html/HTMLTrackElement.cpp @@ -70,7 +70,7 @@ static MOZ_CONSTEXPR nsAttrValue::EnumTable kKindTable[] = { }; // The default value for kKindTable is "subtitles" -static MOZ_CONSTEXPR const char* kKindTableDefaultString = kKindTable->tag; +static MOZ_CONSTEXPR const char* kKindTableDefaultString = kKindTable[0].tag; /** HTMLTrackElement */ HTMLTrackElement::HTMLTrackElement(already_AddRefed& aNodeInfo) diff --git a/dom/html/test/file_bug1166138_1x.png b/dom/html/test/file_bug1166138_1x.png new file mode 100644 index 0000000000000000000000000000000000000000..df421453c25631353cfe86bde5973fdca15b1ea9 GIT binary patch literal 91 zcmeAS@N?(olHy`uVBq!ia0vp^DImQX|f*K(4T-i(`m{ lWU>V7;slYNra*^q1_ss&2F5$COwvGU22WQ%mvv4FO#rqO5n=!U literal 0 HcmV?d00001 diff --git a/dom/html/test/file_bug1166138_2x.png b/dom/html/test/file_bug1166138_2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6f76d4438724111983a11860f13568361b52d9bc GIT binary patch literal 100 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5y8Awi_W^)%vF$egBxTd6}EPfSw1jv=~ba4!k rkbHZv5y)XUyx@1R|3ikWAU1;^qeuV)<1IPC3=q%L)z4*}Q$iB}r??p{ literal 0 HcmV?d00001 diff --git a/dom/html/test/file_bug1166138_def.png b/dom/html/test/file_bug1166138_def.png new file mode 100644 index 0000000000000000000000000000000000000000..144a2f0b93c5913872f30056999da000ae6049cd GIT binary patch literal 85 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_{3?x-PN__%S%mF?ju9A|H-p`i&1#)>kT^vIs fB$Fjrn}OOGConJ`{I<#tD8bgTe~DWM4fq|_2j literal 0 HcmV?d00001 diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini index f581f727d3..24d80a53dc 100644 --- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -197,6 +197,9 @@ support-files = file_ignoreuserfocus.html simpleFileOpener.js file_mozaudiochannel.html + file_bug1166138_1x.png + file_bug1166138_2x.png + file_bug1166138_def.png [test_a_text.html] [test_anchor_href_cache_invalidation.html] @@ -289,7 +292,7 @@ skip-if = toolkit == 'android' [test_bug518122.html] [test_bug519987.html] [test_bug523771.html] -skip-if = buildapp == 'b2g' || e10s # b2g(onload of iframe not firing, because submit not working?) b2g-debug(onload of iframe not firing, because submit not working?) b2g-desktop(onload of iframe not firing, because submit not working?) +skip-if = buildapp == 'b2g' # b2g(onload of iframe not firing, because submit not working?) b2g-debug(onload of iframe not firing, because submit not working?) b2g-desktop(onload of iframe not firing, because submit not working?) [test_bug529819.html] [test_bug529859.html] [test_bug535043.html] @@ -313,7 +316,7 @@ skip-if = toolkit == 'android' #TIMED_OUT [test_bug560112.html] [test_bug561634.html] [test_bug561636.html] -skip-if = buildapp == 'b2g' || e10s # b2g(observerservice not working) b2g-debug(observerservice not working) b2g-desktop(observerservice not working) +skip-if = buildapp == 'b2g' # b2g(observerservice not working) b2g-debug(observerservice not working) b2g-desktop(observerservice not working) [test_bug561640.html] [test_bug564001.html] [test_bug566046.html] @@ -338,10 +341,9 @@ skip-if = buildapp == 'b2g' || e10s # b2g(observerservice not working) b2g-debug [test_bug592802.html] [test_bug593689.html] [test_bug595429.html] -skip-if = e10s [test_bug595447.html] [test_bug595449.html] -skip-if = (toolkit == 'gonk' && debug) || e10s #debug-only failure +skip-if = (toolkit == 'gonk' && debug) #debug-only failure [test_bug596350.html] [test_bug596511.html] [test_bug598643.html] @@ -362,7 +364,7 @@ skip-if = buildapp == 'mulet' # TC: Bug 1144079 - Re-enable Mulet mochitests and [test_bug612730.html] skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android) [test_bug613113.html] -skip-if = buildapp == 'b2g' || e10s # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) +skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) [test_bug613722.html] [test_bug613979.html] [test_bug615595.html] @@ -370,29 +372,22 @@ skip-if = buildapp == 'b2g' || e10s # b2g(bug 587671, need an invalidformsubmit skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android) [test_bug617528.html] [test_bug618948.html] -skip-if = buildapp == 'b2g' || e10s # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) +skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) [test_bug619278.html] -skip-if = buildapp == 'b2g' || e10s # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) +skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) [test_bug622558.html] -skip-if = e10s [test_bug622597.html] -skip-if = buildapp == 'b2g' || e10s # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) +skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer) [test_bug623291.html] -skip-if = e10s [test_bug6296.html] -skip-if = e10s [test_bug629801.html] -skip-if = e10s [test_bug633058.html] -skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || e10s #Bug 931116, b2g desktop specific, initial triage +skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_bug636336.html] -skip-if = e10s [test_bug641219.html] -skip-if = e10s [test_bug643051.html] -skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || e10s #Bug 931116, b2g desktop specific, initial triage +skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_bug646157.html] -skip-if = e10s [test_bug649134.html] # This extra subdirectory is needed due to the nature of this test. # With the bug, the test loads the base URL of the bug649134/file_*.sjs @@ -444,7 +439,7 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop spec [test_bug839913.html] [test_bug840877.html] [test_bug841466.html] -skip-if = (toolkit == 'gonk' && debug) || e10s #debug-only failure +skip-if = (toolkit == 'gonk' && debug) #debug-only failure [test_bug845057.html] [test_bug869040.html] [test_bug870787.html] @@ -486,11 +481,11 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #time out on b2g desktop spec [test_iframe_sandbox_navigation2.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #time out on b2g desktop specific [test_iframe_sandbox_plugins.html] -skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(plugins not supported) b2g-debug(plugins not supported) b2g-desktop(plugins not supported) +skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(plugins not supported) b2g-debug(plugins not supported) b2g-desktop(plugins not supported) [test_iframe_sandbox_popups.html] skip-if = buildapp == 'b2g' # b2g(multiple concurrent window.open()s fail on B2G) b2g-debug(multiple concurrent window.open()s fail on B2G) b2g-desktop(Bug 931116, b2g desktop specific, initial triage) [test_iframe_sandbox_popups_inheritance.html] -skip-if = buildapp == 'b2g' || e10s # b2g(multiple concurrent window.open()s fail on B2G) b2g-debug(multiple concurrent window.open()s fail on B2G) b2g-desktop(Bug 931116, b2g desktop specific, initial triage) +skip-if = buildapp == 'b2g' || e10s || toolkit == 'android' # b2g(multiple concurrent window.open()s fail on B2G) b2g-debug(multiple concurrent window.open()s fail on B2G) b2g-desktop(Bug 931116, b2g desktop specific, initial triage) android(bug 939642) [test_iframe_sandbox_redirect.html] [test_iframe_sandbox_refresh.html] [test_iframe_sandbox_same_origin.html] @@ -511,12 +506,12 @@ skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator [test_meta_attributes_reflection.html] [test_mod_attributes_reflection.html] [test_mozaudiochannel.html] -skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(Perma-orange on debug emulator) b2g-desktop(Bug 931116, b2g desktop specific, initial triage) +skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(Perma-orange on debug emulator) b2g-desktop(Bug 931116, b2g desktop specific, initial triage) [test_named_options.html] [test_nested_invalid_fieldsets.html] [test_object_attributes_reflection.html] [test_object_plugin_nav.html] -skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT # b2g(plugins not supported) b2g-debug(plugins not supported) b2g-desktop(plugins not supported) +skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(plugins not supported) b2g-debug(plugins not supported) b2g-desktop(plugins not supported) [test_ol_attributes_reflection.html] [test_option_defaultSelected.html] [test_option_selected_state.html] @@ -541,9 +536,9 @@ skip-if = (toolkit == 'gonk' && debug) [test_bug1823.html] [test_bug57600.html] [test_bug196523.html] -skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || e10s #Bug 931116, b2g desktop specific, initial triage +skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage [test_bug199692.html] -skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || toolkit == 'android' || e10s #bug 811644 #Bug 931116, b2g desktop specific, initial triage +skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || toolkit == 'android' #bug 811644 #Bug 931116, b2g desktop specific, initial triage [test_bug172261.html] [test_bug255820.html] [test_bug259332.html] @@ -592,7 +587,7 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e1 skip-if = buildapp == 'b2g' || e10s [test_bug765780.html] [test_bug871161.html] -skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || e10s #Bug 931116, b2g desktop specific, initial triage +skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage support-files = file_bug871161-1.html file_bug871161-2.html [test_bug1013316.html] [test_hash_encoded.html] @@ -600,7 +595,10 @@ support-files = file_bug871161-1.html file_bug871161-2.html [test_window_open_close.html] skip-if = buildapp == 'b2g' # bug 1129014 [test_img_complete.html] -[test_extapp.html] [test_viewport_resize.html] +[test_extapp.html] [test_image_clone_load.html] [test_bug1203668.html] +[test_bug1166138.html] +[test_filepicker_default_directory.html] +skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' diff --git a/dom/html/test/test_bug1166138.html b/dom/html/test/test_bug1166138.html new file mode 100644 index 0000000000..889416775f --- /dev/null +++ b/dom/html/test/test_bug1166138.html @@ -0,0 +1,130 @@ + + + + + + Test for Bug 1166138 + + + + + + Mozilla Bug 1166138 +

+ + + + + + + diff --git a/dom/html/test/test_filepicker_default_directory.html b/dom/html/test/test_filepicker_default_directory.html new file mode 100644 index 0000000000..c2212baa04 --- /dev/null +++ b/dom/html/test/test_filepicker_default_directory.html @@ -0,0 +1,83 @@ + + + + + Test for filepicker default directory + + + + + +Mozilla Bug 1194893 +
+ +
+
+
+
+ + diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index a6adff26f0..c8400c21c7 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2963,6 +2963,15 @@ ContentChild::RecvShutdown() GetIPCChannel()->SetAbortOnError(false); +#ifdef MOZ_ENABLE_PROFILER_SPS + if (profiler_is_active()) { + // We're shutting down while we were profiling. Send the + // profile up to the parent so that we don't lose this + // information. + Unused << RecvGatherProfile(); + } +#endif + // Ignore errors here. If this fails, the parent will kill us after a // timeout. Unused << SendFinishShutdown(); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index a44263c1d1..a8491cc670 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -134,6 +134,7 @@ #include "nsIMutable.h" #include "nsIObserverService.h" #include "nsIPresShell.h" +#include "nsIRemoteWindowContext.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsISiteSecurityService.h" @@ -1686,6 +1687,47 @@ StaticAutoPtr > NS_IMPL_ISUPPORTS(SystemMessageHandledListener, nsITimerCallback) + +class RemoteWindowContext final : public nsIRemoteWindowContext + , public nsIInterfaceRequestor +{ +public: + explicit RemoteWindowContext(TabParent* aTabParent) + : mTabParent(aTabParent) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIREMOTEWINDOWCONTEXT + +private: + ~RemoteWindowContext(); + RefPtr mTabParent; +}; + +NS_IMPL_ISUPPORTS(RemoteWindowContext, nsIRemoteWindowContext, nsIInterfaceRequestor) + +RemoteWindowContext::~RemoteWindowContext() +{ +} + +NS_IMETHODIMP +RemoteWindowContext::GetInterface(const nsIID& aIID, void** aSink) +{ + return QueryInterface(aIID, aSink); +} + +NS_IMETHODIMP +RemoteWindowContext::OpenURI(nsIURI* aURI, uint32_t aFlags) +{ + URIParams uri; + SerializeURI(aURI, uri); + + Unused << mTabParent->SendOpenURI(uri, aFlags); + return NS_OK; +} + } // namespace void @@ -2089,6 +2131,12 @@ ContentParent::ActorDestroy(ActorDestroyReason why) mConsoleService = nullptr; +#ifdef MOZ_ENABLE_PROFILER_SPS + if (mGatherer && !mProfile.IsEmpty()) { + mGatherer->OOPExitProfile(mProfile); + } +#endif + if (obs) { RefPtr props = new nsHashPropertyBag(); @@ -4314,7 +4362,8 @@ ContentParent::RecvAccumulateMixedContentHSTS(const URIParams& aURI, const bool& } bool -ContentParent::RecvLoadURIExternal(const URIParams& uri) +ContentParent::RecvLoadURIExternal(const URIParams& uri, + PBrowserParent* windowContext) { nsCOMPtr extProtService(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID)); if (!extProtService) { @@ -4324,7 +4373,10 @@ ContentParent::RecvLoadURIExternal(const URIParams& uri) if (!ourURI) { return false; } - extProtService->LoadURI(ourURI, nullptr); + + RefPtr context = + new RemoteWindowContext(static_cast(windowContext)); + extProtService->LoadURI(ourURI, context); return true; } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 26f4a94094..778b3cd735 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -795,7 +795,8 @@ private: virtual bool RecvOpenNotificationSettings(const IPC::Principal& aPrincipal) override; - virtual bool RecvLoadURIExternal(const URIParams& uri) override; + virtual bool RecvLoadURIExternal(const URIParams& uri, + PBrowserParent* windowContext) override; virtual bool RecvSyncMessage(const nsString& aMsg, const ClonedMessageData& aData, diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 6f093f0607..f116e51ef7 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -554,6 +554,8 @@ child: LoadURL(nsCString uri, BrowserConfiguration config, ShowInfo info); + OpenURI(URIParams uri, uint32_t flags); + CacheFileDescriptor(nsString path, FileDescriptor fd); UpdateDimensions(CSSRect rect, CSSSize size, nsSizeMode sizeMode, diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index cc51160364..9dbe11cd56 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -278,6 +278,16 @@ struct FileSystemGetDirectoryListingParams { nsString filesystem; nsString realPath; + // 'filters' could be an array rather than a semicolon separated string + // (we'd then use InfallibleTArray internally), but that is + // wasteful. E10s requires us to pass the filters over as a string anyway, + // so avoiding using an array avoids serialization on the side passing the + // filters. Since an nsString can share its buffer when copied, + // using that instead of InfallibleTArray makes copying the filters + // around in any given process a bit more efficient too, since copying a + // single nsString is cheaper than copying InfallibleTArray member data and + // each nsString that it contains. + nsString filters; }; struct FileSystemGetFileOrDirectoryParams @@ -836,7 +846,7 @@ parent: async VisitURI(URIParams uri, OptionalURIParams referrer, uint32_t flags); async SetURITitle(URIParams uri, nsString title); - async LoadURIExternal(URIParams uri); + async LoadURIExternal(URIParams uri, PBrowser windowContext); // PrefService message sync ReadPrefsArray() returns (PrefSetting[] prefs); diff --git a/dom/ipc/StructuredCloneData.cpp b/dom/ipc/StructuredCloneData.cpp index 616e8fc987..8ae867ca4b 100644 --- a/dom/ipc/StructuredCloneData.cpp +++ b/dom/ipc/StructuredCloneData.cpp @@ -28,21 +28,19 @@ namespace ipc { bool StructuredCloneData::Copy(const StructuredCloneData& aData) { - if (!aData.mData) { + if (!aData.Data()) { return true; } - uint64_t* data = static_cast(js_malloc(aData.mDataLength)); - if (!data) { - return false; + if (aData.SharedData()) { + mSharedData = aData.SharedData(); + } else { + mSharedData = + SharedJSAllocatedData::CreateFromExternalData(aData.Data(), + aData.DataLength()); + NS_ENSURE_TRUE(mSharedData, false); } - memcpy(data, aData.mData, aData.mDataLength); - - mData = data; - mDataLength = aData.mDataLength; - mDataOwned = eJSAllocated; - MOZ_ASSERT(BlobImpls().IsEmpty()); BlobImpls().AppendElements(aData.BlobImpls()); @@ -56,12 +54,12 @@ StructuredCloneData::Read(JSContext* aCx, JS::MutableHandle aValue, ErrorResult &aRv) { - MOZ_ASSERT(mData); + MOZ_ASSERT(Data()); nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); MOZ_ASSERT(global); - ReadFromBuffer(global, aCx, mData, mDataLength, aValue, aRv); + ReadFromBuffer(global, aCx, Data(), DataLength(), aValue, aRv); } void @@ -69,26 +67,28 @@ StructuredCloneData::Write(JSContext* aCx, JS::Handle aValue, ErrorResult &aRv) { - MOZ_ASSERT(!mData); + MOZ_ASSERT(!Data()); StructuredCloneHolder::Write(aCx, aValue, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } - mBuffer->steal(&mData, &mDataLength); + uint64_t* data = nullptr; + size_t dataLength = 0; + mBuffer->steal(&data, &dataLength); mBuffer = nullptr; - mDataOwned = eJSAllocated; + mSharedData = new SharedJSAllocatedData(data, dataLength); } void StructuredCloneData::WriteIPCParams(IPC::Message* aMsg) const { - WriteParam(aMsg, mDataLength); + WriteParam(aMsg, DataLength()); - if (mDataLength) { + if (DataLength()) { // Structured clone data must be 64-bit aligned. - aMsg->WriteBytes(mData, mDataLength, sizeof(uint64_t)); + aMsg->WriteBytes(Data(), DataLength(), sizeof(uint64_t)); } } @@ -96,31 +96,29 @@ bool StructuredCloneData::ReadIPCParams(const IPC::Message* aMsg, void** aIter) { - MOZ_ASSERT(!mData); + MOZ_ASSERT(!Data()); - if (!ReadParam(aMsg, aIter, &mDataLength)) { + size_t dataLength = 0; + if (!ReadParam(aMsg, aIter, &dataLength)) { return false; } - if (!mDataLength) { + if (!dataLength) { return true; } + uint64_t* dataBuffer = nullptr; const char** buffer = - const_cast(reinterpret_cast(&mData)); + const_cast(reinterpret_cast(&dataBuffer)); // Structured clone data must be 64-bit aligned. - if (!aMsg->ReadBytes(aIter, buffer, mDataLength, sizeof(uint64_t))) { + if (!aMsg->ReadBytes(aIter, buffer, dataLength, sizeof(uint64_t))) { return false; } - uint64_t* data = static_cast(js_malloc(mDataLength)); - if (!data) { - return false; - } + mSharedData = SharedJSAllocatedData::CreateFromExternalData(dataBuffer, + dataLength); + NS_ENSURE_TRUE(mSharedData, false); - memcpy(data, mData, mDataLength); - mData = data; - mDataOwned = eJSAllocated; return true; } @@ -128,17 +126,10 @@ bool StructuredCloneData::CopyExternalData(const void* aData, size_t aDataLength) { - MOZ_ASSERT(!mData); - uint64_t* data = static_cast(js_malloc(aDataLength)); - if (!data) { - return false; - } - - memcpy(data, aData, aDataLength); - mData = data; - mDataLength = aDataLength; - mDataOwned = eJSAllocated; - + MOZ_ASSERT(!Data()); + mSharedData = SharedJSAllocatedData::CreateFromExternalData(aData, + aDataLength); + NS_ENSURE_TRUE(mSharedData, false); return true; } diff --git a/dom/ipc/StructuredCloneData.h b/dom/ipc/StructuredCloneData.h index c10e706577..3bcf67b872 100644 --- a/dom/ipc/StructuredCloneData.h +++ b/dom/ipc/StructuredCloneData.h @@ -7,7 +7,10 @@ #ifndef mozilla_dom_ipc_StructuredCloneData_h #define mozilla_dom_ipc_StructuredCloneData_h +#include +#include "mozilla/RefPtr.h" #include "mozilla/dom/StructuredCloneHolder.h" +#include "nsISupportsImpl.h" namespace IPC { class Message; @@ -17,6 +20,51 @@ namespace mozilla { namespace dom { namespace ipc { +class SharedJSAllocatedData final +{ +public: + SharedJSAllocatedData(uint64_t* aData, size_t aDataLength) + : mData(aData), mDataLength(aDataLength) + { + MOZ_ASSERT(mData); + } + + static already_AddRefed + CreateFromExternalData(const void* aData, size_t aDataLength) + { + uint64_t* data = Allocate64bitSafely(aDataLength); + if (!data) { + return nullptr; + } + + memcpy(data, aData, aDataLength); + RefPtr sharedData = + new SharedJSAllocatedData(data, aDataLength); + return sharedData.forget(); + } + + NS_INLINE_DECL_REFCOUNTING(SharedJSAllocatedData) + + uint64_t* Data() const { return mData; } + size_t DataLength() const { return mDataLength; } + +private: + ~SharedJSAllocatedData() + { + js_free(mData); + } + + static uint64_t* + Allocate64bitSafely(size_t aSize) + { + // Structured cloning requires 64-bit aligment. + return static_cast(js_malloc(std::max(sizeof(uint64_t), aSize))); + } + + uint64_t* mData; + size_t mDataLength; +}; + class StructuredCloneData : public StructuredCloneHolder { public: @@ -24,18 +72,15 @@ public: : StructuredCloneHolder(StructuredCloneHolder::CloningSupported, StructuredCloneHolder::TransferringNotSupported, StructuredCloneHolder::DifferentProcess) - , mData(nullptr) - , mDataLength(0) - , mDataOwned(eNone) + , mExternalData(nullptr) + , mExternalDataLength(0) {} StructuredCloneData(const StructuredCloneData&) = delete; ~StructuredCloneData() { - if (mDataOwned == eJSAllocated) { - js_free(mData); - } + MOZ_ASSERT(!(mExternalData && mSharedData)); } StructuredCloneData& @@ -63,22 +108,26 @@ public: void UseExternalData(uint64_t* aData, size_t aDataLength) { - MOZ_ASSERT(!mData); - mData = aData; - mDataLength = aDataLength; - MOZ_ASSERT(mDataOwned == eNone); + MOZ_ASSERT(!Data()); + mExternalData = aData; + mExternalDataLength = aDataLength; } bool CopyExternalData(const void* aData, size_t aDataLength); uint64_t* Data() const { - return mData; + return mSharedData ? mSharedData->Data() : mExternalData; } size_t DataLength() const { - return mDataLength; + return mSharedData ? mSharedData->DataLength() : mExternalDataLength; + } + + SharedJSAllocatedData* SharedData() const + { + return mSharedData; } // For IPC serialization @@ -86,12 +135,10 @@ public: bool ReadIPCParams(const IPC::Message* aMessage, void** aIter); private: - uint64_t* mData; - size_t mDataLength; - enum { - eNone, - eJSAllocated, - } mDataOwned; + uint64_t* MOZ_NON_OWNING_REF mExternalData; + size_t mExternalDataLength; + + RefPtr mSharedData; }; } // namespace ipc diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 3f0870ecf6..bf6e31f2ca 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -23,6 +23,7 @@ #include "mozilla/plugins/PluginWidgetChild.h" #include "mozilla/IMEStateManager.h" #include "mozilla/ipc/DocumentRendererChild.h" +#include "mozilla/ipc/URIUtils.h" #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif @@ -101,6 +102,7 @@ #include "nsIAppsService.h" #include "nsNetUtil.h" #include "nsIPermissionManager.h" +#include "nsIURILoader.h" #include "nsIScriptError.h" #include "mozilla/EventForwards.h" #include "nsDeviceContext.h" @@ -1262,6 +1264,31 @@ TabChild::RecvLoadURL(const nsCString& aURI, return true; } +bool +TabChild::RecvOpenURI(const URIParams& aURI, const uint32_t& aFlags) +{ + nsCOMPtr uri = DeserializeURI(aURI); + nsCOMPtr channel; + nsresult rv = + NS_NewChannel(getter_AddRefs(channel), + uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_DOCUMENT); + if (NS_WARN_IF(NS_FAILED(rv))) { + return true; + } + + nsCOMPtr loader = do_GetService("@mozilla.org/uriloader;1"); + if (NS_WARN_IF(!loader)) { + return true; + } + + nsCOMPtr context(do_QueryInterface(WebNavigation())); + loader->OpenURI(channel, aFlags, context); + return true; +} + bool TabChild::RecvCacheFileDescriptor(const nsString& aPath, const FileDescriptor& aFileDescriptor) diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index 05c73ca5d4..dd968cdc0a 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -300,6 +300,8 @@ public: virtual bool RecvLoadURL(const nsCString& aURI, const BrowserConfiguration& aConfiguration, const ShowInfo& aInfo) override; + virtual bool RecvOpenURI(const URIParams& aURI, + const uint32_t& aFlags) override; virtual bool RecvCacheFileDescriptor(const nsString& aPath, const FileDescriptor& aFileDescriptor) override; diff --git a/dom/ipc/preload.js b/dom/ipc/preload.js index 5f8bf03f5a..f4897ea7f6 100644 --- a/dom/ipc/preload.js +++ b/dom/ipc/preload.js @@ -7,9 +7,9 @@ // This script is run when the preallocated process starts. It is injected as // a frame script. -const BrowserElementIsPreloaded = true; +var BrowserElementIsPreloaded = true; -const DoPreloadPostfork = function(aCallback) { +var DoPreloadPostfork = function(aCallback) { Services.obs.addObserver({ _callback: aCallback, diff --git a/dom/media/tests/mochitest/mochitest.ini b/dom/media/tests/mochitest/mochitest.ini index 2033013715..2b88706e6c 100644 --- a/dom/media/tests/mochitest/mochitest.ini +++ b/dom/media/tests/mochitest/mochitest.ini @@ -118,6 +118,8 @@ skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video suppo skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) [test_peerConnection_promiseSendOnly.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) +[test_peerConnection_relayOnly.html] +skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) [test_peerConnection_callbacks.html] skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g) [test_peerConnection_replaceTrack.html] diff --git a/dom/media/tests/mochitest/pc.js b/dom/media/tests/mochitest/pc.js index 5cc71e389e..9fc28d4982 100644 --- a/dom/media/tests/mochitest/pc.js +++ b/dom/media/tests/mochitest/pc.js @@ -1266,18 +1266,15 @@ PeerConnectionWrapper.prototype = { * resolves when connected, rejects on failure */ waitForIceConnected : function() { - return new Promise((resolve, reject) => { - var iceConnectedChanged = () => { - if (this.isIceConnected()) { - delete this.ice_connection_callbacks.waitForIceConnected; - resolve(); - } else if (! this.isIceConnectionPending()) { - delete this.ice_connection_callbacks.waitForIceConnected; - resolve(); - } + return new Promise((resolve, reject) => + this.ice_connection_callbacks.waitForIceConnected = () => { + if (this.isIceConnected()) { + delete this.ice_connection_callbacks.waitForIceConnected; + resolve(); + } else if (!this.isIceConnectionPending()) { + delete this.ice_connection_callbacks.waitForIceConnected; + reject(new Error('ICE failed')); } - - this.ice_connection_callbacks.waitForIceConnected = iceConnectedChanged; }); }, diff --git a/dom/media/tests/mochitest/test_peerConnection_relayOnly.html b/dom/media/tests/mochitest/test_peerConnection_relayOnly.html new file mode 100644 index 0000000000..49f4eca2b5 --- /dev/null +++ b/dom/media/tests/mochitest/test_peerConnection_relayOnly.html @@ -0,0 +1,61 @@ + + + + + + +
+
+
+ + diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index de6ee08abd..8554db6f0f 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -3153,8 +3153,7 @@ PluginProfilerObserver::Observe(nsISupports *aSubject, } else if (!strcmp(aTopic, "profiler-stopped")) { mPmp->StopProfiler(); } else if (!strcmp(aTopic, "profiler-subprocess-gather")) { - RefPtr gatherer = static_cast(aSubject); - mPmp->GatherAsyncProfile(gatherer); + mPmp->GatherAsyncProfile(); } else if (!strcmp(aTopic, "profiler-subprocess")) { nsCOMPtr pse = do_QueryInterface(aSubject); mPmp->GatheredAsyncProfile(pse); diff --git a/dom/tests/mochitest/general/test_img_mutations.html b/dom/tests/mochitest/general/test_img_mutations.html index e2bc72d864..d15ec0f429 100644 --- a/dom/tests/mochitest/general/test_img_mutations.html +++ b/dom/tests/mochitest/general/test_img_mutations.html @@ -33,8 +33,9 @@ if (expectingLoads > 0) { expectingLoads--; } - if (!expectingLoads && !expectingErrors) { + if (!expectingLoads && !expectingErrors && afterExpectCallback) { setTimeout(afterExpectCallback, 0); + afterExpectCallback = null; } } function onImgError() { @@ -42,8 +43,9 @@ if (expectingErrors > 0) { expectingErrors--; } - if (!expectingLoads && !expectingErrors) { + if (!expectingLoads && !expectingErrors && afterExpectCallback) { setTimeout(afterExpectCallback, 0); + afterExpectCallback = null; } } function expectEvents(loads, errors, callback) { @@ -150,7 +152,7 @@ expectEvents(0, 0, nextTest); }); - // Re-binding image to document should be a no-op + // re-binding the image to the document should be a no-op tests.push(function () { info("test 8"); document.body.appendChild(img); @@ -167,19 +169,20 @@ expectEvents(1, 0, function() { is(img.currentSrc, testPNG50, "Should now have testPNG50 as current request"); - SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] }, - function() { - // We don't currently dynamically switch, but doing so is - // up to the UA so we may in the future. In which case - // this test needs to be changed. - is(img.currentSrc, testPNG50, "Should now have testPNG50 as current request"); + + // The preference change will trigger a load, as the image will change + SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] }); + expectEvents(1, 0, function() { + is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request"); img.src = img.src; - is(img.currentSrc, testPNG50, "Should still have testPNG50 as current request"); + is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request"); + // img.src = img.src is special-cased by the spec. It should always + // trigger an load event expectEvents(1, 0, function() { - is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request"); - nextTest(); + is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request"); + expectEvents(0, 0, nextTest); }); - }); + }) }); }); @@ -206,6 +209,10 @@ (tests.shift())(); }, 0); } else { + // Remove the event listeners to prevent the prefenv being popped from + // causing test failures. + img.removeEventListener("load", onImgLoad); + img.removeEventListener("error", onImgError); SimpleTest.finish(); } } diff --git a/gfx/thebes/gfxFontPrefLangList.h b/gfx/thebes/gfxFontPrefLangList.h index bc121884d2..804b2b17ef 100644 --- a/gfx/thebes/gfxFontPrefLangList.h +++ b/gfx/thebes/gfxFontPrefLangList.h @@ -27,6 +27,7 @@ FONT_PREF_LANG(Gujarati, "x-gujr", x_gujr), FONT_PREF_LANG(Gurmukhi, "x-guru", x_guru), FONT_PREF_LANG(Khmer, "x-khmr", x_khmr), FONT_PREF_LANG(Malayalam, "x-mlym", x_mlym), +FONT_PREF_LANG(Mathematics, "x-math", x_math), FONT_PREF_LANG(Oriya, "x-orya", x_orya), FONT_PREF_LANG(Telugu, "x-telu", x_telu), FONT_PREF_LANG(Kannada, "x-knda", x_knda), diff --git a/gfx/thebes/gfxPlatform.h b/gfx/thebes/gfxPlatform.h index 6ce806a198..351dfdbc75 100644 --- a/gfx/thebes/gfxPlatform.h +++ b/gfx/thebes/gfxPlatform.h @@ -605,6 +605,12 @@ public: virtual void FlushContentDrawing() {} + // If a device reset has occurred, update the necessary platform backend + // bits. + virtual bool UpdateForDeviceReset() { + return false; + } + /** * Helper method, creates a draw target for a specific Azure backend. * Used by CreateOffscreenDrawTarget. diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index 14211d09ed..ac122cd866 100644 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -1195,6 +1195,30 @@ gfxWindowsPlatform::DidRenderingDeviceReset(DeviceResetReason* aResetReason) return false; } +BOOL CALLBACK +InvalidateWindowForDeviceReset(HWND aWnd, LPARAM aMsg) +{ + RedrawWindow(aWnd, nullptr, nullptr, + RDW_INVALIDATE|RDW_INTERNALPAINT|RDW_FRAME); + return TRUE; +} + +bool +gfxWindowsPlatform::UpdateForDeviceReset() +{ + if (!DidRenderingDeviceReset()) { + return false; + } + + // Trigger an ::OnPaint for each window. + ::EnumThreadWindows(GetCurrentThreadId(), + InvalidateWindowForDeviceReset, + 0); + + gfxCriticalNote << "Detected rendering device reset on refresh"; + return true; +} + void gfxWindowsPlatform::GetPlatformCMSOutputProfile(void* &mem, size_t &mem_size) { diff --git a/gfx/thebes/gfxWindowsPlatform.h b/gfx/thebes/gfxWindowsPlatform.h index 20ec4c5fc2..02c0af4e64 100644 --- a/gfx/thebes/gfxWindowsPlatform.h +++ b/gfx/thebes/gfxWindowsPlatform.h @@ -213,7 +213,8 @@ public: */ virtual bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags) override; - virtual bool DidRenderingDeviceReset(DeviceResetReason* aResetReason = nullptr) override; + bool DidRenderingDeviceReset(DeviceResetReason* aResetReason = nullptr) override; + bool UpdateForDeviceReset() override; mozilla::gfx::BackendType GetContentBackendFor(mozilla::layers::LayersBackend aLayers) override; diff --git a/js/public/Utility.h b/js/public/Utility.h index d62e62e42d..fc7c42abb9 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -302,14 +302,14 @@ static inline char* js_strdup(const char* s) * Note: Do not add a ; at the end of a use of JS_DECLARE_NEW_METHODS, * or the build will break. */ -#define JS_DECLARE_NEW_METHODS(NEWNAME, ALLOCATOR, QUALIFIERS)\ +#define JS_DECLARE_NEW_METHODS(NEWNAME, ALLOCATOR, QUALIFIERS) \ template \ QUALIFIERS T * \ NEWNAME(Args&&... args) MOZ_HEAP_ALLOCATOR { \ void* memory = ALLOCATOR(sizeof(T)); \ - return memory \ - ? new(memory) T(mozilla::Forward(args)...) \ - : nullptr; \ + return MOZ_LIKELY(memory) \ + ? new(memory) T(mozilla::Forward(args)...) \ + : nullptr; \ } /* diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index 15b36626a1..393779c44c 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -14,7 +14,7 @@ function ModuleGetExportedNames(exportStarSet = []) let module = this; // Step 2 - if (module in exportStarSet) + if (callFunction(ArrayIncludes, exportStarSet, module)) return []; // Step 3 @@ -47,7 +47,7 @@ function ModuleGetExportedNames(exportStarSet = []) exportStarSet); for (let j = 0; j < starNames.length; j++) { let n = starNames[j]; - if (n !== "default" && !(n in exportedNames)) + if (n !== "default" && !callFunction(ArrayIncludes, exportedNames, n)) _DefineDataProperty(exportedNames, namesCount++, n); } } @@ -104,7 +104,7 @@ function ModuleResolveExport(exportName, resolveSet = [], exportStarSet = []) } // Step 7 - if (module in exportStarSet) + if (callFunction(ArrayIncludes, exportStarSet, module)) return null; // Step 8 @@ -214,9 +214,9 @@ function ModuleDeclarationInstantiation() let e = indirectExportEntries[i]; let resolution = callFunction(module.resolveExport, module, e.exportName); if (resolution === null) - ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT); + ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT, e.exportName); if (resolution === "ambiguous") - ThrowSyntaxError(JSMSG_AMBIGUOUS_INDIRECT_EXPORT); + ThrowSyntaxError(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, e.exportName); } // Step 12 @@ -231,9 +231,9 @@ function ModuleDeclarationInstantiation() let resolution = callFunction(importedModule.resolveExport, importedModule, imp.importName); if (resolution === null) - ThrowSyntaxError(JSMSG_MISSING_IMPORT); + ThrowSyntaxError(JSMSG_MISSING_IMPORT, imp.importName); if (resolution === "ambiguous") - ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT); + ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT, imp.importName); CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName); } } diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index 9f470daeff..1d2085bf77 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -16,9 +16,6 @@ using namespace js; using namespace js::frontend; -typedef JS::Rooted RootedImportEntry; -typedef JS::Rooted RootedExportEntry; - template static bool ModuleValueGetterImpl(JSContext* cx, const CallArgs& args) @@ -121,7 +118,7 @@ ImportEntryObject::create(JSContext* cx, if (!obj) return nullptr; - RootedImportEntry self(cx, &obj->as()); + RootedImportEntryObject self(cx, &obj->as()); self->initReservedSlot(ModuleRequestSlot, StringValue(moduleRequest)); self->initReservedSlot(ImportNameSlot, StringValue(importName)); self->initReservedSlot(LocalNameSlot, StringValue(localName)); @@ -194,7 +191,7 @@ ExportEntryObject::create(JSContext* cx, if (!obj) return nullptr; - RootedExportEntry self(cx, &obj->as()); + RootedExportEntryObject self(cx, &obj->as()); self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName)); self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest)); self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName)); @@ -896,8 +893,9 @@ js::InitModuleClasses(JSContext* cx, HandleObject obj) /////////////////////////////////////////////////////////////////////////// // ModuleBuilder -ModuleBuilder::ModuleBuilder(JSContext* cx) +ModuleBuilder::ModuleBuilder(JSContext* cx, HandleModuleObject module) : cx_(cx), + module_(cx, module), requestedModules_(cx, AtomVector(cx)), importedBoundNames_(cx, AtomVector(cx)), importEntries_(cx, ImportEntryVector(cx)), @@ -908,7 +906,7 @@ ModuleBuilder::ModuleBuilder(JSContext* cx) {} bool -ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject module) +ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode) { MOZ_ASSERT(moduleNode->isKind(PNK_MODULE)); @@ -940,21 +938,21 @@ ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject } for (const auto& e : exportEntries_) { - RootedExportEntry exp(cx_, e); + RootedExportEntryObject exp(cx_, e); if (!exp->moduleRequest()) { - RootedImportEntry importEntry(cx_, importEntryFor(exp->localName())); + RootedImportEntryObject importEntry(cx_, importEntryFor(exp->localName())); if (!importEntry) { - if (!localExportEntries_.append(exp)) + if (!appendLocalExportEntry(exp)) return false; } else { if (importEntry->importName() == cx_->names().star) { - if (!localExportEntries_.append(exp)) + if (!appendLocalExportEntry(exp)) return false; } else { RootedAtom exportName(cx_, exp->exportName()); RootedAtom moduleRequest(cx_, importEntry->moduleRequest()); RootedAtom importName(cx_, importEntry->importName()); - RootedExportEntry exportEntry(cx_); + RootedExportEntryObject exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, @@ -994,7 +992,7 @@ ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject if (!starExportEntries) return false; - module->initImportExportData(requestedModules, + module_->initImportExportData(requestedModules, importEntries, localExportEntries, indirectExportEntries, @@ -1003,6 +1001,19 @@ ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject return true; } +bool +ModuleBuilder::appendLocalExportEntry(HandleExportEntryObject exp) +{ + if (!module_->initialEnvironment().lookup(cx_, AtomToId(exp->localName()))) { + JSAutoByteString str; + str.encodeLatin1(cx_, exp->localName()); + JS_ReportErrorNumber(cx_, GetErrorMessage, nullptr, JSMSG_MISSING_EXPORT, str.ptr()); + return false; + } + + return localExportEntries_.append(exp); +} + bool ModuleBuilder::processImport(frontend::ParseNode* pn) { @@ -1025,7 +1036,7 @@ ModuleBuilder::processImport(frontend::ParseNode* pn) if (!importedBoundNames_.append(localName)) return false; - RootedImportEntry importEntry(cx_); + RootedImportEntryObject importEntry(cx_); importEntry = ImportEntryObject::create(cx_, module, importName, localName); if (!importEntry || !importEntries_.append(importEntry)) return false; @@ -1049,7 +1060,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) MOZ_ASSERT(spec->isKind(PNK_EXPORT_SPEC)); RootedAtom localName(cx_, spec->pn_left->pn_atom); RootedAtom exportName(cx_, spec->pn_right->pn_atom); - if (!appendLocalExportEntry(exportName, localName)) + if (!appendExportEntry(exportName, localName)) return false; } break; @@ -1058,7 +1069,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) RootedFunction func(cx_, kid->pn_funbox->function()); RootedAtom localName(cx_, func->atom()); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); - if (!appendLocalExportEntry(exportName, localName)) + if (!appendExportEntry(exportName, localName)) return false; break; } @@ -1068,7 +1079,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) MOZ_ASSERT(cls.names()); RootedAtom localName(cx_, cls.names()->innerBinding()->pn_atom); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); - if (!appendLocalExportEntry(exportName, localName)) + if (!appendExportEntry(exportName, localName)) return false; break; } @@ -1083,7 +1094,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) MOZ_ASSERT(var->isKind(PNK_NAME)); RootedAtom localName(cx_, var->pn_atom); RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get()); - if (!appendLocalExportEntry(exportName, localName)) + if (!appendExportEntry(exportName, localName)) return false; } break; @@ -1093,7 +1104,7 @@ ModuleBuilder::processExport(frontend::ParseNode* pn) MOZ_ASSERT(isDefault); RootedAtom localName(cx_, cx_->names().starDefaultStar); RootedAtom exportName(cx_, cx_->names().default_); - if (!appendLocalExportEntry(exportName, localName)) + if (!appendExportEntry(exportName, localName)) return false; break; } @@ -1115,12 +1126,12 @@ ModuleBuilder::processExportFrom(frontend::ParseNode* pn) if (spec->isKind(PNK_EXPORT_SPEC)) { RootedAtom bindingName(cx_, spec->pn_left->pn_atom); RootedAtom exportName(cx_, spec->pn_right->pn_atom); - if (!appendIndirectExportEntry(exportName, module, bindingName)) + if (!appendExportFromEntry(exportName, module, bindingName)) return false; } else { MOZ_ASSERT(spec->isKind(PNK_EXPORT_BATCH_SPEC)); RootedAtom importName(cx_, cx_->names().star); - if (!appendIndirectExportEntry(nullptr, module, importName)) + if (!appendExportFromEntry(nullptr, module, importName)) return false; } } @@ -1139,7 +1150,7 @@ ModuleBuilder::importEntryFor(JSAtom* localName) } bool -ModuleBuilder::appendLocalExportEntry(HandleAtom exportName, HandleAtom localName) +ModuleBuilder::appendExportEntry(HandleAtom exportName, HandleAtom localName) { Rooted exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName); @@ -1147,8 +1158,8 @@ ModuleBuilder::appendLocalExportEntry(HandleAtom exportName, HandleAtom localNam } bool -ModuleBuilder::appendIndirectExportEntry(HandleAtom exportName, HandleAtom moduleRequest, - HandleAtom importName) +ModuleBuilder::appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest, + HandleAtom importName) { Rooted exportEntry(cx_); exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr); diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index c5b306daa1..bd6a61dc18 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -54,6 +54,9 @@ class ImportEntryObject : public NativeObject JSAtom* localName(); }; +typedef Rooted RootedImportEntryObject; +typedef Handle HandleImportEntryObject; + class ExportEntryObject : public NativeObject { public: @@ -80,6 +83,9 @@ class ExportEntryObject : public NativeObject JSAtom* localName(); }; +typedef Rooted RootedExportEntryObject; +typedef Handle HandleExportEntryObject; + class IndirectBindingMap { public: @@ -268,9 +274,9 @@ class ModuleObject : public NativeObject class MOZ_STACK_CLASS ModuleBuilder { public: - explicit ModuleBuilder(JSContext* cx); + explicit ModuleBuilder(JSContext* cx, HandleModuleObject module); - bool buildAndInit(frontend::ParseNode* pn, HandleModuleObject module); + bool buildAndInit(frontend::ParseNode* pn); private: using AtomVector = TraceableVector; @@ -281,8 +287,8 @@ class MOZ_STACK_CLASS ModuleBuilder using RootedExportEntryVector = JS::Rooted ; JSContext* cx_; + RootedModuleObject module_; RootedAtomVector requestedModules_; - RootedAtomVector importedBoundNames_; RootedImportEntryVector importEntries_; RootedExportEntryVector exportEntries_; @@ -296,12 +302,14 @@ class MOZ_STACK_CLASS ModuleBuilder ImportEntryObject* importEntryFor(JSAtom* localName); - bool appendLocalExportEntry(HandleAtom exportName, HandleAtom localName); - bool appendIndirectExportEntry(HandleAtom exportName, HandleAtom moduleRequest, - HandleAtom importName); + bool appendExportEntry(HandleAtom exportName, HandleAtom localName); + bool appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest, + HandleAtom importName); bool maybeAppendRequestedModule(HandleAtom module); + bool appendLocalExportEntry(HandleExportEntryObject exp); + template ArrayObject* createArray(const TraceableVector& vector); }; diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 7500b9aff4..33431517d5 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -1010,6 +1010,10 @@ static const JSFunctionSpec object_static_methods[] = { JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0), JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2, 0), JS_FN("keys", obj_keys, 1, 0), +#ifndef RELEASE_BUILD + JS_SELF_HOSTED_FN("values", "ObjectValues", 1, JSPROP_DEFINE_LATE), + JS_SELF_HOSTED_FN("entries", "ObjectEntries", 1, JSPROP_DEFINE_LATE), +#endif JS_FN("is", obj_is, 2, 0), JS_FN("defineProperty", obj_defineProperty, 3, 0), JS_FN("defineProperties", obj_defineProperties, 2, 0), diff --git a/js/src/builtin/Object.js b/js/src/builtin/Object.js index 72d0f95694..5bde011db9 100644 --- a/js/src/builtin/Object.js +++ b/js/src/builtin/Object.js @@ -134,3 +134,48 @@ function ObjectLookupGetter(name) { } while (object !== null); } +// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.values +function ObjectValues(O) { + // Steps 1-2. + var object = ToObject(O); + + // Steps 3-4. + // EnumerableOwnProperties is inlined here. + var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN); + var values = []; + var valuesCount = 0; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (!callFunction(std_Object_propertyIsEnumerable, object, key)) + continue; + + var value = object[key]; + _DefineDataProperty(values, valuesCount++, value); + } + + // Step 5. + return values; +} + +// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.entries +function ObjectEntries(O) { + // Steps 1-2. + var object = ToObject(O); + + // Steps 3-4. + // EnumerableOwnProperties is inlined here. + var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN); + var entries = []; + var entriesCount = 0; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (!callFunction(std_Object_propertyIsEnumerable, object, key)) + continue; + + var value = object[key]; + _DefineDataProperty(entries, entriesCount++, [key, value]); + } + + // Step 5. + return entries; +} diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index 5bc4ecb6bb..17b44c8d7f 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -927,6 +927,7 @@ js::ExecuteRegExp(JSContext* cx, HandleObject regexp, HandleString string, return RegExpRunStatus_Error; } else if (reobj->needUpdateLastIndex()) { /* Steps 18.a-b. */ + MOZ_ASSERT(matches && !matches->empty()); if (!SetLastIndex(cx, reobj, (*matches)[0].limit)) return RegExpRunStatus_Error; } diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index da5e5ea83d..215d94160f 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -298,8 +298,8 @@ GC(JSContext* cx, unsigned argc, Value* vp) char buf[256] = { '\0' }; #ifndef JS_MORE_DETERMINISTIC - JS_snprintf(buf, sizeof(buf), "before %lu, after %lu\n", - (unsigned long)preBytes, (unsigned long)cx->runtime()->gc.usage.gcBytes()); + JS_snprintf(buf, sizeof(buf), "before %" PRIuSIZE ", after %" PRIuSIZE "\n", + preBytes, cx->runtime()->gc.usage.gcBytes()); #endif JSString* str = JS_NewStringCopyZ(cx, buf); if (!str) diff --git a/js/src/builtin/TypedObject.js b/js/src/builtin/TypedObject.js index aebac7e32d..946b41fc67 100644 --- a/js/src/builtin/TypedObject.js +++ b/js/src/builtin/TypedObject.js @@ -650,14 +650,14 @@ function ArrayShorthand(...dims) { if (!IsObject(this) || !ObjectIsTypeDescr(this)) ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS); - var T = GetTypedObjectModule(); + var AT = GetTypedObjectModule().ArrayType; if (dims.length == 0) ThrowTypeError(JSMSG_TYPEDOBJECT_BAD_ARGS); var accum = this; for (var i = dims.length - 1; i >= 0; i--) - accum = new T.ArrayType(accum, dims[i]); + accum = new AT(accum, dims[i]); return accum; } @@ -1059,7 +1059,11 @@ function MapTypedSeqImpl(inArray, depth, outputType, func) { } function DoMapTypedSeqDepthN() { - var indices = new Uint32Array(depth); + // Simulate Uint32Array(depth) with a dumber (and more accessible) + // datastructure. + var indices = new List(); + for (var i = 0; i < depth; i++) + callFunction(std_Array_push, indices, 0); for (var i = 0; i < totalLength; i++) { // Prepare input element and out pointer @@ -1156,9 +1160,9 @@ function FilterTypedSeqImpl(array, func) { inOffset += size; } - var T = GetTypedObjectModule(); + var AT = GetTypedObjectModule().ArrayType; - var resultType = new T.ArrayType(elementType, count); + var resultType = new AT(elementType, count); var result = new resultType(); for (var i = 0, j = 0; i < array.length; i++) { if (GET_BIT(flags, i)) diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 7f836ebe01..b9ec4cbbff 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -27,7 +27,7 @@ // Assertions, defined here instead of in the header above to make `assert` // invisible to C++. #ifdef DEBUG -#define assert(b, info) if (!(b)) AssertionFailed(info) +#define assert(b, info) if (!(b)) AssertionFailed(__FILE__ + ":" + __LINE__ + ": " + info) #else #define assert(b, info) // Elided assertion. #endif diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js index 141da64114..860e06eacb 100644 --- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -135,9 +135,6 @@ var ignoreFunctions = { "JSObject* js::GetWeakmapKeyDelegate(JSObject*)" : true, // FIXME: mark with AutoSuppressGCAnalysis instead "uint8 NS_IsMainThread()" : true, - // Bug 1056410 - devirtualization prevents the standard nsISupports::Release heuristic from working - "uint32 nsXPConnect::Release()" : true, - // Has an indirect call under it by the name "__f", which seemed too // generic to ignore by itself. "void* std::_Locale_impl::~_Locale_impl(int32)" : true, diff --git a/js/src/devtools/rootAnalysis/build/gcc.manifest b/js/src/devtools/rootAnalysis/build/gcc.manifest index 7dcd080f30..684f5dd3f7 100644 --- a/js/src/devtools/rootAnalysis/build/gcc.manifest +++ b/js/src/devtools/rootAnalysis/build/gcc.manifest @@ -10,8 +10,8 @@ "unpack": true }, { -"size": 4431740, -"digest": "68fc56b0fb0cdba629b95683d6649ff76b00dccf97af90960c3d7716f6108b2162ffd5ffcd5c3a60a21b28674df688fe4dabc67345e2da35ec5abeae3d48c8e3", +"size": 12057960, +"digest": "6105d6432943141cffb40020dc5ba3a793650bdeb3af9bd5e56d3796c5f03df9962a73e521646cd71fbfb5e266c1e74716ad722fb6055589dfb7d35175bca89e", "algorithm": "sha512", "filename": "gtk3.tar.xz", "unpack": true diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index a3b7c9e986..8cd69f9cb3 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -601,8 +601,8 @@ ModuleObject* BytecodeCompiler::compileModule() return nullptr; } - ModuleBuilder builder(cx->asJSContext()); - if (!builder.buildAndInit(pn, module)) + ModuleBuilder builder(cx->asJSContext(), module); + if (!builder.buildAndInit(pn)) return nullptr; parser->handler.freeTree(pn); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 6c13497846..5ab7422dc4 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -4413,13 +4413,25 @@ BytecodeEmitter::emitVariables(ParseNode* pn, VarEmitOption emitOption) * i' to be hoisted out of the loop. */ MOZ_ASSERT(binding->isOp(JSOP_NOP)); - MOZ_ASSERT(emitOption != DefineVars && emitOption != AnnexB); + MOZ_ASSERT(emitOption != DefineVars); + MOZ_ASSERT_IF(emitOption == AnnexB, binding->pn_left->isKind(PNK_NAME)); - /* - * To allow the front end to rewrite var f = x; as f = x; when a - * function f(){} precedes the var, detect simple name assignment - * here and initialize the name. - */ + // To allow the front end to rewrite |var f = x;| as |f = x;| when a + // |function f(){}| precedes the var, detect simple name assignment + // here and initialize the name. + // + // There is a corner case where a function declaration synthesizes + // an Annex B declaration, which in turn gets rewritten later as a + // simple assignment due to hoisted function declaration of the + // same name. For example, + // + // { + // // Synthesizes an Annex B declaration because no 'f' binding + // // yet exists. This later gets rewritten as an assignment when + // // the outer function 'f' gets hoisted. + // function f() {} + // } + // function f() {} if (binding->pn_left->isKind(PNK_NAME)) { if (!emitSingleVariable(pn, binding->pn_left, binding->pn_right, emitOption)) return false; @@ -6728,6 +6740,16 @@ BytecodeEmitter::emitReturn(ParseNode* pn) if (!emit1((isGenerator || isDerivedClassConstructor) ? JSOP_SETRVAL : JSOP_RETURN)) return false; + // Make sure that we emit this before popping the blocks in prepareForNonLocalJump, + // to ensure that the error is thrown while the scope-chain is still intact. + if (isDerivedClassConstructor) { + BindingIter bi = Bindings::thisBinding(cx, script); + if (!emitLoadFromTopScope(bi)) + return false; + if (!emit1(JSOP_CHECKRETURN)) + return false; + } + NonLocalExitScope nle(this); if (!nle.prepareForNonLocalJump(nullptr)) @@ -6745,11 +6767,6 @@ BytecodeEmitter::emitReturn(ParseNode* pn) return false; } else if (isDerivedClassConstructor) { MOZ_ASSERT(code()[top] == JSOP_SETRVAL); - BindingIter bi = Bindings::thisBinding(cx, script); - if (!emitLoadFromTopScope(bi)) - return false; - if (!emit1(JSOP_CHECKRETURN)) - return false; if (!emit1(JSOP_RETRVAL)) return false; } else if (top + static_cast(JSOP_RETURN_LENGTH) != offset()) { @@ -7145,6 +7162,18 @@ BytecodeEmitter::emitDeleteExpression(ParseNode* node) return emit1(JSOP_TRUE); } +bool +BytecodeEmitter::emitDebugOnlyCheckSelfHosted() +{ +#ifdef DEBUG + if (emitterMode == BytecodeEmitter::SelfHosting) { + if (!emit1(JSOP_DEBUGCHECKSELFHOSTED)) + return false; + } +#endif + return true; +} + bool BytecodeEmitter::emitSelfHostedCallFunction(ParseNode* pn) { @@ -7157,22 +7186,27 @@ BytecodeEmitter::emitSelfHostedCallFunction(ParseNode* pn) // // argc is set to the amount of actually emitted args and the // emitting of args below is disabled by setting emitArgs to false. + ParseNode* pn2 = pn->pn_head; + const char* errorName = pn2->name() == cx->names().callFunction ? + "callFunction" : "callContentFunction"; if (pn->pn_count < 3) { - reportError(pn, JSMSG_MORE_ARGS_NEEDED, "callFunction", "1", "s"); + reportError(pn, JSMSG_MORE_ARGS_NEEDED, errorName, "2", "s"); + return false; + } + + if (pn->getOp() != JSOP_CALL) { + reportError(pn, JSMSG_NOT_CONSTRUCTOR, errorName); return false; } - ParseNode* pn2 = pn->pn_head; ParseNode* funNode = pn2->pn_next; if (!emitTree(funNode)) return false; -#ifdef DEBUG if (pn2->name() != cx->names().callContentFunction) { - if (!emit1(JSOP_DEBUGCHECKSELFHOSTED)) + if (!emitDebugOnlyCheckSelfHosted()) return false; } -#endif ParseNode* thisArg = funNode->pn_next; if (!emitTree(thisArg)) @@ -7272,8 +7306,9 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) // We shouldn't see foo(bar) = x in self-hosted code. MOZ_ASSERT(!(pn->pn_xflags & PNX_SETCALL)); - // Calls to "forceInterpreter", "callFunction" or "resumeGenerator" - // in self-hosted code generate inline bytecode. + // Calls to "forceInterpreter", "callFunction", + // "callContentFunction", or "resumeGenerator" in self-hosted + // code generate inline bytecode. if (pn2->name() == cx->names().callFunction || pn2->name() == cx->names().callContentFunction) { @@ -7296,6 +7331,10 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) if (!emitPropOp(pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP)) return false; } + + if (!emitDebugOnlyCheckSelfHosted()) + return false; + break; case PNK_ELEM: if (pn2->as().isSuper()) { @@ -7309,6 +7348,10 @@ BytecodeEmitter::emitCallOrNew(ParseNode* pn) return false; } } + + if (!emitDebugOnlyCheckSelfHosted()) + return false; + break; case PNK_FUNCTION: /* diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 7d4b31d1cc..bf9e9b89bb 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -608,6 +608,7 @@ struct BytecodeEmitter bool emitConditionalExpression(ConditionalExpression& conditional); bool emitCallOrNew(ParseNode* pn); + bool emitDebugOnlyCheckSelfHosted(); bool emitSelfHostedCallFunction(ParseNode* pn); bool emitSelfHostedResumeGenerator(ParseNode* pn); bool emitSelfHostedForceInterpreter(ParseNode* pn); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 0dfeb0cc4d..243bfadb6a 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -1163,22 +1163,32 @@ ObjectBox::asModuleBox() return static_cast(this); } +/* static */ void +ObjectBox::TraceList(JSTracer* trc, ObjectBox* listHead) +{ + for (ObjectBox* box = listHead; box; box = box->traceLink) + box->trace(trc); +} + void ObjectBox::trace(JSTracer* trc) { - ObjectBox* box = this; - while (box) { - TraceRoot(trc, &box->object, "parser.object"); - if (box->isFunctionBox()) { - FunctionBox* funbox = box->asFunctionBox(); - funbox->bindings.trace(trc); - if (funbox->enclosingStaticScope_) - TraceRoot(trc, &funbox->enclosingStaticScope_, "funbox-enclosingStaticScope"); - } else if (box->isModuleBox()) { - ModuleBox* modulebox = box->asModuleBox(); - modulebox->bindings.trace(trc); - modulebox->exportNames.trace(trc); - } - box = box->traceLink; - } + TraceRoot(trc, &object, "parser.object"); +} + +void +FunctionBox::trace(JSTracer* trc) +{ + ObjectBox::trace(trc); + bindings.trace(trc); + if (enclosingStaticScope_) + TraceRoot(trc, &enclosingStaticScope_, "funbox-enclosingStaticScope"); +} + +void +ModuleBox::trace(JSTracer* trc) +{ + ObjectBox::trace(trc); + bindings.trace(trc); + exportNames.trace(trc); } diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 71308c1657..ebbb1e6995 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -1722,7 +1722,9 @@ class ObjectBox FunctionBox* asFunctionBox(); bool isModuleBox() { return object->is(); } ModuleBox* asModuleBox(); - void trace(JSTracer* trc); + virtual void trace(JSTracer* trc); + + static void TraceList(JSTracer* trc, ObjectBox* listHead); protected: friend struct CGObjectList; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 7ccb77ae3a..53e6a38242 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -855,7 +855,7 @@ template void Parser::trace(JSTracer* trc) { - traceListHead->trace(trc); + ObjectBox::TraceList(trc, traceListHead); } void @@ -5050,8 +5050,32 @@ template<> bool Parser::addExportName(JSAtom* exportName) { - JS_ALWAYS_FALSE(abortIfSyntaxParser()); - return SyntaxParseHandler::NodeFailure; + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; +} + +template<> +bool +Parser::addExportNamesForDeclaration(ParseNode* node) +{ + MOZ_ASSERT(node->isArity(PN_LIST)); + for (ParseNode* binding = node->pn_head; binding; binding = binding->pn_next) { + if (binding->isKind(PNK_ASSIGN)) + binding = binding->pn_left; + MOZ_ASSERT(binding->isKind(PNK_NAME)); + if (!addExportName(binding->pn_atom)) + return false; + } + + return true; +} + +template<> +bool +Parser::addExportNamesForDeclaration(Node node) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return false; } template<> @@ -5228,15 +5252,8 @@ Parser::exportDeclaration() return null(); if (!MatchOrInsertSemicolonAfterExpression(tokenStream)) return null(); - - MOZ_ASSERT(kid->isArity(PN_LIST)); - for (ParseNode* var = kid->pn_head; var; var = var->pn_next) { - if (var->isKind(PNK_ASSIGN)) - var = var->pn_left; - MOZ_ASSERT(var->isKind(PNK_NAME)); - if (!addExportName(var->pn_atom)) - return null(); - } + if (!addExportNamesForDeclaration(kid)) + return null(); break; case TOK_DEFAULT: { @@ -5280,6 +5297,8 @@ Parser::exportDeclaration() kid = lexicalDeclaration(YieldIsName, tt == TOK_CONST); if (!kid) return null(); + if (!addExportNamesForDeclaration(kid)) + return null(); break; default: @@ -8922,11 +8941,6 @@ Parser::memberExpr(YieldHandling yieldHandling, TripledotHandling return handler.newSetThis(thisName, nextMember); } - if (options().selfHostingMode && handler.isPropertyAccess(lhs)) { - report(ParseError, false, null(), JSMSG_SELFHOSTED_METHOD_CALL); - return null(); - } - nextMember = tt == TOK_LP ? handler.newCall() : handler.newTaggedTemplate(); if (!nextMember) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index f4b6e3f383..14674e01de 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -761,6 +761,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node newBoundImportForCurrentName(); bool namedImportsOrNamespaceImport(TokenKind tt, Node importSpecSet); bool addExportName(JSAtom* exportName); + bool addExportNamesForDeclaration(Node node); enum ClassContext { ClassStatement, ClassExpression }; Node classDefinition(YieldHandling yieldHandling, ClassContext classContext, DefaultHandling defaultHandling); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index c3633fbb4e..0b79e39d8e 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -427,6 +427,8 @@ class FunctionBox : public ObjectBox, public SharedContext isDerivedClassConstructor() || isGenerator(); } + + void trace(JSTracer* trc) override; }; class ModuleBox : public ObjectBox, public SharedContext @@ -442,6 +444,8 @@ class ModuleBox : public ObjectBox, public SharedContext ObjectBox* toObjectBox() override { return this; } ModuleObject* module() const { return &object->as(); } JSObject* staticScope() const override { return module(); } + + void trace(JSTracer* trc) override; }; inline FunctionBox* diff --git a/js/src/jit-test/modules/ambiguous.js b/js/src/jit-test/modules/ambiguous.js new file mode 100644 index 0000000000..3e0d31e3ed --- /dev/null +++ b/js/src/jit-test/modules/ambiguous.js @@ -0,0 +1,2 @@ +export * from 'module1.js'; +export * from 'module1a.js'; diff --git a/js/src/jit-test/modules/module1a.js b/js/src/jit-test/modules/module1a.js new file mode 100644 index 0000000000..66d48fc1e6 --- /dev/null +++ b/js/src/jit-test/modules/module1a.js @@ -0,0 +1 @@ +export const a = 2; diff --git a/js/src/jit-test/modules/recursiveStarExport.js b/js/src/jit-test/modules/recursiveStarExport.js new file mode 100644 index 0000000000..681c7be91b --- /dev/null +++ b/js/src/jit-test/modules/recursiveStarExport.js @@ -0,0 +1 @@ +export * from 'recursiveStarExport.js'; diff --git a/js/src/jit-test/tests/TypedObject/bug1232159.js b/js/src/jit-test/tests/TypedObject/bug1232159.js new file mode 100644 index 0000000000..9d75b22885 --- /dev/null +++ b/js/src/jit-test/tests/TypedObject/bug1232159.js @@ -0,0 +1,13 @@ +Function.prototype.prototype = function() {} + +var type = TypedObject.uint8.array(4).array(4); +var x = new type([ + [, , , 0], + [, , , 0], + [, , , 0], + [, , , 0] +]); + +x.map(2, function(y) { + return 0; +}); diff --git a/js/src/jit-test/tests/modules/ambiguous-import.js b/js/src/jit-test/tests/modules/ambiguous-import.js new file mode 100644 index 0000000000..6a91f9537b --- /dev/null +++ b/js/src/jit-test/tests/modules/ambiguous-import.js @@ -0,0 +1,2 @@ +// |jit-test| module; error: SyntaxError +import { a } from "ambiguous.js"; diff --git a/js/src/jit-test/tests/modules/ambiguous-indirect-export.js b/js/src/jit-test/tests/modules/ambiguous-indirect-export.js new file mode 100644 index 0000000000..17949955ea --- /dev/null +++ b/js/src/jit-test/tests/modules/ambiguous-indirect-export.js @@ -0,0 +1,2 @@ +// |jit-test| module; error: SyntaxError +export { a } from "ambiguous.js"; diff --git a/js/src/jit-test/tests/modules/duplicate-exports.js b/js/src/jit-test/tests/modules/duplicate-exports.js index a34b1024c4..7e45ac6f37 100644 --- a/js/src/jit-test/tests/modules/duplicate-exports.js +++ b/js/src/jit-test/tests/modules/duplicate-exports.js @@ -7,15 +7,26 @@ function testSyntaxError(source) { }, SyntaxError); } +// SyntexError due to duplicate exports testSyntaxError("export var v; export var v;"); testSyntaxError("export var x, y, z; export var y;"); - +testSyntaxError("export let v; var w; export {w as v};"); +testSyntaxError("export const v; var w; export {w as v};"); +testSyntaxError("export var v; let w; export {w as v};"); +testSyntaxError("export var v; const w; export {w as v};"); testSyntaxError("export default 1; export default 2;"); -testSyntaxError("export var default; export default 1;"); -testSyntaxError("export var default; export default function() {};"); -testSyntaxError("export var default; export default function foo() {};"); -testSyntaxError("export var default; export default export class { constructor() {} };"); -testSyntaxError("export var default; export default export class foo { constructor() {} };"); - +testSyntaxError("export default 1; export default function() {};"); +testSyntaxError("export default 1; export default function foo() {};"); testSyntaxError("var v; export {v}; export {v};"); testSyntaxError("var v, x; export {v}; export {x as v};"); +testSyntaxError("export default 1; export default export class { constructor() {} };"); +testSyntaxError("export default 1; export default export class foo { constructor() {} };"); + +// SyntaxError due to redeclared binding +testSyntaxError("export let v; export let v;"); +testSyntaxError("export let x, y, z; export let y;"); +testSyntaxError("export const v = 0; export const v = 0;"); +testSyntaxError("export const x = 0, y = 0, z = 0; export const y = 0;"); +testSyntaxError("export var v; export let v;"); +testSyntaxError("export var v; export const v = 0;"); +testSyntaxError("export let v; export const v;"); diff --git a/js/src/jit-test/tests/modules/import-not-found.js b/js/src/jit-test/tests/modules/import-not-found.js new file mode 100644 index 0000000000..05e6e1e8d5 --- /dev/null +++ b/js/src/jit-test/tests/modules/import-not-found.js @@ -0,0 +1,2 @@ +// |jit-test| module; error: SyntaxError +import { foo } from "module1.js"; diff --git a/js/src/jit-test/tests/modules/missing-indirect-export.js b/js/src/jit-test/tests/modules/missing-indirect-export.js new file mode 100644 index 0000000000..207e49ba05 --- /dev/null +++ b/js/src/jit-test/tests/modules/missing-indirect-export.js @@ -0,0 +1,2 @@ +// |jit-test| module; error: SyntaxError +export { foo } from "module1.js"; diff --git a/js/src/jit-test/tests/modules/recursive-star-export.js b/js/src/jit-test/tests/modules/recursive-star-export.js new file mode 100644 index 0000000000..0313321c59 --- /dev/null +++ b/js/src/jit-test/tests/modules/recursive-star-export.js @@ -0,0 +1,2 @@ +// |jit-test| module; +import * as ns from "recursiveStarExport.js"; diff --git a/js/src/jit-test/tests/modules/unbound-export.js b/js/src/jit-test/tests/modules/unbound-export.js new file mode 100644 index 0000000000..f70405d56f --- /dev/null +++ b/js/src/jit-test/tests/modules/unbound-export.js @@ -0,0 +1,2 @@ +// |jit-test| module; error: SyntaxError: local binding for export 'b' not found +export { b }; diff --git a/js/src/js.msg b/js/src/js.msg index 47ecefabed..87d39c63d4 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -317,7 +317,6 @@ MSG_DEF(JSMSG_RESERVED_ID, 1, JSEXN_SYNTAXERR, "{0} is a reserved id MSG_DEF(JSMSG_REST_WITH_DEFAULT, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default") MSG_DEF(JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, 1, JSEXN_SYNTAXERR, "self-hosted code cannot contain top-level {0} declarations") MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups") -MSG_DEF(JSMSG_SELFHOSTED_METHOD_CALL, 0, JSEXN_SYNTAXERR, "self-hosted code may not contain direct method calls") MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND, 0, JSEXN_SYNTAXERR, "missing ; after for-loop condition") MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT, 0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer") MSG_DEF(JSMSG_SEMI_BEFORE_STMNT, 0, JSEXN_SYNTAXERR, "missing ; before statement") @@ -526,8 +525,9 @@ MSG_DEF(JSMSG_REINIT_THIS, 0, JSEXN_REFERENCEERR, "super() called twice in // Modules MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *") -MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "indirect export not found") -MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export") -MSG_DEF(JSMSG_MISSING_IMPORT, 0, JSEXN_SYNTAXERR, "import not found") -MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 0, JSEXN_SYNTAXERR, "ambiguous import") +MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 1, JSEXN_SYNTAXERR, "indirect export '{0}' not found") +MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 1, JSEXN_SYNTAXERR, "ambiguous indirect export '{0}'") +MSG_DEF(JSMSG_MISSING_IMPORT, 1, JSEXN_SYNTAXERR, "import '{0}' not found") +MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 1, JSEXN_SYNTAXERR, "ambiguous import '{0}'") MSG_DEF(JSMSG_MISSING_NAMESPACE_EXPORT, 0, JSEXN_SYNTAXERR, "export not found for namespace") +MSG_DEF(JSMSG_MISSING_EXPORT, 1, JSEXN_SYNTAXERR, "local binding for export '{0}' not found") diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index be10cb528d..a5a7fa2257 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2124,9 +2124,10 @@ TryNotes(JSContext* cx, HandleScript script, Sprinter* sp) Sprint(sp, "\nException table:\nkind stack start end\n"); do { MOZ_ASSERT(tn->kind < ArrayLength(TryNoteNames)); + uint8_t startOff = script->pcToOffset(script->main()) + tn->start; Sprint(sp, " %-7s %6u %8u %8u\n", TryNoteNames[tn->kind], tn->stackDepth, - tn->start, tn->start + tn->length); + startOff, startOff + tn->length); } while (++tn != tnlimit); return true; } diff --git a/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnTry.js b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnTry.js new file mode 100644 index 0000000000..96791c0e78 --- /dev/null +++ b/js/src/tests/ecma_6/Class/derivedConstructorTDZReturnTry.js @@ -0,0 +1,16 @@ +class base {} +class derived extends base { + constructor() { + try { + return; + } catch (e) { + try { + return; + } catch (e) {} + } + } +} +assertThrowsInstanceOf(() => new derived, ReferenceError); + +if (typeof reportCompare === 'function') + reportCompare(0,0,"OK"); diff --git a/js/src/tests/ecma_7/Object/browser.js b/js/src/tests/ecma_7/Object/browser.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/src/tests/ecma_7/Object/entries.js b/js/src/tests/ecma_7/Object/entries.js new file mode 100644 index 0000000000..ee339ae236 --- /dev/null +++ b/js/src/tests/ecma_7/Object/entries.js @@ -0,0 +1,94 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +if ("entries" in Object) { + assertEq(Object.entries.length, 1); + + var o, entries; + + o = { a: 3, b: 2 }; + entries = Object.entries(o); + assertDeepEq(entries, [["a", 3], ["b", 2]]); + + o = { get a() { return 17; }, b: 2 }; + entries = Object.entries(o), + assertDeepEq(entries, [["a", 17], ["b", 2]]); + + o = { __iterator__: function() { return Iterator({a: 2, b: 3}); } }; + entries = Object.entries(o); + assertDeepEq(entries, [["__iterator__", o.__iterator__]]); + + o = { a: 1, b: 2 }; + delete o.a; + o.a = 3; + entries = Object.entries(o); + assertDeepEq(entries, [["b", 2], ["a", 3]]); + + o = [0, 1, 2]; + entries = Object.entries(o); + assertDeepEq(entries, [["0", 0], ["1", 1], ["2", 2]]); + + o = /./.exec("abc"); + entries = Object.entries(o); + assertDeepEq(entries, [["0", "a"], ["index", 0], ["input", "abc"]]); + + o = { a: 1, b: 2, c: 3 }; + delete o.b; + o.b = 5; + entries = Object.entries(o); + assertDeepEq(entries, [["a", 1], ["c", 3], ["b", 5]]); + + function f() { } + f.prototype.p = 1; + o = new f(); + o.g = 1; + entries = Object.entries(o); + assertDeepEq(entries, [["g", 1]]); + + var o = {get a() {delete this.b; return 1}, b: 2, c: 3}; + entries = Object.entries(o); + assertDeepEq(entries, [["a", 1], ["c", 3]]); + + assertThrowsInstanceOf(() => Object.entries(), TypeError); + assertThrowsInstanceOf(() => Object.entries(undefined), TypeError); + assertThrowsInstanceOf(() => Object.entries(null), TypeError); + + assertDeepEq(Object.entries(1), []); + assertDeepEq(Object.entries(true), []); + if (typeof Symbol === "function") + assertDeepEq(Object.entries(Symbol("foo")), []); + + assertDeepEq(Object.entries("foo"), [["0", "f"], ["1", "o"], ["2", "o"]]); + + entries = Object.entries({ + get a(){ + Object.defineProperty(this, "b", {enumerable: false}); + return "A"; + }, + b: "B" + }); + assertDeepEq(entries, [["a", "A"]]); + + let ownKeysCallCount = 0; + let getOwnPropertyDescriptorCalls = []; + let target = { a: 1, b: 2, c: 3 }; + o = new Proxy(target, { + ownKeys() { + ownKeysCallCount++; + return ["c", "a"]; + }, + getOwnPropertyDescriptor(target, key) { + getOwnPropertyDescriptorCalls.push(key); + return Object.getOwnPropertyDescriptor(target, key); + } + }); + entries = Object.entries(o); + assertEq(ownKeysCallCount, 1); + assertDeepEq(entries, [["c", 3], ["a", 1]]); + assertDeepEq(getOwnPropertyDescriptorCalls, ["c", "a"]); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_7/Object/shell.js b/js/src/tests/ecma_7/Object/shell.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/js/src/tests/ecma_7/Object/values.js b/js/src/tests/ecma_7/Object/values.js new file mode 100644 index 0000000000..78543c875a --- /dev/null +++ b/js/src/tests/ecma_7/Object/values.js @@ -0,0 +1,94 @@ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ + */ + +if ("values" in Object) { + assertEq(Object.values.length, 1); + + var o, values; + + o = { a: 3, b: 2 }; + values = Object.values(o); + assertDeepEq(values, [3, 2]); + + o = { get a() { return 17; }, b: 2 }; + values = Object.values(o), + assertDeepEq(values, [17, 2]); + + o = { __iterator__: function() { return Iterator({a: 2, b: 3}); } }; + values = Object.values(o); + assertDeepEq(values, [o.__iterator__]); + + o = { a: 1, b: 2 }; + delete o.a; + o.a = 3; + values = Object.values(o); + assertDeepEq(values, [2, 3]); + + o = [0, 1, 2]; + values = Object.values(o); + assertDeepEq(values, [0, 1, 2]); + + o = /./.exec("abc"); + values = Object.values(o); + assertDeepEq(values, ["a", 0, "abc"]); + + o = { a: 1, b: 2, c: 3 }; + delete o.b; + o.b = 5; + values = Object.values(o); + assertDeepEq(values, [1, 3, 5]); + + function f() { } + f.prototype.p = 1; + o = new f(); + o.g = 1; + values = Object.values(o); + assertDeepEq(values, [1]); + + var o = {get a() {delete this.b; return 1}, b: 2, c: 3}; + values = Object.values(o); + assertDeepEq(values, [1, 3]); + + assertThrowsInstanceOf(() => Object.values(), TypeError); + assertThrowsInstanceOf(() => Object.values(undefined), TypeError); + assertThrowsInstanceOf(() => Object.values(null), TypeError); + + assertDeepEq(Object.values(1), []); + assertDeepEq(Object.values(true), []); + if (typeof Symbol === "function") + assertDeepEq(Object.values(Symbol("foo")), []); + + assertDeepEq(Object.values("foo"), ["f", "o", "o"]); + + values = Object.values({ + get a(){ + Object.defineProperty(this, "b", {enumerable: false}); + return "A"; + }, + b: "B" + }); + assertDeepEq(values, ["A"]); + + let ownKeysCallCount = 0; + let getOwnPropertyDescriptorCalls = []; + let target = { a: 1, b: 2, c: 3 }; + o = new Proxy(target, { + ownKeys() { + ownKeysCallCount++; + return ["c", "a"]; + }, + getOwnPropertyDescriptor(target, key) { + getOwnPropertyDescriptorCalls.push(key); + return Object.getOwnPropertyDescriptor(target, key); + } + }); + values = Object.values(o); + assertEq(ownKeysCallCount, 1); + assertDeepEq(values, [3, 1]); + assertDeepEq(getOwnPropertyDescriptorCalls, ["c", "a"]); +} + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 0df19dc349..5492254a5f 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -416,7 +416,6 @@ GlobalObject::initSelfHostingBuiltins(JSContext* cx, Handle globa return InitBareBuiltinCtor(cx, global, JSProto_Array) && InitBareBuiltinCtor(cx, global, JSProto_TypedArray) && InitBareBuiltinCtor(cx, global, JSProto_Uint8Array) && - InitBareBuiltinCtor(cx, global, JSProto_Uint32Array) && InitBareWeakMapCtor(cx, global) && InitStopIterationClass(cx, global) && InitSelfHostingCollectionIteratorFunctions(cx, global) && diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 14d63acc62..669efbe8b3 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -179,7 +179,7 @@ bool js::Debug_CheckSelfHosted(JSContext* cx, HandleValue fun) { #ifndef DEBUG - MOZ_CRASH("Self hosted checks should only be done in Debug builds"); + MOZ_CRASH("self-hosted checks should only be done in Debug builds"); #endif MOZ_ASSERT(fun.isObject()); diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index e11cdf563c..a4e3b173c5 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,7 +29,7 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 325; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 327; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index fe0c6875e9..f57cdaf80c 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -1628,6 +1628,8 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) AutoRestore restoreTickStart(mTickStart); mTickStart = TimeStamp::Now(); + gfxPlatform::GetPlatform()->UpdateForDeviceReset(); + /* * The timer holds a reference to |this| while calling |Notify|. * However, implementations of |WillRefresh| are permitted to destroy diff --git a/media/mtransport/build/moz.build b/media/mtransport/build/moz.build index 969b7765bf..9732c0ee94 100644 --- a/media/mtransport/build/moz.build +++ b/media/mtransport/build/moz.build @@ -4,6 +4,8 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +include("/ipc/chromium/chromium-config.mozbuild") + EXPORTS.mtransport += [ '../dtlsidentity.h', '../m_cpp_utils.h', diff --git a/media/mtransport/common.build b/media/mtransport/common.build index 5184f2aa1c..db348098e8 100644 --- a/media/mtransport/common.build +++ b/media/mtransport/common.build @@ -63,7 +63,6 @@ if CONFIG['OS_TARGET'] in ['Darwin', 'DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD' ] elif CONFIG['OS_TARGET'] == 'Linux': DEFINES['LINUX'] = True - DEFINES['USE_INTERFACE_PRIORITIZER'] = True LOCAL_INCLUDES += [ '/media/mtransport/third_party/nrappkit/src/port/linux/include', ] @@ -81,9 +80,6 @@ elif CONFIG['OS_TARGET'] == 'WINNT': '/media/mtransport/third_party/nrappkit/src/port/win32/include', ] -if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': - DEFINES['USE_INTERFACE_PRIORITIZER'] = True - for var in ('HAVE_STRDUP', 'NR_SOCKET_IS_VOID_PTR'): DEFINES[var] = True diff --git a/media/mtransport/moz.build b/media/mtransport/moz.build index 4b532c07db..883507d3d2 100644 --- a/media/mtransport/moz.build +++ b/media/mtransport/moz.build @@ -4,6 +4,8 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +include("/ipc/chromium/chromium-config.mozbuild") + DIRS += [ '/media/mtransport/third_party', '/media/mtransport/build', diff --git a/media/mtransport/nr_socket_prsock.cpp b/media/mtransport/nr_socket_prsock.cpp index 710465d5f4..81d95ad082 100644 --- a/media/mtransport/nr_socket_prsock.cpp +++ b/media/mtransport/nr_socket_prsock.cpp @@ -106,6 +106,54 @@ nrappkit copyright: #include "nsXPCOM.h" #include "nsXULAppAPI.h" #include "runnable_utils.h" +#include "mozilla/SyncRunnable.h" +#include "nsTArray.h" +#include "mozilla/dom/TCPSocketBinding.h" +#include "nsITCPSocketCallback.h" + +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) +// csi_platform.h deep in nrappkit defines LOG_INFO and LOG_WARNING +#ifdef LOG_INFO +#define LOG_TEMP_INFO LOG_INFO +#undef LOG_INFO +#endif +#ifdef LOG_WARNING +#define LOG_TEMP_WARNING LOG_WARNING +#undef LOG_WARNING +#endif +#if defined(LOG_DEBUG) +#define LOG_TEMP_DEBUG LOG_DEBUG +#undef LOG_DEBUG +#endif +#undef strlcpy + +// TCPSocketChild.h doesn't include TypedArray.h +namespace mozilla { +namespace dom { +class ArrayBuffer; +} +} +#include "mozilla/dom/network/TCPSocketChild.h" + +#ifdef LOG_TEMP_INFO +#define LOG_INFO LOG_TEMP_INFO +#endif +#ifdef LOG_TEMP_WARNING +#define LOG_WARNING LOG_TEMP_WARNING +#endif + +#ifdef LOG_TEMP_DEBUG +#define LOG_DEBUG LOG_TEMP_DEBUG +#endif +#ifdef XP_WIN +#ifdef LOG_DEBUG +#undef LOG_DEBUG +#endif +// cloned from csi_platform.h. Win32 doesn't like how we hide symbols +#define LOG_DEBUG 7 +#endif +#endif + extern "C" { #include "nr_api.h" @@ -205,6 +253,31 @@ static void ClearSingletonOnShutdown() } #endif +static nsIThread* GetIOThreadAndAddUse_s() +{ + // Always runs on STS thread! +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) + // We need to safely release this on shutdown to avoid leaks + if (!sThread) { + sThread = new SingletonThreadHolder(NS_LITERAL_CSTRING("mtransport")); + NS_DispatchToMainThread(mozilla::WrapRunnableNM(&ClearSingletonOnShutdown)); + } + // Mark that we're using the shared thread and need it to stick around + sThread->AddUse(); + return sThread->GetThread(); +#else + static nsCOMPtr sThread; + if (!sThread) { + (void) NS_NewNamedThread("mtransport", getter_AddRefs(sThread)); + } + return sThread; +#endif +} + +NrSocketIpc::NrSocketIpc(nsIEventTarget *aThread) + : io_thread_(aThread) +{} + static TimeStamp nr_socket_short_term_violation_time; static TimeStamp nr_socket_long_term_violation_time; @@ -736,7 +809,7 @@ int NrSocket::connect(nr_transport_addr *addr) { ASSERT_ON_THREAD(ststhread_); int r,_status; PRNetAddr naddr; - int32_t status; + int32_t connect_status, getsockname_status; if ((r=nr_transport_addr_to_praddr(addr, &naddr))) ABORT(r); @@ -747,13 +820,28 @@ int NrSocket::connect(nr_transport_addr *addr) { // Note: this just means we tried to connect, not that we // are actually live. connect_invoked_ = true; - status = PR_Connect(fd_, &naddr, PR_INTERVAL_NO_WAIT); + connect_status = PR_Connect(fd_, &naddr, PR_INTERVAL_NO_WAIT); + if (connect_status != PR_SUCCESS) { + if (PR_GetError() != PR_IN_PROGRESS_ERROR) + ABORT(R_IO_ERROR); + } - if (status != PR_SUCCESS) { - if (PR_GetError() == PR_IN_PROGRESS_ERROR) - ABORT(R_WOULDBLOCK); + // If our local address is wildcard, then fill in the + // address now. + if(nr_transport_addr_is_wildcard(&my_addr_)){ + getsockname_status = PR_GetSockName(fd_, &naddr); + if (getsockname_status != PR_SUCCESS){ + r_log(LOG_GENERIC, LOG_CRIT, "Couldn't get sock name for socket"); + ABORT(R_INTERNAL); + } - ABORT(R_IO_ERROR); + if((r=nr_praddr_to_transport_addr(&naddr,&my_addr_,addr->protocol,1))) + ABORT(r); + } + + // Now return the WOULDBLOCK if needed. + if (connect_status != PR_SUCCESS) { + ABORT(R_WOULDBLOCK); } _status=0; @@ -907,10 +995,10 @@ abort: return(_status); } -NS_IMPL_ISUPPORTS(NrSocketIpcProxy, nsIUDPSocketInternal) +NS_IMPL_ISUPPORTS(NrUdpSocketIpcProxy, nsIUDPSocketInternal) nsresult -NrSocketIpcProxy::Init(const RefPtr& socket) +NrUdpSocketIpcProxy::Init(const RefPtr& socket) { nsresult rv; sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); @@ -923,7 +1011,7 @@ NrSocketIpcProxy::Init(const RefPtr& socket) return NS_OK; } -NrSocketIpcProxy::~NrSocketIpcProxy() +NrUdpSocketIpcProxy::~NrUdpSocketIpcProxy() { // Send our ref to STS to be released RUN_ON_THREAD(sts_thread_, @@ -933,39 +1021,39 @@ NrSocketIpcProxy::~NrSocketIpcProxy() // IUDPSocketInternal interfaces // callback while error happened in UDP socket operation -NS_IMETHODIMP NrSocketIpcProxy::CallListenerError(const nsACString &message, - const nsACString &filename, - uint32_t line_number) { +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerError(const nsACString &message, + const nsACString &filename, + uint32_t line_number) { return socket_->CallListenerError(message, filename, line_number); } // callback while receiving UDP packet -NS_IMETHODIMP NrSocketIpcProxy::CallListenerReceivedData(const nsACString &host, - uint16_t port, - const uint8_t *data, - uint32_t data_length) { +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerReceivedData(const nsACString &host, + uint16_t port, + const uint8_t *data, + uint32_t data_length) { return socket_->CallListenerReceivedData(host, port, data, data_length); } // callback while UDP socket is opened -NS_IMETHODIMP NrSocketIpcProxy::CallListenerOpened() { +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerOpened() { return socket_->CallListenerOpened(); } // callback while UDP socket is closed -NS_IMETHODIMP NrSocketIpcProxy::CallListenerClosed() { +NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerClosed() { return socket_->CallListenerClosed(); } -// NrSocketIpc Implementation -NrSocketIpc::NrSocketIpc() - : err_(false), - state_(NR_INIT), - io_thread_(GetIOThreadAndAddUse_s()), - monitor_("NrSocketIpc") { +// NrUdpSocketIpc Implementation +NrUdpSocketIpc::NrUdpSocketIpc() + : NrSocketIpc(GetIOThreadAndAddUse_s()), + monitor_("NrUdpSocketIpc"), + err_(false), + state_(NR_INIT) { } -NrSocketIpc::~NrSocketIpc() +NrUdpSocketIpc::~NrUdpSocketIpc() { // also guarantees socket_child_ is released from the io_thread, and // tells the SingletonThreadHolder we're done with it @@ -973,40 +1061,18 @@ NrSocketIpc::~NrSocketIpc() #if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) // close(), but transfer the socket_child_ reference to die as well RUN_ON_THREAD(io_thread_, - mozilla::WrapRunnableNM(&NrSocketIpc::release_child_i, + mozilla::WrapRunnableNM(&NrUdpSocketIpc::release_child_i, socket_child_.forget().take(), sts_thread_), NS_DISPATCH_NORMAL); #endif } -/* static */ -nsIThread* NrSocketIpc::GetIOThreadAndAddUse_s() -{ - // Always runs on STS thread! -#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) - // We need to safely release this on shutdown to avoid leaks - if (!sThread) { - sThread = new SingletonThreadHolder(NS_LITERAL_CSTRING("mtransport")); - NS_DispatchToMainThread(mozilla::WrapRunnableNM(&ClearSingletonOnShutdown)); - } - // Mark that we're using the shared thread and need it to stick around - sThread->AddUse(); - return sThread->GetThread(); -#else - static nsCOMPtr sThread; - if (!sThread) { - (void) NS_NewNamedThread("mtransport", getter_AddRefs(sThread)); - } - return sThread; -#endif -} - // IUDPSocketInternal interfaces // callback while error happened in UDP socket operation -NS_IMETHODIMP NrSocketIpc::CallListenerError(const nsACString &message, - const nsACString &filename, - uint32_t line_number) { +NS_IMETHODIMP NrUdpSocketIpc::CallListenerError(const nsACString &message, + const nsACString &filename, + uint32_t line_number) { ASSERT_ON_THREAD(io_thread_); r_log(LOG_GENERIC, LOG_ERR, "UDP socket error:%s at %s:%d", @@ -1020,10 +1086,10 @@ NS_IMETHODIMP NrSocketIpc::CallListenerError(const nsACString &message, } // callback while receiving UDP packet -NS_IMETHODIMP NrSocketIpc::CallListenerReceivedData(const nsACString &host, - uint16_t port, - const uint8_t *data, - uint32_t data_length) { +NS_IMETHODIMP NrUdpSocketIpc::CallListenerReceivedData(const nsACString &host, + uint16_t port, + const uint8_t *data, + uint32_t data_length) { ASSERT_ON_THREAD(io_thread_); PRNetAddr addr; @@ -1050,15 +1116,15 @@ NS_IMETHODIMP NrSocketIpc::CallListenerReceivedData(const nsACString &host, RefPtr msg(new nr_udp_message(addr, buf)); RUN_ON_THREAD(sts_thread_, - mozilla::WrapRunnable(RefPtr(this), - &NrSocketIpc::recv_callback_s, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::recv_callback_s, msg), NS_DISPATCH_NORMAL); return NS_OK; } // callback while UDP socket is opened -NS_IMETHODIMP NrSocketIpc::CallListenerOpened() { +NS_IMETHODIMP NrUdpSocketIpc::CallListenerOpened() { ASSERT_ON_THREAD(io_thread_); ReentrantMonitorAutoEnter mon(monitor_); @@ -1112,7 +1178,7 @@ NS_IMETHODIMP NrSocketIpc::CallListenerOpened() { } // callback while UDP socket is closed -NS_IMETHODIMP NrSocketIpc::CallListenerClosed() { +NS_IMETHODIMP NrUdpSocketIpc::CallListenerClosed() { ASSERT_ON_THREAD(io_thread_); ReentrantMonitorAutoEnter mon(monitor_); @@ -1123,8 +1189,10 @@ NS_IMETHODIMP NrSocketIpc::CallListenerClosed() { return NS_OK; } -// nr_socket public APIs -int NrSocketIpc::create(nr_transport_addr *addr) { +// +// NrSocketBase methods. +// +int NrUdpSocketIpc::create(nr_transport_addr *addr) { ASSERT_ON_THREAD(sts_thread_); int r, _status; @@ -1138,12 +1206,6 @@ int NrSocketIpc::create(nr_transport_addr *addr) { ABORT(R_INTERNAL); } - // Bug 950660: Remote TCP socket is not supported yet. - if (NS_WARN_IF(addr->protocol != IPPROTO_UDP)) { - MOZ_ASSERT(false, "NrSocket over TCP is not e10s ready, see Bug 950660"); - ABORT(R_INTERNAL); - } - sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) { MOZ_ASSERT(false, "Failed to get STS thread"); @@ -1154,7 +1216,7 @@ int NrSocketIpc::create(nr_transport_addr *addr) { ABORT(r); } - // wildcard address will be resolved at NrSocketIpc::CallListenerVoid + // wildcard address will be resolved at NrUdpSocketIpc::CallListenerVoid if ((r=nr_transport_addr_copy(&my_addr_, addr))) { ABORT(r); } @@ -1162,8 +1224,8 @@ int NrSocketIpc::create(nr_transport_addr *addr) { state_ = NR_CONNECTING; RUN_ON_THREAD(io_thread_, - mozilla::WrapRunnable(RefPtr(this), - &NrSocketIpc::create_i, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::create_i, host, static_cast(port)), NS_DISPATCH_NORMAL); @@ -1181,7 +1243,7 @@ abort: return(_status); } -int NrSocketIpc::sendto(const void *msg, size_t len, int flags, +int NrUdpSocketIpc::sendto(const void *msg, size_t len, int flags, nr_transport_addr *to) { ASSERT_ON_THREAD(sts_thread_); @@ -1205,22 +1267,22 @@ int NrSocketIpc::sendto(const void *msg, size_t len, int flags, nsAutoPtr buf(new DataBuffer(static_cast(msg), len)); RUN_ON_THREAD(io_thread_, - mozilla::WrapRunnable(RefPtr(this), - &NrSocketIpc::sendto_i, + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::sendto_i, addr, buf), NS_DISPATCH_NORMAL); return 0; } -void NrSocketIpc::close() { +void NrUdpSocketIpc::close() { ASSERT_ON_THREAD(sts_thread_); ReentrantMonitorAutoEnter mon(monitor_); state_ = NR_CLOSING; RUN_ON_THREAD(io_thread_, - mozilla::WrapRunnable(RefPtr(this), - &NrSocketIpc::close_i), + mozilla::WrapRunnable(RefPtr(this), + &NrUdpSocketIpc::close_i), NS_DISPATCH_NORMAL); //remove all enqueued messages @@ -1228,7 +1290,7 @@ void NrSocketIpc::close() { std::swap(received_msgs_, empty); } -int NrSocketIpc::recvfrom(void *buf, size_t maxlen, size_t *len, int flags, +int NrUdpSocketIpc::recvfrom(void *buf, size_t maxlen, size_t *len, int flags, nr_transport_addr *from) { ASSERT_ON_THREAD(sts_thread_); @@ -1272,7 +1334,7 @@ abort: return(_status); } -int NrSocketIpc::getaddr(nr_transport_addr *addrp) { +int NrUdpSocketIpc::getaddr(nr_transport_addr *addrp) { ASSERT_ON_THREAD(sts_thread_); ReentrantMonitorAutoEnter mon(monitor_); @@ -1284,38 +1346,39 @@ int NrSocketIpc::getaddr(nr_transport_addr *addrp) { return nr_transport_addr_copy(addrp, &my_addr_); } -int NrSocketIpc::connect(nr_transport_addr *addr) { +int NrUdpSocketIpc::connect(nr_transport_addr *addr) { MOZ_ASSERT(false); return R_INTERNAL; } -int NrSocketIpc::write(const void *msg, size_t len, size_t *written) { +int NrUdpSocketIpc::write(const void *msg, size_t len, size_t *written) { MOZ_ASSERT(false); return R_INTERNAL; } -int NrSocketIpc::read(void* buf, size_t maxlen, size_t *len) { +int NrUdpSocketIpc::read(void* buf, size_t maxlen, size_t *len) { MOZ_ASSERT(false); return R_INTERNAL; } -int NrSocketIpc::listen(int backlog) { +int NrUdpSocketIpc::listen(int backlog) { MOZ_ASSERT(false); return R_INTERNAL; } -int NrSocketIpc::accept(nr_transport_addr *addrp, nr_socket **sockp) { +int NrUdpSocketIpc::accept(nr_transport_addr *addrp, nr_socket **sockp) { MOZ_ASSERT(false); return R_INTERNAL; } // IO thread executors -void NrSocketIpc::create_i(const nsACString &host, const uint16_t port) { +void NrUdpSocketIpc::create_i(const nsACString &host, const uint16_t port) { ASSERT_ON_THREAD(io_thread_); nsresult rv; nsCOMPtr socketChild = do_CreateInstance("@mozilla.org/udp-socket-child;1", &rv); if (NS_FAILED(rv)) { + ReentrantMonitorAutoEnter mon(monitor_); err_ = true; MOZ_ASSERT(false, "Failed to create UDPSocketChild"); return; @@ -1332,7 +1395,7 @@ void NrSocketIpc::create_i(const nsACString &host, const uint16_t port) { socketChild = nullptr; } - RefPtr proxy(new NrSocketIpcProxy); + RefPtr proxy(new NrUdpSocketIpcProxy); rv = proxy->Init(this); if (NS_FAILED(rv)) { err_ = true; @@ -1351,17 +1414,16 @@ void NrSocketIpc::create_i(const nsACString &host, const uint16_t port) { } } -void NrSocketIpc::sendto_i(const net::NetAddr &addr, nsAutoPtr buf) { +void NrUdpSocketIpc::sendto_i(const net::NetAddr &addr, nsAutoPtr buf) { ASSERT_ON_THREAD(io_thread_); + ReentrantMonitorAutoEnter mon(monitor_); + if (!socket_child_) { MOZ_ASSERT(false); err_ = true; return; } - - ReentrantMonitorAutoEnter mon(monitor_); - if (NS_FAILED(socket_child_->SendWithAddress(&addr, buf->data(), buf->len()))) { @@ -1369,7 +1431,7 @@ void NrSocketIpc::sendto_i(const net::NetAddr &addr, nsAutoPtr buf) } } -void NrSocketIpc::close_i() { +void NrUdpSocketIpc::close_i() { ASSERT_ON_THREAD(io_thread_); if (socket_child_) { @@ -1381,8 +1443,8 @@ void NrSocketIpc::close_i() { #if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) // close(), but transfer the socket_child_ reference to die as well // static -void NrSocketIpc::release_child_i(nsIUDPSocketChild* aChild, - nsCOMPtr sts_thread) { +void NrUdpSocketIpc::release_child_i(nsIUDPSocketChild* aChild, + nsCOMPtr sts_thread) { RefPtr socket_child_ref = already_AddRefed(aChild); if (socket_child_ref) { @@ -1390,16 +1452,16 @@ void NrSocketIpc::release_child_i(nsIUDPSocketChild* aChild, } // Tell SingletonThreadHolder we're done with it RUN_ON_THREAD(sts_thread, - mozilla::WrapRunnableNM(&NrSocketIpc::release_use_s), + mozilla::WrapRunnableNM(&NrUdpSocketIpc::release_use_s), NS_DISPATCH_NORMAL); } -void NrSocketIpc::release_use_s() { +void NrUdpSocketIpc::release_use_s() { sThread->ReleaseUse(); } #endif -void NrSocketIpc::recv_callback_s(RefPtr msg) { +void NrUdpSocketIpc::recv_callback_s(RefPtr msg) { ASSERT_ON_THREAD(sts_thread_); { @@ -1417,6 +1479,448 @@ void NrSocketIpc::recv_callback_s(RefPtr msg) { } } +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) +// TCPSocket. +class NrTcpSocketIpc::TcpSocketReadyRunner: public nsRunnable +{ +public: + explicit TcpSocketReadyRunner(NrTcpSocketIpc *sck) + : socket_(sck) {} + + NS_IMETHODIMP Run() { + socket_->maybe_post_socket_ready(); + return NS_OK; + } + +private: + RefPtr socket_; +}; + + +NS_IMPL_ISUPPORTS(NrTcpSocketIpc, + nsITCPSocketCallback) + +NrTcpSocketIpc::NrTcpSocketIpc(nsIThread* aThread) + : NrSocketIpc(static_cast(aThread)), + mirror_state_(NR_INIT), + state_(NR_INIT), + buffered_bytes_(0), + tracking_number_(0) { +} + +NrTcpSocketIpc::~NrTcpSocketIpc() +{ + // also guarantees socket_child_ is released from the io_thread + + // close(), but transfer the socket_child_ reference to die as well + RUN_ON_THREAD(io_thread_, + mozilla::WrapRunnableNM(&NrTcpSocketIpc::release_child_i, + socket_child_.forget().take(), + sts_thread_), + NS_DISPATCH_NORMAL); +} + +// +// nsITCPSocketCallback methods +// +NS_IMETHODIMP NrTcpSocketIpc::UpdateReadyState(uint32_t aReadyState) { + NrSocketIpcState temp = NR_INIT; + switch (static_cast(aReadyState)) { + case dom::TCPReadyState::Connecting: + temp = NR_CONNECTING; + break; + case dom::TCPReadyState::Open: + temp = NR_CONNECTED; + break; + case dom::TCPReadyState::Closing: + temp = NR_CLOSING; + break; + case dom::TCPReadyState::Closed: + temp = NR_CLOSED; + break; + default: + MOZ_ASSERT(false, "Invalid ReadyState"); + return NS_OK; + } + if (mirror_state_ != temp) { + mirror_state_ = temp; + RUN_ON_THREAD(sts_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrTcpSocketIpc::update_state_s, + temp), + NS_DISPATCH_NORMAL); + } + return NS_OK; +} + +NS_IMETHODIMP NrTcpSocketIpc::UpdateBufferedAmount(uint32_t buffered_amount, + uint32_t tracking_number) { + RUN_ON_THREAD(sts_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrTcpSocketIpc::message_sent_s, + buffered_amount, + tracking_number), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +NS_IMETHODIMP NrTcpSocketIpc::FireDataArrayEvent(const nsAString& aType, + const InfallibleTArray& buffer) { + // Called when we received data. + uint8_t *buf = const_cast(buffer.Elements()); + + nsAutoPtr data_buf(new DataBuffer(buf, buffer.Length())); + RefPtr msg = new nr_tcp_message(data_buf); + + RUN_ON_THREAD(sts_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrTcpSocketIpc::recv_message_s, + msg), + NS_DISPATCH_NORMAL); + return NS_OK; +} + +NS_IMETHODIMP NrTcpSocketIpc::FireErrorEvent(const nsAString &type, + const nsAString &name) { + r_log(LOG_GENERIC, LOG_ERR, + "Error from TCPSocketChild: type: %s, name: %s", + NS_LossyConvertUTF16toASCII(type).get(), NS_LossyConvertUTF16toASCII(name).get()); + socket_child_ = nullptr; + + mirror_state_ = NR_CLOSED; + RUN_ON_THREAD(sts_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrTcpSocketIpc::update_state_s, + NR_CLOSED), + NS_DISPATCH_NORMAL); + + return NS_OK; +} + +// methods of nsITCPSocketCallback that we are not going to implement. + +NS_IMETHODIMP NrTcpSocketIpc::FireDataEvent(JSContext* aCx, + const nsAString &type, + const JS::HandleValue data) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP NrTcpSocketIpc::FireDataStringEvent(const nsAString &type, + const nsACString &data) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP NrTcpSocketIpc::FireEvent(const nsAString &type) { + // XXX support type.mData == 'close' at least + return NS_ERROR_NOT_IMPLEMENTED; +} + +// +// NrSocketBase methods. +// +int NrTcpSocketIpc::create(nr_transport_addr *addr) { + int r, _status; + nsresult rv; + int32_t port; + nsCString host; + + if (state_ != NR_INIT) { + ABORT(R_INTERNAL); + } + + sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to get STS thread"); + ABORT(R_INTERNAL); + } + + // Sanity check + if ((r=nr_transport_addr_get_addrstring_and_port(addr, &host, &port))) { + ABORT(r); + } + + if ((r=nr_transport_addr_copy(&my_addr_, addr))) { + ABORT(r); + } + + _status = 0; +abort: + return(_status); +} + +int NrTcpSocketIpc::sendto(const void *msg, size_t len, + int flags, nr_transport_addr *to) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +int NrTcpSocketIpc::recvfrom(void * buf, size_t maxlen, + size_t *len, int flags, + nr_transport_addr *from) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +int NrTcpSocketIpc::getaddr(nr_transport_addr *addrp) { + ASSERT_ON_THREAD(sts_thread_); + return nr_transport_addr_copy(addrp, &my_addr_); +} + +void NrTcpSocketIpc::close() { + ASSERT_ON_THREAD(sts_thread_); + + if (state_ == NR_CLOSED || state_ == NR_CLOSING) { + return; + } + + state_ = NR_CLOSING; + + RUN_ON_THREAD(io_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrTcpSocketIpc::close_i), + NS_DISPATCH_NORMAL); + + //remove all enqueued messages + std::queue> empty; + std::swap(msg_queue_, empty); +} + +int NrTcpSocketIpc::connect(nr_transport_addr *addr) { + nsCString remote_addr, local_addr; + int32_t remote_port, local_port; + int r, _status; + if ((r=nr_transport_addr_get_addrstring_and_port(addr, + &remote_addr, + &remote_port))) { + ABORT(r); + } + + if ((r=nr_transport_addr_get_addrstring_and_port(&my_addr_, + &local_addr, + &local_port))) { + MOZ_ASSERT(false); // shouldn't fail as it was sanity-checked in ::create() + ABORT(r); + } + + state_ = mirror_state_ = NR_CONNECTING; + RUN_ON_THREAD(io_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrTcpSocketIpc::connect_i, + remote_addr, + static_cast(remote_port), + local_addr, + static_cast(local_port)), + NS_DISPATCH_NORMAL); + + // Make caller wait for ready to write. + _status = R_WOULDBLOCK; + abort: + return _status; +} + +int NrTcpSocketIpc::write(const void *msg, size_t len, size_t *written) { + ASSERT_ON_THREAD(sts_thread_); + int _status = 0; + if (state_ != NR_CONNECTED) { + ABORT(R_FAILED); + } + + if (buffered_bytes_ + len >= nsITCPSocketCallback::BUFFER_SIZE) { + ABORT(R_WOULDBLOCK); + } + + buffered_bytes_ += len; + { + InfallibleTArray* arr = new InfallibleTArray(); + arr->AppendElements(static_cast(msg), len); + // keep track of un-acknowleged writes by tracking number. + writes_in_flight_.push_back(len); + RUN_ON_THREAD(io_thread_, + mozilla::WrapRunnable(RefPtr(this), + &NrTcpSocketIpc::write_i, + nsAutoPtr>(arr), + ++tracking_number_), + NS_DISPATCH_NORMAL); + } + *written = len; + abort: + return _status; +} + +int NrTcpSocketIpc::read(void* buf, size_t maxlen, size_t *len) { + int _status = 0; + if (state_ != NR_CONNECTED) { + ABORT(R_FAILED); + } + + if (msg_queue_.size() == 0) { + ABORT(R_WOULDBLOCK); + } + + { + RefPtr msg(msg_queue_.front()); + size_t consumed_len = std::min(maxlen, msg->unread_bytes()); + memcpy(buf, msg->reading_pointer(), consumed_len); + if (consumed_len < msg->unread_bytes()) { + // There is still something left in buffer. + msg->read_bytes += consumed_len; + } else { + msg_queue_.pop(); + } + *len = consumed_len; + } + + abort: + return _status; +} + +int NrTcpSocketIpc::listen(int backlog) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +int NrTcpSocketIpc::accept(nr_transport_addr *addrp, nr_socket **sockp) { + MOZ_ASSERT(false); + return R_INTERNAL; +} + +void NrTcpSocketIpc::connect_i(const nsACString &remote_addr, + uint16_t remote_port, + const nsACString &local_addr, + uint16_t local_port) { + ASSERT_ON_THREAD(io_thread_); + mirror_state_ = NR_CONNECTING; + + dom::TCPSocketChild* child = new dom::TCPSocketChild(NS_ConvertUTF8toUTF16(remote_addr), remote_port); + socket_child_ = child; + + // XXX remove remote! + socket_child_->SendWindowlessOpenBind(this, + remote_addr, remote_port, + local_addr, local_port, + /* use ssl */ false); +} + +void NrTcpSocketIpc::write_i(nsAutoPtr> arr, + uint32_t tracking_number) { + ASSERT_ON_THREAD(io_thread_); + if (!socket_child_) { + return; + } + socket_child_->SendSendArray(*arr, tracking_number); +} + +void NrTcpSocketIpc::close_i() { + ASSERT_ON_THREAD(io_thread_); + mirror_state_ = NR_CLOSING; + if (!socket_child_) { + return; + } + socket_child_->SendClose(); +} + +// close(), but transfer the socket_child_ reference to die as well +// static +void NrTcpSocketIpc::release_child_i(dom::TCPSocketChild* aChild, + nsCOMPtr sts_thread) { + RefPtr socket_child_ref = + already_AddRefed(aChild); + if (socket_child_ref) { + socket_child_ref->SendClose(); + } + // io_thread_ is MainThread, so no use to release +} + +void NrTcpSocketIpc::message_sent_s(uint32_t buffered_amount, + uint32_t tracking_number) { + ASSERT_ON_THREAD(sts_thread_); + + size_t num_unacked_writes = tracking_number_ - tracking_number; + while (writes_in_flight_.size() > num_unacked_writes) { + writes_in_flight_.pop_front(); + } + + for (size_t unacked_write_len : writes_in_flight_) { + buffered_amount += unacked_write_len; + } + + r_log(LOG_GENERIC, LOG_ERR, + "UpdateBufferedAmount: (tracking %u): %u, waiting: %s", + tracking_number, buffered_amount, + (poll_flags() & PR_POLL_WRITE) ? "yes" : "no"); + + buffered_bytes_ = buffered_amount; + maybe_post_socket_ready(); +} + +void NrTcpSocketIpc::recv_message_s(nr_tcp_message *msg) { + ASSERT_ON_THREAD(sts_thread_); + msg_queue_.push(msg); + maybe_post_socket_ready(); +} + +void NrTcpSocketIpc::update_state_s(NrSocketIpcState next_state) { + ASSERT_ON_THREAD(sts_thread_); + // only allow valid transitions + switch (state_) { + case NR_CONNECTING: + if (next_state == NR_CONNECTED) { + state_ = NR_CONNECTED; + maybe_post_socket_ready(); + } else { + state_ = next_state; // all states are valid from CONNECTING + } + break; + case NR_CONNECTED: + if (next_state != NR_CONNECTING) { + state_ = next_state; + } + break; + case NR_CLOSING: + if (next_state == NR_CLOSED) { + state_ = next_state; + } + break; + case NR_CLOSED: + break; + default: + MOZ_CRASH("update_state_s while in illegal state"); + } +} + +void NrTcpSocketIpc::maybe_post_socket_ready() { + bool has_event = false; + if (state_ == NR_CONNECTED) { + if (poll_flags() & PR_POLL_WRITE) { + // This effectively polls via the event loop until the + // NR_ASYNC_WAIT_WRITE is no longer armed. + if (buffered_bytes_ < nsITCPSocketCallback::BUFFER_SIZE) { + r_log(LOG_GENERIC, LOG_INFO, "Firing write callback (%u)", + (uint32_t)buffered_bytes_); + fire_callback(NR_ASYNC_WAIT_WRITE); + has_event = true; + } + } + if (poll_flags() & PR_POLL_READ) { + if (msg_queue_.size()) { + r_log(LOG_GENERIC, LOG_INFO, "Firing read callback (%u)", + (uint32_t)msg_queue_.size()); + fire_callback(NR_ASYNC_WAIT_READ); + has_event = true; + } + } + } + + // If any event has been posted, we post a runnable to see + // if the events have to be posted again. + if (has_event) { + RefPtr runnable = new TcpSocketReadyRunner(this); + NS_DispatchToCurrentThread(runnable); + } +} +#endif + } // close namespace @@ -1457,16 +1961,30 @@ static nr_socket_vtbl nr_socket_local_vtbl={ int nr_socket_local_create(void *obj, nr_transport_addr *addr, nr_socket **sockp) { RefPtr sock; + int r, _status; // create IPC bridge for content process if (XRE_IsParentProcess()) { sock = new NrSocket(); } else { - sock = new NrSocketIpc(); + switch (addr->protocol) { + case IPPROTO_UDP: + sock = new NrUdpSocketIpc(); + break; + case IPPROTO_TCP: +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) + { + nsCOMPtr main_thread; + NS_GetMainThread(getter_AddRefs(main_thread)); + sock = new NrTcpSocketIpc(main_thread.get()); + } +#else + ABORT(R_REJECTED); +#endif + break; + } } - int r, _status; - r = sock->create(addr); if (r) ABORT(r); diff --git a/media/mtransport/nr_socket_prsock.h b/media/mtransport/nr_socket_prsock.h index 4e8b041c1e..4096c29e03 100644 --- a/media/mtransport/nr_socket_prsock.h +++ b/media/mtransport/nr_socket_prsock.h @@ -61,6 +61,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "nsProxyRelease.h" #include "nsThreadUtils.h" +#include "nsITCPSocketCallback.h" #include "databuffer.h" #include "m_cpp_utils.h" #include "mozilla/ReentrantMonitor.h" @@ -72,6 +73,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. typedef struct nr_socket_vtbl_ nr_socket_vtbl; typedef struct nr_socket_ nr_socket; +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) +namespace mozilla { +namespace dom { +class TCPSocketChild; +} +} +#endif + namespace mozilla { namespace net { @@ -209,7 +218,22 @@ public: NR_CLOSED, }; - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrSocketIpc) + NrSocketIpc(nsIEventTarget* aThread); + +protected: + nsCOMPtr sts_thread_; + // Note: for UDP PBackground, this is a thread held by SingletonThreadHolder. + // For TCP PNecko, this is MainThread (and TCPSocket requires MainThread currently) + const nsCOMPtr io_thread_; + virtual ~NrSocketIpc() {}; + +private: + DISALLOW_COPY_ASSIGN(NrSocketIpc); +}; + +class NrUdpSocketIpc : public NrSocketIpc { +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrUdpSocketIpc, override) NS_IMETHODIMP CallListenerError(const nsACString &message, const nsACString &filename, @@ -221,7 +245,7 @@ public: NS_IMETHODIMP CallListenerOpened(); NS_IMETHODIMP CallListenerClosed(); - NrSocketIpc(); + NrUdpSocketIpc(); // Implementations of the NrSocketBase APIs virtual int create(nr_transport_addr *addr) override; @@ -239,11 +263,9 @@ public: virtual int accept(nr_transport_addr *addrp, nr_socket **sockp) override; private: - virtual ~NrSocketIpc(); + virtual ~NrUdpSocketIpc(); - DISALLOW_COPY_ASSIGN(NrSocketIpc); - - static nsIThread* GetIOThreadAndAddUse_s(); + DISALLOW_COPY_ASSIGN(NrUdpSocketIpc); // Main or private thread executors of the NrSocketBase APIs void create_i(const nsACString &host, const uint16_t port); @@ -256,32 +278,118 @@ private: // STS thread executor void recv_callback_s(RefPtr msg); + ReentrantMonitor monitor_; // protects err_and state_ bool err_; NrSocketIpcState state_; - std::queue > received_msgs_; + + std::queue> received_msgs_; RefPtr socket_child_; // only accessed from the io_thread - nsCOMPtr sts_thread_; - const nsCOMPtr io_thread_; - ReentrantMonitor monitor_; }; // The socket child holds onto one of these, which just passes callbacks // through and makes sure the ref to the NrSocketIpc is released on STS. -class NrSocketIpcProxy : public nsIUDPSocketInternal { +class NrUdpSocketIpcProxy : public nsIUDPSocketInternal { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIUDPSOCKETINTERNAL - nsresult Init(const RefPtr& socket); + nsresult Init(const RefPtr& socket); private: - virtual ~NrSocketIpcProxy(); + virtual ~NrUdpSocketIpcProxy(); - RefPtr socket_; + RefPtr socket_; nsCOMPtr sts_thread_; }; +struct nr_tcp_message { + explicit nr_tcp_message(nsAutoPtr &data) + : read_bytes(0) + , data(data) { + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nr_tcp_message); + + const uint8_t *reading_pointer() const { + return data->data() + read_bytes; + } + + size_t unread_bytes() const { + return data->len() - read_bytes; + } + + size_t read_bytes; + +private: + ~nr_tcp_message() {} + DISALLOW_COPY_ASSIGN(nr_tcp_message); + + nsAutoPtr data; +}; + +#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API) +class NrTcpSocketIpc : public NrSocketIpc, + public nsITCPSocketCallback { +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITCPSOCKETCALLBACK + + explicit NrTcpSocketIpc(nsIThread* aThread); + + // Implementations of the NrSocketBase APIs + virtual int create(nr_transport_addr *addr) override; + virtual int sendto(const void *msg, size_t len, + int flags, nr_transport_addr *to) override; + virtual int recvfrom(void * buf, size_t maxlen, + size_t *len, int flags, + nr_transport_addr *from) override; + virtual int getaddr(nr_transport_addr *addrp) override; + virtual void close() override; + virtual int connect(nr_transport_addr *addr) override; + virtual int write(const void *msg, size_t len, size_t *written) override; + virtual int read(void* buf, size_t maxlen, size_t *len) override; + virtual int listen(int backlog) override; + virtual int accept(nr_transport_addr *addrp, nr_socket **sockp) override; + +private: + class TcpSocketReadyRunner; + DISALLOW_COPY_ASSIGN(NrTcpSocketIpc); + virtual ~NrTcpSocketIpc(); + + // Main thread executors of the NrSocketBase APIs + void connect_i(const nsACString &remote_addr, + uint16_t remote_port, + const nsACString &local_addr, + uint16_t local_port); + void write_i(nsAutoPtr> buf, + uint32_t tracking_number); + void close_i(); + + static void release_child_i(dom::TCPSocketChild* aChild, nsCOMPtr ststhread); + + // STS thread executor + void message_sent_s(uint32_t bufferedAmount, uint32_t tracking_number); + void recv_message_s(nr_tcp_message *msg); + void update_state_s(NrSocketIpcState next_state); + void maybe_post_socket_ready(); + + // Accessed from UpdateReadyState (not sts_thread) to avoid sending + // runnables when not needed + NrSocketIpcState mirror_state_; + + // variables that can only be accessed on STS. + NrSocketIpcState state_; + std::queue> msg_queue_; + uint32_t buffered_bytes_; + uint32_t tracking_number_; + std::deque writes_in_flight_; + + // main thread. + RefPtr socket_child_; +}; +#endif + int nr_netaddr_to_transport_addr(const net::NetAddr *netaddr, nr_transport_addr *addr, int protocol); diff --git a/media/mtransport/nricectx.cpp b/media/mtransport/nricectx.cpp index 1fc86e4139..f6f4cc94da 100644 --- a/media/mtransport/nricectx.cpp +++ b/media/mtransport/nricectx.cpp @@ -380,13 +380,12 @@ void NrIceCtx::trickle_cb(void *arg, nr_ice_ctx *ice_ctx, RefPtr NrIceCtx::Create(const std::string& name, bool offerer, - bool set_interface_priorities, bool allow_loopback, bool tcp_enabled, bool allow_link_local, + bool hide_non_default, Policy policy) { - - RefPtr ctx = new NrIceCtx(name, offerer, policy); + RefPtr ctx = new NrIceCtx(name, offerer, policy); // Initialize the crypto callbacks and logging stuff if (!initialized) { @@ -406,37 +405,11 @@ RefPtr NrIceCtx::Create(const std::string& name, NR_reg_set_uchar((char *)NR_ICE_REG_PREF_TYPE_HOST_TCP, 125); NR_reg_set_uchar((char *)NR_ICE_REG_PREF_TYPE_RELAYED_TCP, 0); - if (set_interface_priorities) { - NR_reg_set_uchar((char *)"ice.pref.interface.rl0", 255); - NR_reg_set_uchar((char *)"ice.pref.interface.wi0", 254); - NR_reg_set_uchar((char *)"ice.pref.interface.lo0", 253); - NR_reg_set_uchar((char *)"ice.pref.interface.en1", 252); - NR_reg_set_uchar((char *)"ice.pref.interface.en0", 251); - NR_reg_set_uchar((char *)"ice.pref.interface.eth0", 252); - NR_reg_set_uchar((char *)"ice.pref.interface.eth1", 251); - NR_reg_set_uchar((char *)"ice.pref.interface.eth2", 249); - NR_reg_set_uchar((char *)"ice.pref.interface.ppp", 250); - NR_reg_set_uchar((char *)"ice.pref.interface.ppp0", 249); - NR_reg_set_uchar((char *)"ice.pref.interface.en2", 248); - NR_reg_set_uchar((char *)"ice.pref.interface.en3", 247); - NR_reg_set_uchar((char *)"ice.pref.interface.em0", 251); - NR_reg_set_uchar((char *)"ice.pref.interface.em1", 252); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet0", 240); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet1", 241); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet3", 239); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet4", 238); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet5", 237); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet6", 236); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet7", 235); - NR_reg_set_uchar((char *)"ice.pref.interface.vmnet8", 234); - NR_reg_set_uchar((char *)"ice.pref.interface.virbr0", 233); - NR_reg_set_uchar((char *)"ice.pref.interface.wlan0", 232); - } - int32_t stun_client_maximum_transmits = 7; int32_t ice_trickle_grace_period = 5000; int32_t ice_tcp_so_sock_count = 3; int32_t ice_tcp_listen_backlog = 10; + nsAutoCString force_net_interface; #ifndef MOZILLA_XPCOMRT_API nsresult res; nsCOMPtr prefs = @@ -457,6 +430,9 @@ RefPtr NrIceCtx::Create(const std::string& name, branch->GetIntPref( "media.peerconnection.ice.tcp_listen_backlog", &ice_tcp_listen_backlog); + branch->GetCharPref( + "media.peerconnection.ice.force_interface", + getter_Copies(force_net_interface)); } } #endif @@ -466,8 +442,6 @@ RefPtr NrIceCtx::Create(const std::string& name, ice_trickle_grace_period); NR_reg_set_int4((char *)NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT, ice_tcp_so_sock_count); - NR_reg_set_int4((char *)NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT, - ice_tcp_so_sock_count); NR_reg_set_int4((char *)NR_ICE_REG_ICE_TCP_LISTEN_BACKLOG, ice_tcp_listen_backlog); @@ -480,6 +454,11 @@ RefPtr NrIceCtx::Create(const std::string& name, if (allow_link_local) { NR_reg_set_char((char *)NR_STUN_REG_PREF_ALLOW_LINK_LOCAL_ADDRS, 1); } + if (force_net_interface.Length() > 0) { + // Stupid cast.... but needed + const nsCString& flat = PromiseFlatCString(static_cast(force_net_interface)); + NR_reg_set_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, const_cast(flat.get())); + } } // Create the ICE context @@ -488,6 +467,12 @@ RefPtr NrIceCtx::Create(const std::string& name, UINT4 flags = offerer ? NR_ICE_CTX_FLAGS_OFFERER: NR_ICE_CTX_FLAGS_ANSWERER; flags |= NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION; + if (policy == ICE_POLICY_RELAY) { + flags |= NR_ICE_CTX_FLAGS_RELAY_ONLY; + } + + if (hide_non_default) + flags |= NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS; r = nr_ice_ctx_create(const_cast(name.c_str()), flags, &ctx->ctx_); @@ -496,7 +481,6 @@ RefPtr NrIceCtx::Create(const std::string& name, return nullptr; } -#ifdef USE_INTERFACE_PRIORITIZER nr_interface_prioritizer *prioritizer = CreateInterfacePrioritizer(); if (!prioritizer) { MOZ_MTLOG(LogLevel::Error, "Couldn't create interface prioritizer."); @@ -508,7 +492,6 @@ RefPtr NrIceCtx::Create(const std::string& name, MOZ_MTLOG(LogLevel::Error, "Couldn't set interface prioritizer."); return nullptr; } -#endif // USE_INTERFACE_PRIORITIZER if (ctx->generating_trickle()) { r = nr_ice_ctx_set_trickle_cb(ctx->ctx_, &NrIceCtx::trickle_cb, ctx); @@ -727,6 +710,9 @@ abort: nsresult NrIceCtx::StartGathering() { ASSERT_ON_THREAD(sts_target_); + if (policy_ == ICE_POLICY_NONE) { + return NS_OK; + } SetGatheringState(ICE_CTX_GATHER_STARTED); // This might start gathering for the first time, or again after // renegotiation, or might do nothing at all if gathering has already @@ -801,6 +787,11 @@ nsresult NrIceCtx::ParseGlobalAttributes(std::vector attrs) { nsresult NrIceCtx::StartChecks() { int r; + if (policy_ == ICE_POLICY_NONE) { + MOZ_MTLOG(ML_ERROR, "Couldn't start peer checks because policy == none"); + SetConnectionState(ICE_CTX_FAILED); + return NS_ERROR_FAILURE; + } r=nr_ice_peer_ctx_pair_candidates(peer_); if (r) { MOZ_MTLOG(ML_ERROR, "Couldn't pair candidates on " diff --git a/media/mtransport/nricectx.h b/media/mtransport/nricectx.h index 89b129a445..97fc6ab02c 100644 --- a/media/mtransport/nricectx.h +++ b/media/mtransport/nricectx.h @@ -213,12 +213,13 @@ class NrIceCtx { ICE_POLICY_ALL }; + // TODO(ekr@rtfm.com): Too many bools here. Bug 1193437. static RefPtr Create(const std::string& name, bool offerer, - bool set_interface_priorities = true, bool allow_loopback = false, bool tcp_enabled = true, bool allow_link_local = false, + bool hide_non_default = false, Policy policy = ICE_POLICY_ALL); // Deinitialize all ICE global state. Used only for testing. diff --git a/media/mtransport/nricemediastream.cpp b/media/mtransport/nricemediastream.cpp index f6b6105881..36a677b7ee 100644 --- a/media/mtransport/nricemediastream.cpp +++ b/media/mtransport/nricemediastream.cpp @@ -307,15 +307,44 @@ nsresult NrIceMediaStream::GetCandidatePairs(std::vector* return NS_ERROR_FAILURE; } - nr_ice_cand_pair *p1; + nr_ice_cand_pair *p1, *p2; out_pairs->clear(); - TAILQ_FOREACH(p1, &peer_stream->check_list, entry) { + TAILQ_FOREACH(p1, &peer_stream->check_list, check_queue_entry) { MOZ_ASSERT(p1); MOZ_ASSERT(p1->local); MOZ_ASSERT(p1->remote); NrIceCandidatePair pair; + p2 = TAILQ_FIRST(&peer_stream->check_list); + while (p2) { + if (p1 == p2) { + /* Don't compare with our self. */ + p2=TAILQ_NEXT(p2, check_queue_entry); + continue; + } + if (strncmp(p1->codeword,p2->codeword,sizeof(p1->codeword))==0) { + /* In case of duplicate pairs we only report the one winning pair */ + if ( + ((p2->remote->component && (p2->remote->component->active == p2)) && + !(p1->remote->component && (p1->remote->component->active == p1))) || + ((p2->peer_nominated || p2->nominated) && + !(p1->peer_nominated || p1->nominated)) || + (p2->priority > p1->priority) || + ((p2->state == NR_ICE_PAIR_STATE_SUCCEEDED) && + (p1->state != NR_ICE_PAIR_STATE_SUCCEEDED)) + ) { + /* p2 is a better pair. */ + break; + } + } + p2=TAILQ_NEXT(p2, check_queue_entry); + } + if (p2) { + /* p2 points to a duplicate but better pair so skip this one */ + continue; + } + switch (p1->state) { case NR_ICE_PAIR_STATE_FROZEN: pair.state = NrIceCandidatePair::State::STATE_FROZEN; diff --git a/media/mtransport/nriceresolver.cpp b/media/mtransport/nriceresolver.cpp index 0bea3b8d06..1ef619112f 100644 --- a/media/mtransport/nriceresolver.cpp +++ b/media/mtransport/nriceresolver.cpp @@ -154,7 +154,7 @@ int NrIceResolver::resolve(nr_resolver_resource *resource, if (resource->transport_protocol != IPPROTO_UDP && resource->transport_protocol != IPPROTO_TCP) { - MOZ_MTLOG(ML_ERROR, "Only UDP and TCP are is supported."); + MOZ_MTLOG(ML_ERROR, "Only UDP and TCP are supported."); ABORT(R_NOT_FOUND); } pr = new PendingResolution(sts_thread_, diff --git a/media/mtransport/nriceresolver.h b/media/mtransport/nriceresolver.h index 91ec277f52..c3a379e716 100644 --- a/media/mtransport/nriceresolver.h +++ b/media/mtransport/nriceresolver.h @@ -71,6 +71,10 @@ class NrIceResolver void DestroyResolver(); NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceResolver) + int resolve(nr_resolver_resource *resource, + int (*cb)(void *cb_arg, nr_transport_addr *addr), + void *cb_arg, void **handle); + private: // Implementations of vtbl functions static int destroy(void **objp); @@ -80,10 +84,6 @@ class NrIceResolver static void resolve_cb(NR_SOCKET s, int how, void *cb_arg); static int cancel(void *obj, void *handle); - int resolve(nr_resolver_resource *resource, - int (*cb)(void *cb_arg, nr_transport_addr *addr), - void *cb_arg, void **handle); - class PendingResolution : public nsIDNSListener { public: diff --git a/media/mtransport/nrinterfaceprioritizer.cpp b/media/mtransport/nrinterfaceprioritizer.cpp index a20cfcb915..90ab1bc60e 100644 --- a/media/mtransport/nrinterfaceprioritizer.cpp +++ b/media/mtransport/nrinterfaceprioritizer.cpp @@ -1,9 +1,11 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include #include #include #include +#include #include "logging.h" #include "nrinterfaceprioritizer.h" #include "nsCOMPtr.h" @@ -15,20 +17,32 @@ namespace { class LocalAddress { public: LocalAddress() - : key_(), + : ifname_(), + addr_(), + key_(), is_vpn_(-1), estimated_speed_(-1), type_preference_(-1), ip_version_(-1) {} bool Init(const nr_local_addr& local_addr) { + ifname_ = local_addr.addr.ifname; + char buf[MAXIFNAME + 41]; int r = nr_transport_addr_fmt_ifname_addr_string(&local_addr.addr, buf, sizeof(buf)); if (r) { - MOZ_MTLOG(ML_ERROR, "Error formatting interface address string."); + MOZ_MTLOG(ML_ERROR, "Error formatting interface key."); return false; } key_ = buf; + + r = nr_transport_addr_get_addrstring(&local_addr.addr, buf, sizeof(buf)); + if (r) { + MOZ_MTLOG(ML_ERROR, "Error formatting address string."); + return false; + } + addr_ = buf; + is_vpn_ = (local_addr.interface.type & NR_INTERFACE_TYPE_VPN) != 0 ? 1 : 0; estimated_speed_ = local_addr.interface.estimated_speed; type_preference_ = GetNetworkTypePreference(local_addr.interface.type); @@ -56,13 +70,28 @@ public: return estimated_speed_ > rhs.estimated_speed_; } + // See if our hard-coded pref list helps us. + auto thisindex = std::find(interface_preference_list().begin(), + interface_preference_list().end(), + ifname_); + auto rhsindex = std::find(interface_preference_list().begin(), + interface_preference_list().end(), + rhs.ifname_); + if (thisindex != rhsindex) { + return thisindex < rhsindex; + } + // Prefer IPV6 over IPV4 if (ip_version_ != rhs.ip_version_) { return ip_version_ > rhs.ip_version_; } - // All things above are the same, we can at least sort with key. - return key_ < rhs.key_; + // Now we start getting into arbitrary stuff + if (ifname_ != rhs.ifname_) { + return ifname_ < rhs.ifname_; + } + + return addr_ < rhs.addr_; } const std::string& GetKey() const { @@ -85,6 +114,48 @@ private: return 4; } + // TODO(bug 895790): Once we can get useful interface properties on Darwin, + // we should remove this stuff. + static const std::vector& interface_preference_list() + { + static std::vector list(build_interface_preference_list()); + return list; + } + + static std::vector build_interface_preference_list() + { + std::vector result; + result.push_back("rl0"); + result.push_back("wi0"); + result.push_back("en0"); + result.push_back("enp2s0"); + result.push_back("enp3s0"); + result.push_back("en1"); + result.push_back("en2"); + result.push_back("en3"); + result.push_back("eth0"); + result.push_back("eth1"); + result.push_back("eth2"); + result.push_back("em1"); + result.push_back("em0"); + result.push_back("ppp"); + result.push_back("ppp0"); + result.push_back("vmnet1"); + result.push_back("vmnet0"); + result.push_back("vmnet3"); + result.push_back("vmnet4"); + result.push_back("vmnet5"); + result.push_back("vmnet6"); + result.push_back("vmnet7"); + result.push_back("vmnet8"); + result.push_back("virbr0"); + result.push_back("wlan0"); + result.push_back("lo0"); + return result; + } + + std::string ifname_; + std::string addr_; std::string key_; int is_vpn_; int estimated_speed_; diff --git a/media/mtransport/standalone/moz.build b/media/mtransport/standalone/moz.build index ec59b576bc..31f400a66b 100644 --- a/media/mtransport/standalone/moz.build +++ b/media/mtransport/standalone/moz.build @@ -7,6 +7,7 @@ Library('mtransport_standalone') include('../common.build') +include("/ipc/chromium/chromium-config.mozbuild") # These files cannot be built in unified mode because of the redefinition of # getLogModule, UNIMPLEMENTED, nr_socket_long_term_violation_time, diff --git a/media/mtransport/test/ice_unittest.cpp b/media/mtransport/test/ice_unittest.cpp index d7ada0b6af..38b77bb430 100644 --- a/media/mtransport/test/ice_unittest.cpp +++ b/media/mtransport/test/ice_unittest.cpp @@ -48,6 +48,11 @@ #include "ice_peer_ctx.h" #include "ice_media_stream.h" +extern "C" { +#include "r_data.h" +#include "util.h" +} + #define GTEST_HAS_RTTI 0 #include "gtest/gtest.h" #include "gtest_utils.h" @@ -61,7 +66,6 @@ static unsigned int kDefaultTimeout = 7000; //TODO(nils@mozilla.com): This should get replaced with some non-external //solution like discussed in bug 860775. -const std::string kDefaultStunServerAddress((char *)"52.11.16.249"); const std::string kDefaultStunServerHostname( (char *)"global.stun.twilio.com"); const std::string kBogusStunServerHostname( @@ -73,7 +77,7 @@ const std::string kBogusIceCandidate( const std::string kUnreachableHostIceCandidate( (char *)"candidate:0 1 UDP 2113601790 192.168.178.20 50769 typ host"); -std::string g_stun_server_address(kDefaultStunServerAddress); +std::string g_stun_server_address; std::string g_stun_server_hostname(kDefaultStunServerHostname); std::string g_turn_server; std::string g_turn_user; @@ -83,8 +87,32 @@ namespace { enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL }; +const unsigned int ICE_TEST_PEER_OFFERER = (1 << 0); +const unsigned int ICE_TEST_PEER_ALLOW_LOOPBACK = (1 << 1); +const unsigned int ICE_TEST_PEER_ENABLED_TCP = (1 << 2); +const unsigned int ICE_TEST_PEER_ALLOW_LINK_LOCAL = (1 << 3); +const unsigned int ICE_TEST_PEER_HIDE_NON_DEFAULT = (1 << 4); + typedef std::string (*CandidateFilter)(const std::string& candidate); +std::vector split(const std::string &s, char delim) { + std::vector elems; + std::stringstream ss(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + return elems; +} + +static std::string IsSrflxCandidate(const std::string& candidate) { + std::vector tokens = split(candidate, ' '); + if ((tokens.at(6) == "typ") && (tokens.at(7) == "srflx")) { + return candidate; + } + return std::string(); +} + static std::string IsRelayCandidate(const std::string& candidate) { if (candidate.find("typ relay") != std::string::npos) { return candidate; @@ -113,6 +141,14 @@ static std::string IsLoopbackCandidate(const std::string& candidate) { return std::string(); } +static std::string IsIpv4Candidate(const std::string& candidate) { + std::vector tokens = split(candidate, ' '); + if (tokens.at(4).find(":") == std::string::npos) { + return candidate; + } + return std::string(); +} + static std::string SabotageHostCandidateAndDropReflexive( const std::string& candidate) { if (candidate.find("typ srflx") != std::string::npos) { @@ -246,12 +282,14 @@ class SchedulableTrickleCandidate { class IceTestPeer : public sigslot::has_slots<> { public: - - IceTestPeer(const std::string& name, bool offerer, bool set_priorities, - bool allow_loopback = false, bool enable_tcp = true) : + // TODO(ekr@rtfm.com): Convert to flags when NrIceCtx::Create() does. + // Bug 1193437. + IceTestPeer(const std::string& name, bool offerer, + bool allow_loopback = false, bool enable_tcp = true, + bool allow_link_local = false, bool hide_non_default = false) : name_(name), - ice_ctx_(NrIceCtx::Create(name, offerer, set_priorities, allow_loopback, - enable_tcp)), + ice_ctx_(NrIceCtx::Create(name, offerer, allow_loopback, + enable_tcp, allow_link_local, hide_non_default)), streams_(), candidates_(), gathering_complete_(false), @@ -436,7 +474,7 @@ class IceTestPeer : public sigslot::has_slots<> { return attrs; } - std::vector GetCandidates(size_t stream) { + std::vector GetCandidates(size_t stream) { std::vector v; RUN_ON_THREAD( @@ -484,11 +522,36 @@ class IceTestPeer : public sigslot::has_slots<> { expected_remote_type_ = remote; } - void SetExpectedCandidateAddr(const std::string& addr) { - expected_local_addr_ = addr; + void SetExpectedRemoteCandidateAddr(const std::string& addr) { expected_remote_addr_ = addr; } + int GetCandidatesPrivateIpv4Range(size_t stream) { + std::vector candidates = GetCandidates(stream); + + int host_net = 0; + for (auto c : candidates) { + if (c.find("typ host") != std::string::npos) { + nr_transport_addr addr; + std::vector tokens = split(c, ' '); + int r = nr_str_port_to_transport_addr(tokens.at(4).c_str(), 0, IPPROTO_UDP, &addr); + MOZ_ASSERT(!r); + if (!r && (addr.ip_version == NR_IPV4)) { + int n = nr_transport_addr_get_private_addr_range(&addr); + if (n) { + if (host_net) { + // TODO: add support for multiple private interfaces + std::cerr << "This test doesn't support multiple private interfaces"; + return -1; + } + host_net = n; + } + } + } + } + return host_net; + } + bool gathering_complete() { return gathering_complete_; } int ready_ct() { return ready_ct_; } bool is_ready_s(size_t stream) { @@ -695,12 +758,25 @@ class IceTestPeer : public sigslot::has_slots<> { } else { ASSERT_TRUE(NS_SUCCEEDED(res)); DumpCandidate("Local ", *local); - ASSERT_EQ(expected_local_type_, local->type); + /* Depending on timing, and the whims of the network + * stack/configuration we're running on top of, prflx is always a + * possibility. */ + if (expected_local_type_ == NrIceCandidate::ICE_HOST) { + ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, local->type); + ASSERT_NE(NrIceCandidate::ICE_RELAYED, local->type); + } else { + ASSERT_EQ(expected_local_type_, local->type); + } ASSERT_EQ(expected_local_transport_, local->local_addr.transport); DumpCandidate("Remote ", *remote); - ASSERT_EQ(expected_remote_type_, remote->type); - if (!expected_local_addr_.empty()) { - ASSERT_EQ(expected_local_addr_, local->cand_addr.host); + /* Depending on timing, and the whims of the network + * stack/configuration we're running on top of, prflx is always a + * possibility. */ + if (expected_remote_type_ == NrIceCandidate::ICE_HOST) { + ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, remote->type); + ASSERT_NE(NrIceCandidate::ICE_RELAYED, remote->type); + } else { + ASSERT_EQ(expected_remote_type_, remote->type); } if (!expected_remote_addr_.empty()) { ASSERT_EQ(expected_remote_addr_, remote->cand_addr.host); @@ -792,7 +868,8 @@ class IceTestPeer : public sigslot::has_slots<> { if (candidate.empty()) { return; } - std::cerr << "Candidate initialized: " << candidate << std::endl; + std::cerr << "Candidate for stream " << stream->name() << " initialized: " + << candidate << std::endl; candidates_[stream->name()].push_back(candidate); // If we are connected, then try to trickle to the @@ -884,10 +961,15 @@ class IceTestPeer : public sigslot::has_slots<> { DumpCandidatePair(pairs[p]); return false; } else if (priority == pairs[p].priority) { - std::cerr << "Duplicate priority in subseqent pairs:" << std::endl; - DumpCandidatePair(pairs[p-1]); - DumpCandidatePair(pairs[p]); - return false; + if (!IceCandidatePairCompare()(pairs[p], pairs[p-1]) && + !IceCandidatePairCompare()(pairs[p-1], pairs[p])) { + std::cerr << "Ignoring identical pair from trigger check" << std::endl; + } else { + std::cerr << "Duplicate priority in subseqent pairs:" << std::endl; + DumpCandidatePair(pairs[p-1]); + DumpCandidatePair(pairs[p]); + return false; + } } priority = pairs[p].priority; } @@ -1061,6 +1143,22 @@ class IceTestPeer : public sigslot::has_slots<> { SetControlling(NrIceCtx::ICE_CONTROLLED); } + nsresult GetDefaultCandidate(unsigned int stream, NrIceCandidate* cand) { + nsresult rv; + + test_utils->sts_target()->Dispatch( + WrapRunnableRet(&rv, this, + &IceTestPeer::GetDefaultCandidate_s, + stream, cand), + NS_DISPATCH_SYNC); + + return rv; + } + + nsresult GetDefaultCandidate_s(unsigned int stream, NrIceCandidate* cand) { + return streams_[stream]->GetDefaultCandidate(1, cand); + } + private: std::string name_; RefPtr ice_ctx_; @@ -1082,7 +1180,6 @@ class IceTestPeer : public sigslot::has_slots<> { NrIceCandidate::Type expected_local_type_; std::string expected_local_transport_; NrIceCandidate::Type expected_remote_type_; - std::string expected_local_addr_; std::string expected_remote_addr_; TrickleMode trickle_mode_; int trickled_; @@ -1123,9 +1220,14 @@ class IceGatherTest : public ::testing::Test { NrIceCtx::internal_DeinitializeGlobal(); } - void EnsurePeer() { + void EnsurePeer(const unsigned int flags = ICE_TEST_PEER_OFFERER) { if (!peer_) { - peer_ = new IceTestPeer("P1", true, false); + peer_ = new IceTestPeer("P1", + flags & ICE_TEST_PEER_OFFERER, + flags & ICE_TEST_PEER_ALLOW_LOOPBACK, + flags & ICE_TEST_PEER_ENABLED_TCP, + flags & ICE_TEST_PEER_ALLOW_LINK_LOCAL, + flags & ICE_TEST_PEER_HIDE_NON_DEFAULT); peer_->AddStream(1); } } @@ -1196,7 +1298,7 @@ class IceGatherTest : public ::testing::Test { const std::string& fake_addr, uint16_t fake_port, const std::string& fqdn = std::string()) { - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); std::vector stun_servers; AddStunServerWithResponse(fake_addr, fake_port, fqdn, "tcp", &stun_servers); peer_->SetStunServers(stun_servers); @@ -1207,7 +1309,7 @@ class IceGatherTest : public ::testing::Test { uint16_t fake_udp_port, const std::string& fake_tcp_addr, uint16_t fake_tcp_port) { - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); std::vector stun_servers; AddStunServerWithResponse(fake_udp_addr, fake_udp_port, @@ -1246,6 +1348,17 @@ class IceGatherTest : public ::testing::Test { return false; } + void DumpCandidates(unsigned int stream) { + std::vector candidates = peer_->GetCandidates(stream); + + std::cerr << "Candidates for stream " << stream << "->" + << candidates.size() << std::endl; + + for (auto c : candidates) { + std::cerr << "Candidate: " << c << std::endl; + } + } + protected: mozilla::ScopedDeletePtr peer_; }; @@ -1279,7 +1392,7 @@ class IceConnectTest : public ::testing::Test { } void AddStream(const std::string& name, int components) { - Init(false, false, false); + Init(false, false); p1_->AddStream(components); p2_->AddStream(components); } @@ -1289,24 +1402,27 @@ class IceConnectTest : public ::testing::Test { p2_->RemoveStream(index); } - void Init(bool set_priorities, bool allow_loopback, bool enable_tcp) { + void Init(bool allow_loopback, bool enable_tcp, bool default_only = false) { if (!initted_) { - p1_ = new IceTestPeer("P1", true, set_priorities, allow_loopback, - enable_tcp); - p2_ = new IceTestPeer("P2", false, set_priorities, allow_loopback, - enable_tcp); + p1_ = new IceTestPeer("P1", true, allow_loopback, + enable_tcp, false, default_only); + p2_ = new IceTestPeer("P2", false, allow_loopback, + enable_tcp, false, default_only); } initted_ = true; } - bool Gather(unsigned int waitTime = kDefaultTimeout) { - Init(false, false, false); + bool Gather(unsigned int waitTime = kDefaultTimeout, + bool setupStunServers = true) { + Init(false, false); if (use_nat_) { // If we enable nat simulation, but still use a real STUN server somewhere // on the internet, we will see failures if there is a real NAT in // addition to our simulated one, particularly if it disallows // hairpinning. - UseTestStunServer(); + if (setupStunServers) { + UseTestStunServer(); + } p1_->UseNat(); p2_->UseNat(); p1_->SetFilteringType(filtering_type_); @@ -1315,13 +1431,13 @@ class IceConnectTest : public ::testing::Test { p2_->SetMappingType(mapping_type_); p1_->SetBlockUdp(block_udp_); p2_->SetBlockUdp(block_udp_); - } else { + } else if (setupStunServers) { std::vector stun_servers; stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_address, - kDefaultStunServerPort, kNrIceTransportUdp)); + kDefaultStunServerPort, kNrIceTransportUdp)); stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_address, - kDefaultStunServerPort, kNrIceTransportTcp)); + kDefaultStunServerPort, kNrIceTransportTcp)); p1_->SetStunServers(stun_servers); p2_->SetStunServers(stun_servers); @@ -1413,9 +1529,9 @@ class IceConnectTest : public ::testing::Test { p2_->SetExpectedTypes(local2, remote2); } - void SetExpectedCandidateAddr(const std::string& addr) { - p1_->SetExpectedCandidateAddr(addr); - p2_->SetExpectedCandidateAddr(addr); + void SetExpectedRemoteCandidateAddr(const std::string& addr) { + p1_->SetExpectedRemoteCandidateAddr(addr); + p2_->SetExpectedRemoteCandidateAddr(addr); } void ConnectP1(TrickleMode mode = TRICKLE_NONE) { @@ -1629,10 +1745,11 @@ TEST_F(IceGatherTest, TestGatherFakeStunServerTcpHostnameNoResolver) { return; } - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort, kNrIceTransportTcp); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); } TEST_F(IceGatherTest, TestGatherFakeStunServerIpAddress) { @@ -1646,6 +1763,19 @@ TEST_F(IceGatherTest, TestGatherFakeStunServerIpAddress) { Gather(); } +TEST_F(IceGatherTest, TestGatherStunServerIpAddressDefaultRouteOnly) { + if (g_stun_server_address.empty()) { + return; + } + + peer_ = new IceTestPeer("P1", true, false, false, false, true); + peer_->AddStream(1); + peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); + peer_->SetFakeResolver(); + Gather(); + ASSERT_FALSE(StreamHasMatchingCandidate(0, " host ")); +} + TEST_F(IceGatherTest, TestGatherFakeStunServerHostname) { if (g_stun_server_hostname.empty()) { return; @@ -1673,7 +1803,8 @@ TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddress) { peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort); peer_->SetDNSResolver(); Gather(); - // TODO(jib@mozilla.com): ensure we get server reflexive candidates Bug 848094 + ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr")); } TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddressTcp) { @@ -1681,11 +1812,16 @@ TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddressTcp) { return; } - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); peer_->SetStunServer(g_stun_server_address, kDefaultStunServerPort, kNrIceTransportTcp); peer_->SetDNSResolver(); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive")); + ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so")); + ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 ")); } TEST_F(IceGatherTest, TestGatherDNSStunServerHostname) { @@ -1697,14 +1833,21 @@ TEST_F(IceGatherTest, TestGatherDNSStunServerHostname) { peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort); peer_->SetDNSResolver(); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr")); } TEST_F(IceGatherTest, TestGatherDNSStunServerHostnameTcp) { - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort, kNrIceTransportTcp); peer_->SetDNSResolver(); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive")); + ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so")); + ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 ")); } TEST_F(IceGatherTest, TestGatherDNSStunServerHostnameBothUdpTcp) { @@ -1714,7 +1857,7 @@ TEST_F(IceGatherTest, TestGatherDNSStunServerHostnameBothUdpTcp) { std::vector stun_servers; - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_hostname, kDefaultStunServerPort, kNrIceTransportUdp)); stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_hostname, @@ -1722,6 +1865,8 @@ TEST_F(IceGatherTest, TestGatherDNSStunServerHostnameBothUdpTcp) { peer_->SetStunServers(stun_servers); peer_->SetDNSResolver(); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); } TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddressBothUdpTcp) { @@ -1731,7 +1876,7 @@ TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddressBothUdpTcp) { std::vector stun_servers; - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_address, kDefaultStunServerPort, kNrIceTransportUdp)); stun_servers.push_back(*NrIceStunServer::Create(g_stun_server_address, @@ -1739,6 +1884,8 @@ TEST_F(IceGatherTest, TestGatherDNSStunServerIpAddressBothUdpTcp) { peer_->SetStunServers(stun_servers); peer_->SetDNSResolver(); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); } TEST_F(IceGatherTest, TestGatherDNSStunBogusHostname) { @@ -1746,14 +1893,24 @@ TEST_F(IceGatherTest, TestGatherDNSStunBogusHostname) { peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort); peer_->SetDNSResolver(); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP ")); } TEST_F(IceGatherTest, TestGatherDNSStunBogusHostnameTcp) { - EnsurePeer(); + EnsurePeer(ICE_TEST_PEER_OFFERER | ICE_TEST_PEER_ENABLED_TCP); peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort, kNrIceTransportTcp); peer_->SetDNSResolver(); Gather(); + ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP ")); +} + +TEST_F(IceGatherTest, TestDefaultCandidate) { + EnsurePeer(); + peer_->SetStunServer(g_stun_server_hostname, kDefaultStunServerPort); + Gather(); + NrIceCandidate default_candidate; + ASSERT_TRUE(NS_SUCCEEDED(peer_->GetDefaultCandidate(0, &default_candidate))); } TEST_F(IceGatherTest, TestGatherTurn) { @@ -1800,7 +1957,7 @@ TEST_F(IceGatherTest, TestGatherVerifyNoLoopback) { TEST_F(IceGatherTest, TestGatherAllowLoopback) { // Set up peer with loopback allowed. - peer_ = new IceTestPeer("P1", true, false, true); + peer_ = new IceTestPeer("P1", true, true); peer_->AddStream(1); Gather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "127.0.0.1")); @@ -1808,7 +1965,7 @@ TEST_F(IceGatherTest, TestGatherAllowLoopback) { TEST_F(IceGatherTest, TestGatherTcpDisabled) { // Set up peer with tcp disabled. - peer_ = new IceTestPeer("P1", true, false, false, false); + peer_ = new IceTestPeer("P1", true, false, false); peer_->AddStream(1); Gather(); ASSERT_FALSE(StreamHasMatchingCandidate(0, " TCP ")); @@ -1901,33 +2058,60 @@ TEST_F(IceGatherTest, TestStunServerReturnsLoopbackAddrV6) { TEST_F(IceGatherTest, TestStunServerTrickle) { UseFakeStunUdpServerWithResponse("192.0.2.1", 3333); - TestStunServer::GetInstance(AF_INET)->SetActive(false); + TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3); Gather(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1")); - TestStunServer::GetInstance(AF_INET)->SetActive(true); WaitForGather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1")); } +// Test default route only with our fake STUN server and +// apparently NATted. +TEST_F(IceGatherTest, TestFakeStunServerNatedDefaultRouteOnly) { + peer_ = new IceTestPeer("P1", true, false, false, false, true); + peer_->AddStream(1); + UseFakeStunUdpServerWithResponse("192.0.2.1", 3333); + Gather(0); + WaitForGather(); + DumpCandidates(0); + ASSERT_FALSE(StreamHasMatchingCandidate(0, "host")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx")); + NrIceCandidate default_candidate; + nsresult rv = peer_->GetDefaultCandidate(0, &default_candidate); + if (NS_SUCCEEDED(rv)) { + ASSERT_NE(NrIceCandidate::ICE_HOST, default_candidate.type); + } +} + +// Test default route only with our fake STUN server and +// apparently non-NATted. +TEST_F(IceGatherTest, TestFakeStunServerNoNatDefaultRouteOnly) { + peer_ = new IceTestPeer("P1", true, false, false, false, true); + peer_->AddStream(1); + UseTestStunServer(); + Gather(0); + WaitForGather(); + DumpCandidates(0); + ASSERT_FALSE(StreamHasMatchingCandidate(0, "host")); + ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx")); +} + TEST_F(IceGatherTest, TestStunTcpServerTrickle) { UseFakeStunTcpServerWithResponse("192.0.3.1", 3333); - TestStunTcpServer::GetInstance(AF_INET)->SetActive(false); + TestStunServer::GetInstance(AF_INET)->SetDelay(500); Gather(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); - TestStunTcpServer::GetInstance(AF_INET)->SetActive(true); WaitForGather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); } TEST_F(IceGatherTest, TestStunTcpAndUdpServerTrickle) { UseFakeStunUdpTcpServersWithResponse("192.0.2.1", 3333, "192.0.3.1", 3333); - TestStunServer::GetInstance(AF_INET)->SetActive(false); - TestStunTcpServer::GetInstance(AF_INET)->SetActive(false); + TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3); + TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500); Gather(0); ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP")); ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); - TestStunServer::GetInstance(AF_INET)->SetActive(true); - TestStunTcpServer::GetInstance(AF_INET)->SetActive(true); WaitForGather(); ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP")); ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype ")); @@ -1939,13 +2123,13 @@ TEST_F(IceConnectTest, TestGather) { } TEST_F(IceConnectTest, TestGatherTcp) { - Init(false, false, true); + Init(false, true); AddStream("first", 1); ASSERT_TRUE(Gather()); } TEST_F(IceConnectTest, TestGatherAutoPrioritize) { - Init(false, false, false); + Init(false, false); AddStream("first", 1); ASSERT_TRUE(Gather()); } @@ -1958,7 +2142,7 @@ TEST_F(IceConnectTest, TestConnect) { } TEST_F(IceConnectTest, TestConnectTcp) { - Init(false, false, true); + Init(false, true); AddStream("first", 1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpCandidate); @@ -1970,7 +2154,7 @@ TEST_F(IceConnectTest, TestConnectTcp) { //TCP SO tests works on localhost only with delay applied: // tc qdisc add dev lo root netem delay 10ms TEST_F(IceConnectTest, DISABLED_TestConnectTcpSo) { - Init(false, false, true); + Init(false, true); AddStream("first", 1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpSoCandidate); @@ -1979,15 +2163,24 @@ TEST_F(IceConnectTest, DISABLED_TestConnectTcpSo) { Connect(); } -TEST_F(IceConnectTest, TestLoopbackOnlySortOf) { - Init(false, true, false); +// Disabled because this breaks with hairpinning. +TEST_F(IceConnectTest, DISABLED_TestConnectDefaultRouteOnly) { + Init(false, false, true); AddStream("first", 1); ASSERT_TRUE(Gather()); - SetCandidateFilter(IsLoopbackCandidate); - SetExpectedCandidateAddr("127.0.0.1"); + SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, + NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, kNrIceTransportTcp); Connect(); } +TEST_F(IceConnectTest, TestLoopbackOnlySortOf) { + Init(true, false); + AddStream("first", 1); + SetCandidateFilter(IsLoopbackCandidate); + ASSERT_TRUE(Gather(kDefaultTimeout, false)); + SetExpectedRemoteCandidateAddr("127.0.0.1"); + Connect(); +} TEST_F(IceConnectTest, TestConnectBothControllingP1Wins) { AddStream("first", 1); @@ -2064,7 +2257,7 @@ TEST_F(IceConnectTest, TestGatherFullCone) { } TEST_F(IceConnectTest, TestGatherFullConeAutoPrioritize) { - Init(false, true, false); + Init(true, false); AddStream("first", 1); UseNat(); SetFilteringType(TestNat::ENDPOINT_INDEPENDENT); @@ -2084,6 +2277,31 @@ TEST_F(IceConnectTest, TestConnectFullCone) { Connect(); } +TEST_F(IceConnectTest, TestConnectNoNatRouteOnly) { + Init(false, false, true); + AddStream("first", 1); + UseTestStunServer(); + // Because we are connecting from our host candidate to the + // other side's apparent srflx (which is also their host) + // we see a host/srflx pair. + SetExpectedTypes(NrIceCandidate::Type::ICE_HOST, + NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); + ASSERT_TRUE(Gather(kDefaultTimeout, false)); + Connect(); +} + +TEST_F(IceConnectTest, TestConnectFullConeDefaultRouteOnly) { + Init(false, false, true); + AddStream("first", 1); + UseNat(); + SetFilteringType(TestNat::ENDPOINT_INDEPENDENT); + SetMappingType(TestNat::ENDPOINT_INDEPENDENT); + SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE, + NrIceCandidate::Type::ICE_SERVER_REFLEXIVE); + ASSERT_TRUE(Gather()); + Connect(); +} + TEST_F(IceConnectTest, TestGatherAddressRestrictedCone) { AddStream("first", 1); UseNat(); @@ -2246,7 +2464,7 @@ TEST_F(IceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) { } TEST_F(IceConnectTest, TestConnectAutoPrioritize) { - Init(false, false, false); + Init(false, false); AddStream("first", 1); ASSERT_TRUE(Gather()); Connect(); @@ -2300,6 +2518,44 @@ void DelayRelayCandidates( } } +void AddNonPairableCandidates( + std::vector& candidates, + IceTestPeer *peer, size_t stream, int net_type) { + for (int i=1; i<5; i++) { + if (net_type == i) + continue; + switch (i) { + case 1: + candidates.push_back(new SchedulableTrickleCandidate(peer, stream, + "candidate:0 1 UDP 2113601790 10.0.0.1 12345 typ host")); + break; + case 2: + candidates.push_back(new SchedulableTrickleCandidate(peer, stream, + "candidate:0 1 UDP 2113601791 172.16.1.1 12345 typ host")); + break; + case 3: + candidates.push_back(new SchedulableTrickleCandidate(peer, stream, + "candidate:0 1 UDP 2113601792 192.168.0.1 12345 typ host")); + break; + case 4: + candidates.push_back(new SchedulableTrickleCandidate(peer, stream, + "candidate:0 1 UDP 2113601793 100.64.1.1 12345 typ host")); + break; + default: + UNIMPLEMENTED; + } + } + + for (auto i = candidates.rbegin(); i != candidates.rend(); ++i) { + std::cerr << "Scheduling candidate: " << (*i)->Candidate().c_str() << std::endl; + (*i)->Schedule(0); + } +} + +void DropTrickleCandidates( + std::vector& candidates) { +} + TEST_F(IceConnectTest, TestConnectTrickleAddStreamDuringICE) { AddStream("first", 1); ASSERT_TRUE(Gather()); @@ -2349,6 +2605,26 @@ TEST_F(IceConnectTest, RemoveStream) { ConnectTrickle(); } +TEST_F(IceConnectTest, P1NoTrickle) { + AddStream("first", 1); + ASSERT_TRUE(Gather()); + ConnectTrickle(); + DropTrickleCandidates(p1_->ControlTrickle(0)); + RealisticTrickleDelay(p2_->ControlTrickle(0)); + ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000); + ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000); +} + +TEST_F(IceConnectTest, P2NoTrickle) { + AddStream("first", 1); + ASSERT_TRUE(Gather()); + ConnectTrickle(); + RealisticTrickleDelay(p1_->ControlTrickle(0)); + DropTrickleCandidates(p2_->ControlTrickle(0)); + ASSERT_TRUE_WAIT(p1_->ice_complete(), 1000); + ASSERT_TRUE_WAIT(p2_->ice_complete(), 1000); +} + TEST_F(IceConnectTest, RemoveAndAddStream) { AddStream("first", 1); AddStream("second", 1); @@ -2429,7 +2705,7 @@ TEST_F(IceConnectTest, TestSendReceive) { } TEST_F(IceConnectTest, TestSendReceiveTcp) { - Init(false, false, true); + Init(false, true); AddStream("first", 1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpCandidate); @@ -2442,7 +2718,7 @@ TEST_F(IceConnectTest, TestSendReceiveTcp) { //TCP SO tests works on localhost only with delay applied: // tc qdisc add dev lo root netem delay 10ms TEST_F(IceConnectTest, DISABLED_TestSendReceiveTcpSo) { - Init(false, false, true); + Init(false, true); AddStream("first", 1); ASSERT_TRUE(Gather()); SetCandidateFilter(IsTcpSoCandidate); @@ -2672,6 +2948,76 @@ TEST_F(IceConnectTest, TestPollCandPairsAfterConnect) { ASSERT_TRUE(ContainsSucceededPair(pairs)); } +TEST_F(IceConnectTest, TestHostCandPairingFilter) { + AddStream("first", 1); + ASSERT_TRUE(Gather(kDefaultTimeout, false)); + SetCandidateFilter(IsIpv4Candidate); + + int host_net = p1_->GetCandidatesPrivateIpv4Range(0); + if (host_net <= 0) { + // TODO bug 1226838: make this work with multiple private IPs + FAIL() << "This test needs exactly one private IPv4 host candidate to work" << std::endl; + } + + ConnectTrickle(); + AddNonPairableCandidates(p1_->ControlTrickle(0), p1_, 0, host_net); + AddNonPairableCandidates(p2_->ControlTrickle(0), p2_, 0, host_net); + + std::vector pairs; + p1_->GetCandidatePairs(0, &pairs); + for (auto p : pairs) { + std::cerr << "Verifying pair:" << std::endl; + p1_->DumpCandidatePair(p); + nr_transport_addr addr; + nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr); + ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net); + nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr); + ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net); + } +} + +TEST_F(IceConnectTest, TestSrflxCandPairingFilter) { + if (g_stun_server_address.empty()) { + return; + } + + AddStream("first", 1); + ASSERT_TRUE(Gather(kDefaultTimeout)); + SetCandidateFilter(IsSrflxCandidate); + + if (p1_->GetCandidatesPrivateIpv4Range(0) <= 0) { + // TODO bug 1226838: make this work with public IP addresses + std::cerr << "Don't run this test at IETF meetings!" << std::endl; + FAIL() << "This test needs one private IPv4 host candidate to work" << std::endl; + } + + ConnectTrickle(); + SimulateTrickleP1(0); + SimulateTrickleP2(0); + + std::vector pairs; + p1_->GetCandidatePairs(0, &pairs); + for (auto p : pairs) { + std::cerr << "Verifying P1 pair:" << std::endl; + p1_->DumpCandidatePair(p); + nr_transport_addr addr; + nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr); + ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0); + nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr); + ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0); + } + p2_->GetCandidatePairs(0, &pairs); + for (auto p : pairs) { + std::cerr << "Verifying P2 pair:" << std::endl; + p2_->DumpCandidatePair(p); + nr_transport_addr addr; + nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0, IPPROTO_UDP, &addr); + ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0); + nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0, IPPROTO_UDP, &addr); + ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0); + } +} + TEST_F(IceConnectTest, TestPollCandPairsDuringConnect) { AddStream("first", 1); ASSERT_TRUE(Gather()); @@ -2880,6 +3226,17 @@ TEST_F(PacketFilterTest, TestSendNonRequestStunPacket) { ASSERT_EQ(0, nr_stun_message_destroy(&msg)); } +TEST(InternalsTest, TestAddBogusAttribute) { + nr_stun_message *req; + ASSERT_EQ(0, nr_stun_message_create(&req)); + Data *data; + ASSERT_EQ(0, r_data_alloc(&data, 3000)); + memset(data->data, 'A', data->len); + ASSERT_TRUE(nr_stun_message_add_message_integrity_attribute(req, data)); + ASSERT_EQ(0, r_data_destroy(&data)); + ASSERT_EQ(0, nr_stun_message_destroy(&req)); +} + static std::string get_environment(const char *name) { char *value = getenv(name); @@ -2889,9 +3246,53 @@ static std::string get_environment(const char *name) { return value; } +// DNS resolution helper code +static std::string +Resolve(const std::string& fqdn, int address_family) +{ + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = address_family; + hints.ai_protocol = IPPROTO_UDP; + struct addrinfo *res; + int err = getaddrinfo(fqdn.c_str(), nullptr, &hints, &res); + if (err) { + std::cerr << "Error in getaddrinfo: " << err << std::endl; + return ""; + } + + char str_addr[64] = {0}; + switch (res->ai_family) { + case AF_INET: + inet_ntop( + AF_INET, + &reinterpret_cast(res->ai_addr)->sin_addr, + str_addr, + sizeof(str_addr)); + break; + case AF_INET6: + inet_ntop( + AF_INET6, + &reinterpret_cast(res->ai_addr)->sin6_addr, + str_addr, + sizeof(str_addr)); + break; + default: + std::cerr << "Got unexpected address family in DNS lookup: " + << res->ai_family << std::endl; + return ""; + } + + if (!strlen(str_addr)) { + std::cerr << "inet_ntop failed" << std::endl; + } + + return str_addr; +} + int main(int argc, char **argv) { -#ifdef LINUX +#ifdef ANDROID // This test can cause intermittent oranges on the builders on Linux CHECK_ENVIRONMENT_FLAG("MOZ_WEBRTC_TESTS") #endif @@ -2900,6 +3301,7 @@ int main(int argc, char **argv) g_turn_user = get_environment("TURN_SERVER_USER"); g_turn_password = get_environment("TURN_SERVER_PASSWORD"); + if (g_turn_server.empty() || g_turn_user.empty(), g_turn_password.empty()) { @@ -2933,6 +3335,12 @@ int main(int argc, char **argv) NSS_NoDB_Init(nullptr); NSS_SetDomesticPolicy(); + // If only a STUN server FQDN was provided, look up its IP address for the + // address-only tests. + if (g_stun_server_address.empty() && !g_stun_server_hostname.empty()) { + g_stun_server_address = Resolve(g_stun_server_hostname, AF_INET); + } + // Start the tests ::testing::InitGoogleTest(&argc, argv); diff --git a/media/mtransport/test/moz.build b/media/mtransport/test/moz.build index 770ef18d89..cdd190d395 100644 --- a/media/mtransport/test/moz.build +++ b/media/mtransport/test/moz.build @@ -40,7 +40,6 @@ else: if CONFIG['OS_TARGET'] == 'Linux': DEFINES['LINUX'] = True - DEFINES['USE_INTERFACE_PRIORITIZER'] = True LOCAL_INCLUDES += [ '/media/mtransport/third_party/nrappkit/src/port/linux/include', ] diff --git a/media/mtransport/test/multi_tcp_socket_unittest.cpp b/media/mtransport/test/multi_tcp_socket_unittest.cpp index 0be1f28ce1..047a8ef54e 100644 --- a/media/mtransport/test/multi_tcp_socket_unittest.cpp +++ b/media/mtransport/test/multi_tcp_socket_unittest.cpp @@ -26,6 +26,11 @@ extern "C" { #include "nr_socket_prsock.h" +#include "stunserver.h" +// TODO(bcampen@mozilla.com): Big fat hack since the build system doesn't give +// us a clean way to add object files to a single executable. +#include "stunserver.cpp" + #include "nricectx.h" #include "nricemediastream.h" @@ -82,29 +87,17 @@ class MultiTcpSocketTest : public ::testing::Test { return port | 49152; } - void Create_s(nr_socket_tcp_type tcp_type, nr_socket *stun_server_socket, - int use_framing, nr_socket **sock) { + void Create_s(nr_socket_tcp_type tcp_type, std::string stun_server_addr, + uint16_t stun_server_port, nr_socket **sock) { nr_transport_addr local; // Get start of port range for test static unsigned short port_s = GetRandomPort(); int r; - if (stun_server_socket) { - nr_transport_addr stun_addr; - int port; - char stun_host[1000]; - - r = nr_socket_getaddr(stun_server_socket, &stun_addr); - ASSERT_EQ(0, r); - r = nr_transport_addr_get_port(&stun_addr, &port); - ASSERT_EQ(0, r); - r = nr_transport_addr_get_addrstring(&stun_addr, &stun_host[0], - sizeof(stun_host)); - ASSERT_EQ(0, r); - + if (!stun_server_addr.empty()) { std::vector stun_servers; ScopedDeletePtr server(NrIceStunServer::Create( - stun_host, port, kNrIceTransportTcp)); + stun_server_addr, stun_server_port, kNrIceTransportTcp)); stun_servers.push_back(*server); ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers))); @@ -117,7 +110,7 @@ class MultiTcpSocketTest : public ::testing::Test { ASSERT_EQ(0, r); r = nr_socket_multi_tcp_create(ice_ctx_->ctx(), - &local, tcp_type, 1, use_framing, 2048, sock); + &local, tcp_type, 1, 2048, sock); } ASSERT_EQ(0, r); @@ -128,13 +121,13 @@ class MultiTcpSocketTest : public ::testing::Test { } nr_socket *Create(nr_socket_tcp_type tcp_type, - nr_socket *stun_server_socket = NULL, - int use_framing = 1) { + std::string stun_server_addr = "", + uint16_t stun_server_port = 0) { nr_socket *sock=nullptr; test_utils->sts_target()->Dispatch( WrapRunnable( this, &MultiTcpSocketTest::Create_s, tcp_type, - stun_server_socket, use_framing, &sock), + stun_server_addr, stun_server_port, &sock), NS_DISPATCH_SYNC); return sock; } @@ -195,50 +188,93 @@ class MultiTcpSocketTest : public ::testing::Test { NS_DISPATCH_SYNC); } - void SendData_s(nr_socket *from, nr_socket *to, const char *data, + void SendDataToAddress_s(nr_socket *from, nr_transport_addr *to, const char *data, + size_t len) { + nr_transport_addr addr_from; + + int r=nr_socket_getaddr(from, &addr_from); + ASSERT_EQ(0, r); + printf("Sending %lu bytes %s -> %s\n", (unsigned long)len, + addr_from.as_string, to->as_string); + r=nr_socket_sendto(from, data, len, 0, to); + ASSERT_EQ(0, r); + } + + void SendData(nr_socket *from, nr_transport_addr *to, const char *data, size_t len) { + test_utils->sts_target()->Dispatch( + WrapRunnable( + this, &MultiTcpSocketTest::SendDataToAddress_s, from, to, data, + len), + NS_DISPATCH_SYNC); + } + + void SendDataToSocket_s(nr_socket *from, nr_socket *to, const char *data, size_t len) { - nr_transport_addr addr_from, addr_to; + nr_transport_addr addr_to; int r=nr_socket_getaddr(to, &addr_to); ASSERT_EQ(0, r); - r=nr_socket_getaddr(from, &addr_from); - ASSERT_EQ(0, r); - printf("Sending %lu bytes %s -> %s\n", (unsigned long)len, - addr_from.as_string, addr_to.as_string); - r=nr_socket_sendto(from, data, len, 0, &addr_to); - ASSERT_EQ(0, r); + SendDataToAddress_s(from, &addr_to, data, len); } void SendData(nr_socket *from, nr_socket *to, const char *data, size_t len) { test_utils->sts_target()->Dispatch( WrapRunnable( - this, &MultiTcpSocketTest::SendData_s, from, to, data, len), + this, &MultiTcpSocketTest::SendDataToSocket_s, from, to, data, + len), NS_DISPATCH_SYNC); } - void RecvData_s(nr_socket *expected_from, nr_socket *sent_to, - const char *expected_data, size_t expected_len) { + void RecvDataFromAddress_s(nr_transport_addr *expected_from, + nr_socket *sent_to, + const char *expected_data, + size_t expected_len) { SetReadable(false); - char received_data[expected_len+1]; - nr_transport_addr addr_from, addr_to; + size_t buflen = expected_len ? expected_len+1 : 100; + char received_data[buflen]; + nr_transport_addr addr_to; nr_transport_addr retaddr; size_t retlen; int r=nr_socket_getaddr(sent_to, &addr_to); ASSERT_EQ(0, r); - r=nr_socket_getaddr(expected_from, &addr_from); - ASSERT_EQ(0, r); printf("Receiving %lu bytes %s <- %s\n", (unsigned long)expected_len, - addr_to.as_string, addr_from.as_string); - r=nr_socket_recvfrom(sent_to, received_data, expected_len+1, + addr_to.as_string, expected_from->as_string); + r=nr_socket_recvfrom(sent_to, received_data, buflen, &retlen, 0, &retaddr); ASSERT_EQ(0, r); - r=nr_transport_addr_cmp(&retaddr, &addr_from, + r=nr_transport_addr_cmp(&retaddr, expected_from, NR_TRANSPORT_ADDR_CMP_MODE_ALL); ASSERT_EQ(0, r); - ASSERT_EQ(expected_len, retlen); - r=memcmp(expected_data, received_data, retlen); + // expected_len == 0 means we just expected some data + if (expected_len == 0) { + ASSERT_GT(retlen, 0U); + } else { + ASSERT_EQ(expected_len, retlen); + r=memcmp(expected_data, received_data, retlen); + ASSERT_EQ(0, r); + } + } + + void RecvData(nr_transport_addr *expected_from, nr_socket *sent_to, + const char *expected_data = nullptr, size_t expected_len = 0) { + ASSERT_TRUE_WAIT(IsReadable(), 1000); + test_utils->sts_target()->Dispatch( + WrapRunnable( + this, &MultiTcpSocketTest::RecvDataFromAddress_s, + expected_from, sent_to, expected_data, + expected_len), + NS_DISPATCH_SYNC); + } + + void RecvDataFromSocket_s(nr_socket *expected_from, nr_socket *sent_to, + const char *expected_data, size_t expected_len) { + nr_transport_addr addr_from; + + int r=nr_socket_getaddr(expected_from, &addr_from); ASSERT_EQ(0, r); + + RecvDataFromAddress_s(&addr_from, sent_to, expected_data, expected_len); } void RecvData(nr_socket *expected_from, nr_socket *sent_to, @@ -246,8 +282,8 @@ class MultiTcpSocketTest : public ::testing::Test { ASSERT_TRUE_WAIT(IsReadable(), 1000); test_utils->sts_target()->Dispatch( WrapRunnable( - this, &MultiTcpSocketTest::RecvData_s, expected_from, sent_to, - expected_data, expected_len), + this, &MultiTcpSocketTest::RecvDataFromSocket_s, + expected_from, sent_to, expected_data, expected_len), NS_DISPATCH_SYNC); } @@ -357,18 +393,25 @@ TEST_F(MultiTcpSocketTest, TestActivePassiveWithStunServerMockup) { }; const char data[] = "TestActivePassiveWithStunServerMockup"; - socks[0] = Create(TCP_TYPE_PASSIVE, NULL, 0); // stun server socket + nr_transport_addr stun_srv_addr; + std::string stun_addr; + uint16_t stun_port; + stun_addr = TestStunTcpServer::GetInstance(AF_INET)->addr(); + stun_port = TestStunTcpServer::GetInstance(AF_INET)->port(); + int r = nr_str_port_to_transport_addr(stun_addr.c_str(), stun_port, IPPROTO_TCP, &stun_srv_addr); + ASSERT_EQ(0, r); + + socks[0] = Create(TCP_TYPE_PASSIVE, stun_addr, stun_port); Listen(socks[0]); + socks[1] = Create(TCP_TYPE_ACTIVE, stun_addr, stun_port); - socks[1] = Create(TCP_TYPE_PASSIVE, socks[0]); - Listen(socks[1]); - socks[2] = Create(TCP_TYPE_ACTIVE, socks[0]); + /* Send a fake STUN request and expect a STUN error response */ + SendData(socks[0], &stun_srv_addr, stunMessage, sizeof(stunMessage)); + RecvData(&stun_srv_addr, socks[0]); - TransferData(socks[1], socks[0], stunMessage, sizeof(stunMessage)); - TransferData(socks[0], socks[1], stunMessage, sizeof(stunMessage)); - Connect(socks[2], socks[1]); - TransferData(socks[2], socks[1], data, sizeof(data)); - TransferData(socks[1], socks[2], data, sizeof(data)); + Connect(socks[1], socks[0]); + TransferData(socks[1], socks[0], data, sizeof(data)); + TransferData(socks[0], socks[1], data, sizeof(data)); } TEST_F(MultiTcpSocketTest, TestConnectTwoSo) { @@ -423,11 +466,21 @@ int main(int argc, char **argv) // Adds a listener to the end. Google Test takes the ownership. listeners.Append(new test::RingbufferDumper(test_utils)); + test_utils->sts_target()->Dispatch( + WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET), + NS_DISPATCH_SYNC); + test_utils->sts_target()->Dispatch( + WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6), + NS_DISPATCH_SYNC); + // Start the tests ::testing::InitGoogleTest(&argc, argv); int rv = RUN_ALL_TESTS(); + test_utils->sts_target()->Dispatch( + WrapRunnableNM(&TestStunTcpServer::ShutdownInstance), NS_DISPATCH_SYNC); + delete test_utils; return rv; } diff --git a/media/mtransport/test/stunserver.cpp b/media/mtransport/test/stunserver.cpp index 2198c41bb7..67b150ed4f 100644 --- a/media/mtransport/test/stunserver.cpp +++ b/media/mtransport/test/stunserver.cpp @@ -92,7 +92,7 @@ extern "C" { #include "local_addr.h" #include "stun_util.h" #include "registry.h" -#include "nr_socket_multi_tcp.h" +#include "nr_socket_buffered_stun.h" } #include "stunserver.h" @@ -131,11 +131,15 @@ static int nr_socket_wrapped_sendto(void *obj, const void *msg, size_t len, int static int nr_socket_wrapped_recvfrom(void *obj, void * restrict buf, size_t maxlen, size_t *len, int flags, nr_transport_addr *addr) { - MOZ_CRASH(); + nr_socket_wrapped *wrapped = static_cast(obj); + + return nr_socket_recvfrom(wrapped->sock_, buf, maxlen, len, flags, addr); } static int nr_socket_wrapped_getfd(void *obj, NR_SOCKET *fd) { - MOZ_CRASH(); + nr_socket_wrapped *wrapped = static_cast(obj); + + return nr_socket_getfd(wrapped->sock_, fd); } static int nr_socket_wrapped_getaddr(void *obj, nr_transport_addr *addrp) { @@ -186,10 +190,10 @@ int nr_socket_wrapped_create(nr_socket *inner, nr_socket **outp) { // Instance static. // Note: Calling Create() at static init time is not going to be safe, since // we have no reason to expect this will be initted to a nullptr yet. -TestStunServer* TestStunServer::instance; -TestStunTcpServer* TestStunTcpServer::instance; -TestStunServer* TestStunServer::instance6; -TestStunTcpServer* TestStunTcpServer::instance6; +TestStunServer *TestStunServer::instance; +TestStunTcpServer *TestStunTcpServer::instance; +TestStunServer *TestStunServer::instance6; +TestStunTcpServer *TestStunTcpServer::instance6; uint16_t TestStunServer::instance_port = 3478; uint16_t TestStunTcpServer::instance_port = 3478; @@ -216,7 +220,7 @@ TestStunServer::~TestStunServer() { delete response_addr_; } -int TestStunServer::SetInternalPort(nr_local_addr* addr, uint16_t port) { +int TestStunServer::SetInternalPort(nr_local_addr *addr, uint16_t port) { if (nr_transport_addr_set_port(&addr->addr, port)) { MOZ_MTLOG(ML_ERROR, "Couldn't set port"); return R_INTERNAL; @@ -230,7 +234,7 @@ int TestStunServer::SetInternalPort(nr_local_addr* addr, uint16_t port) { return 0; } -int TestStunServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) { +int TestStunServer::TryOpenListenSocket(nr_local_addr *addr, uint16_t port) { int r = SetInternalPort(addr, port); @@ -372,21 +376,30 @@ void TestStunServer::ShutdownInstance() { struct DeferredStunOperation { DeferredStunOperation(TestStunServer *server, const char *data, size_t len, - nr_transport_addr *addr) : + nr_transport_addr *addr, + nr_socket *sock) : server_(server), - buffer_(reinterpret_cast(data), len) { + buffer_(reinterpret_cast(data), len), + sock_(sock) { nr_transport_addr_copy(&addr_, addr); } TestStunServer *server_; DataBuffer buffer_; nr_transport_addr addr_; + nr_socket *sock_; }; -void TestStunServer::Process(const uint8_t *msg, size_t len, nr_transport_addr *addr) { +void TestStunServer::Process(const uint8_t *msg, size_t len, nr_transport_addr *addr, nr_socket *sock) { + + if (!sock) { + sock = send_sock_; + } + // Set the wrapped address so that the response goes to the right place. - nr_socket_wrapped_set_send_addr(send_sock_, addr); - nr_stun_server_process_request(stun_server_, send_sock_, + nr_socket_wrapped_set_send_addr(sock, addr); + + nr_stun_server_process_request(stun_server_, sock, const_cast(reinterpret_cast(msg)), len, response_addr_ ? @@ -397,32 +410,43 @@ void TestStunServer::Process(const uint8_t *msg, size_t len, nr_transport_addr * void TestStunServer::process_cb(NR_SOCKET s, int how, void *cb_arg) { DeferredStunOperation *op = static_cast(cb_arg); op->server_->timer_handle_ = nullptr; - op->server_->Process(op->buffer_.data(), op->buffer_.len(), &op->addr_); + op->server_->Process(op->buffer_.data(), op->buffer_.len(), &op->addr_, op->sock_); delete op; } -void TestStunServer::readable_cb(NR_SOCKET s, int how, void *cb_arg) { - TestStunServer* server = static_cast(cb_arg); +nr_socket* TestStunServer::GetReceivingSocket(NR_SOCKET s) { + return listen_sock_; +} - char message[4096]; +nr_socket* TestStunServer::GetSendingSocket(nr_socket *sock) { + return send_sock_; +} + +void TestStunServer::readable_cb(NR_SOCKET s, int how, void *cb_arg) { + TestStunServer *server = static_cast(cb_arg); + + char message[max_stun_message_size]; size_t message_len; nr_transport_addr addr; + nr_socket *recv_sock = server->GetReceivingSocket(s); + if (!recv_sock) { + MOZ_MTLOG(ML_ERROR, "Failed to lookup receiving socket"); + return; + } + nr_socket *send_sock = server->GetSendingSocket(recv_sock); - int r = nr_socket_recvfrom(server->listen_sock_, message, sizeof(message), - &message_len, 0, &addr); + /* Re-arm. */ + NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server); - if (r) { + if (nr_socket_recvfrom(recv_sock, message, sizeof(message), + &message_len, 0, &addr)) { MOZ_MTLOG(ML_ERROR, "Couldn't read STUN message"); return; } MOZ_MTLOG(ML_DEBUG, "Received data of length " << message_len); - // Re-arm. - NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server); - - // If we have initial dropping set, check at this point. std::string key(addr.as_string); @@ -444,10 +468,11 @@ void TestStunServer::readable_cb(NR_SOCKET s, int how, void *cb_arg) { new DeferredStunOperation( server, message, message_len, - &addr), + &addr, send_sock), &server->timer_handle_); } else { - server->Process(reinterpret_cast(message), message_len, &addr); + server->Process(reinterpret_cast(message), message_len, + &addr, send_sock); } } @@ -525,11 +550,12 @@ TestStunTcpServer* TestStunTcpServer::GetInstance(int address_family) { void TestStunTcpServer::ShutdownInstance() { delete instance; - instance = nullptr; + delete instance6; + instance6 = nullptr; } -int TestStunTcpServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) { +int TestStunTcpServer::TryOpenListenSocket(nr_local_addr *addr, uint16_t port) { addr->addr.protocol=IPPROTO_TCP; @@ -538,17 +564,15 @@ int TestStunTcpServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) { if (r) return r; - if (ice_ctx_ == NULL) - ice_ctx_ = NrIceCtx::Create("stun", true); + nr_socket *sock; + if (nr_socket_local_create(nullptr, &addr->addr, &sock)) { + MOZ_MTLOG(ML_ERROR, "Couldn't create listen tcp socket"); + return R_ALREADY; + } - //TODO (nils@mozilla.com) can we replace this with a more basic TCP socket - // alternative which would allow us to remove the framing argument from the - // nr_socket_multi_tcp_create() call? - if(nr_socket_multi_tcp_create(ice_ctx_->ctx(), - &addr->addr, TCP_TYPE_PASSIVE, 0, 0, 2048, - &listen_sock_)) { - MOZ_MTLOG(ML_ERROR, "Couldn't create listen socket"); - return R_ALREADY; + if (nr_socket_buffered_stun_create(sock, 2048, TURN_TCP_FRAMING, &listen_sock_)) { + MOZ_MTLOG(ML_ERROR, "Couldn't create listen tcp socket"); + return R_ALREADY; } if(nr_socket_listen(listen_sock_, 10)) { @@ -559,6 +583,51 @@ int TestStunTcpServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) { return 0; } +nr_socket* TestStunTcpServer::GetReceivingSocket(NR_SOCKET s) { + return connections_[s]; +} + +nr_socket* TestStunTcpServer::GetSendingSocket(nr_socket *sock) { + return sock; +} + +void TestStunTcpServer::accept_cb(NR_SOCKET s, int how, void *cb_arg) { + TestStunTcpServer *server = static_cast(cb_arg); + nr_socket *newsock, *bufsock, *wrapsock; + nr_transport_addr remote_addr; + NR_SOCKET fd; + + /* rearm */ + NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunTcpServer::accept_cb, cb_arg); + + /* accept */ + if (nr_socket_accept(server->listen_sock_, &remote_addr, &newsock)) { + MOZ_MTLOG(ML_ERROR, "Couldn't accept incoming tcp connection"); + return; + } + + if(nr_socket_buffered_stun_create(newsock, 2048, TURN_TCP_FRAMING, &bufsock)) { + MOZ_MTLOG(ML_ERROR, "Couldn't create connected tcp socket"); + return; + } + + nr_socket_buffered_set_connected_to(bufsock, &remote_addr); + + if(nr_socket_wrapped_create(bufsock, &wrapsock)) { + MOZ_MTLOG(ML_ERROR, "Couldn't wrap connected tcp socket"); + return; + } + + if(nr_socket_getfd(bufsock, &fd)) { + MOZ_MTLOG(ML_ERROR, "Couldn't get fd from connected tcp socket"); + return; + } + + server->connections_[fd] = wrapsock; + + NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server); +} + TestStunTcpServer* TestStunTcpServer::Create(int address_family) { NR_reg_init(NR_REG_MODE_LOCAL); @@ -568,15 +637,23 @@ TestStunTcpServer* TestStunTcpServer::Create(int address_family) { return nullptr; } - nr_socket_multi_tcp_set_readable_cb(server->listen_sock_, - &TestStunServer::readable_cb, server.get()); + NR_SOCKET fd; + if(nr_socket_getfd(server->listen_sock_, &fd)) { + MOZ_MTLOG(ML_ERROR, "Couldn't get tcp fd"); + return nullptr; + } + + NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunTcpServer::accept_cb, server.get()); return server.forget(); } TestStunTcpServer::~TestStunTcpServer() { - ice_ctx_ = nullptr; - nr_socket_destroy(&listen_sock_); + for (auto it = connections_.begin(); it != connections_.end();) { + NR_ASYNC_CANCEL(it->first, NR_ASYNC_WAIT_READ); + nr_socket_destroy(&it->second); + connections_.erase(it++); + } } } // close namespace diff --git a/media/mtransport/test/stunserver.h b/media/mtransport/test/stunserver.h index 93fed74d8b..c1a8c9e935 100644 --- a/media/mtransport/test/stunserver.h +++ b/media/mtransport/test/stunserver.h @@ -18,6 +18,7 @@ typedef struct nr_stun_server_ctx_ nr_stun_server_ctx; typedef struct nr_socket_ nr_socket; typedef struct nr_local_addr_ nr_local_addr; + namespace mozilla { class TestStunServer { @@ -47,6 +48,11 @@ class TestStunServer { void Reset(); + static const size_t max_stun_message_size = 4096; + + virtual nr_socket* GetReceivingSocket(NR_SOCKET s); + virtual nr_socket* GetSendingSocket(nr_socket *sock); + protected: TestStunServer() : listen_port_(0), @@ -59,13 +65,14 @@ class TestStunServer { response_addr_(nullptr), timer_handle_(nullptr) {} - int SetInternalPort(nr_local_addr* addr, uint16_t port); + int SetInternalPort(nr_local_addr *addr, uint16_t port); int Initialize(int address_family); + static void readable_cb(NR_SOCKET sock, int how, void *cb_arg); private: - void Process(const uint8_t *msg, size_t len, nr_transport_addr *addr_in); - virtual int TryOpenListenSocket(nr_local_addr* addr, uint16_t port); + void Process(const uint8_t *msg, size_t len, nr_transport_addr *addr_in, nr_socket *sock); + virtual int TryOpenListenSocket(nr_local_addr *addr, uint16_t port); static void process_cb(NR_SOCKET sock, int how, void *cb_arg); protected: @@ -82,8 +89,8 @@ class TestStunServer { void *timer_handle_; std::map received_ct_; - static TestStunServer* instance; - static TestStunServer* instance6; + static TestStunServer *instance; + static TestStunServer *instance6; static uint16_t instance_port; }; @@ -93,18 +100,23 @@ class TestStunTcpServer: public TestStunServer { static void ShutdownInstance(); static void ConfigurePort(uint16_t port); virtual ~TestStunTcpServer(); - protected: - TestStunTcpServer() - : ice_ctx_(nullptr) {} - RefPtr ice_ctx_; + virtual nr_socket* GetReceivingSocket(NR_SOCKET s); + virtual nr_socket* GetSendingSocket(nr_socket *sock); + + protected: + TestStunTcpServer() {} + static void accept_cb(NR_SOCKET sock, int how, void *cb_arg); + private: - virtual int TryOpenListenSocket(nr_local_addr* addr, uint16_t port); + virtual int TryOpenListenSocket(nr_local_addr *addr, uint16_t port); static TestStunTcpServer *Create(int address_family); - static TestStunTcpServer* instance; - static TestStunTcpServer* instance6; + static TestStunTcpServer *instance; + static TestStunTcpServer *instance6; static uint16_t instance_port; + + std::map connections_; }; } // End of namespace mozilla #endif diff --git a/media/mtransport/test_nr_socket.cpp b/media/mtransport/test_nr_socket.cpp index 8d8bdf0ee6..adbf1f4f6c 100644 --- a/media/mtransport/test_nr_socket.cpp +++ b/media/mtransport/test_nr_socket.cpp @@ -380,7 +380,11 @@ int TestNrSocket::connect(nr_transport_addr *addr) { return R_INTERNAL; } - if (!nat_->enabled_ || nat_->is_an_internal_tuple(*addr)) { + if (!nat_->enabled_ + || addr->protocol==IPPROTO_UDP // Horrible hack to allow default address + // discovery to work. Only works because + // we don't normally connect on UDP. + || nat_->is_an_internal_tuple(*addr)) { // This will set connect_invoked_ return NrSocket::connect(addr); } diff --git a/media/mtransport/testlib/moz.build b/media/mtransport/testlib/moz.build index 5a6e62a6cf..b59d59800e 100644 --- a/media/mtransport/testlib/moz.build +++ b/media/mtransport/testlib/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. include('../common.build') +include("/ipc/chromium/chromium-config.mozbuild") # These files cannot be built in unified mode because of the redefinition of # getLogModule, UNIMPLEMENTED, nr_socket_long_term_violation_time, diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c index 7d76b02a02..8b54888651 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c @@ -134,6 +134,7 @@ static int nr_ice_candidate_format_stun_label(char *label, size_t size, nr_ice_c int nr_ice_candidate_create(nr_ice_ctx *ctx,nr_ice_component *comp,nr_ice_socket *isock, nr_socket *osock, nr_ice_candidate_type ctype, nr_socket_tcp_type tcp_type, nr_ice_stun_server *stun_server, UCHAR component_id, nr_ice_candidate **candp) { + assert(!(ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) || ctype == RELAYED); nr_ice_candidate *cand=0; nr_ice_candidate *tmp=0; int r,_status; @@ -272,6 +273,34 @@ int nr_ice_peer_peer_rflx_candidate_create(nr_ice_ctx *ctx,char *label, nr_ice_c return(_status); } +static void nr_ice_candidate_mark_done(nr_ice_candidate *cand, int state) + { + if (!cand || !cand->done_cb) { + assert(0); + return; + } + + /* If this is a relay candidate, there's likely to be a srflx that is + * piggybacking on it. Make sure it is marked done too. */ + if ((cand->type == RELAYED) && cand->u.relayed.srvflx_candidate) { + nr_ice_candidate *srflx=cand->u.relayed.srvflx_candidate; + if (state == NR_ICE_CAND_STATE_INITIALIZED && + nr_turn_client_get_mapped_address(cand->u.relayed.turn, + &srflx->addr)) { + r_log(LOG_ICE, LOG_WARNING, "ICE(%s)/CAND(%s): Failed to get mapped address from TURN allocate response, srflx failed.", cand->ctx->label, cand->label); + nr_ice_candidate_mark_done(srflx, NR_ICE_CAND_STATE_FAILED); + } else { + nr_ice_candidate_mark_done(srflx, state); + } + } + + NR_async_cb done_cb=cand->done_cb; + cand->done_cb=0; + cand->state=state; + /* This might destroy cand! */ + done_cb(0,0,cand->cb_arg); + } + int nr_ice_candidate_destroy(nr_ice_candidate **candp) { nr_ice_candidate *cand=0; @@ -284,8 +313,7 @@ int nr_ice_candidate_destroy(nr_ice_candidate **candp) if (cand->state == NR_ICE_CAND_STATE_INITIALIZING) { /* Make sure the ICE ctx isn't still waiting around for this candidate * to init. */ - cand->state=NR_ICE_CAND_STATE_FAILED; - cand->done_cb(0,0,cand->cb_arg); + nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED); } switch(cand->type){ @@ -295,6 +323,8 @@ int nr_ice_candidate_destroy(nr_ice_candidate **candp) case RELAYED: if (cand->u.relayed.turn_handle) nr_ice_socket_deregister(cand->isock, cand->u.relayed.turn_handle); + if (cand->u.relayed.srvflx_candidate) + cand->u.relayed.srvflx_candidate->u.srvrflx.relay_candidate=0; nr_turn_client_ctx_destroy(&cand->u.relayed.turn); nr_socket_destroy(&cand->u.relayed.turn_sock); break; @@ -302,6 +332,8 @@ int nr_ice_candidate_destroy(nr_ice_candidate **candp) case SERVER_REFLEXIVE: if (cand->u.srvrflx.stun_handle) nr_ice_socket_deregister(cand->isock, cand->u.srvrflx.stun_handle); + if (cand->u.srvrflx.relay_candidate) + cand->u.srvrflx.relay_candidate->u.relayed.srvflx_candidate=0; nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun); break; default: @@ -531,15 +563,13 @@ int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, vo int protocol=NR_RESOLVE_PROTOCOL_STUN; cand->done_cb=ready_cb; cand->cb_arg=cb_arg; + cand->state=NR_ICE_CAND_STATE_INITIALIZING; switch(cand->type){ case HOST: if(r=nr_socket_getaddr(cand->isock->sock,&cand->addr)) ABORT(r); cand->osock=cand->isock->sock; - // This is actually ready, but we set this anyway to prevent it from - // being paired twice. - cand->state=NR_ICE_CAND_STATE_INITIALIZING; // Post this so that it doesn't happen in-line cand->ready_cb = ready_cb; cand->ready_cb_arg = cb_arg; @@ -551,11 +581,9 @@ int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, vo /* Fall through */ #endif case SERVER_REFLEXIVE: - cand->state=NR_ICE_CAND_STATE_INITIALIZING; - if(cand->stun_server->type == NR_ICE_STUN_SERVER_TYPE_ADDR) { - if(cand->base.ip_version != cand->stun_server->u.addr.ip_version) { - r_log(LOG_ICE, LOG_INFO, "ICE-CANDIDATE(%s): Skipping srflx/relayed candidate with different IP version (%d) than STUN/TURN server (%d).", cand->label,cand->base.ip_version,cand->stun_server->u.addr.ip_version); + if(nr_transport_addr_cmp(&cand->base,&cand->stun_server->u.addr,NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL)) { + r_log(LOG_ICE, LOG_INFO, "ICE-CANDIDATE(%s): Skipping srflx/relayed candidate because of IP version/transport mis-match with STUN/TURN server (%u/%s - %u/%s).", cand->label,cand->base.ip_version,cand->base.protocol==IPPROTO_UDP?"UDP":"TCP",cand->stun_server->u.addr.ip_version,cand->stun_server->u.addr.protocol==IPPROTO_UDP?"UDP":"TCP"); ABORT(R_NOT_FOUND); /* Same error code when DNS lookup fails */ } @@ -612,7 +640,7 @@ int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, vo _status=0; abort: if(_status && _status!=R_WOULDBLOCK) - cand->state=NR_ICE_CAND_STATE_FAILED; + nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED); return(_status); } @@ -650,8 +678,7 @@ static int nr_ice_candidate_resolved_cb(void *cb_arg, nr_transport_addr *addr) _status=0; abort: if(_status && _status!=R_WOULDBLOCK) { - cand->state=NR_ICE_CAND_STATE_FAILED; - cand->done_cb(0,0,cand->cb_arg); + nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED); } return(_status); } @@ -685,8 +712,6 @@ static int nr_ice_candidate_initialize2(nr_ice_candidate *cand) _status=0; abort: - if(_status && _status!=R_WOULDBLOCK) - cand->state=NR_ICE_CAND_STATE_FAILED; return(_status); } @@ -711,10 +736,9 @@ static void nr_ice_srvrflx_start_stun_timer_cb(NR_SOCKET s, int how, void *cb_ar _status=0; abort: - if(_status){ - cand->state=NR_ICE_CAND_STATE_FAILED; + if (_status && (cand->u.srvrflx.stun->state==NR_STUN_CLIENT_STATE_RUNNING)) { + nr_stun_client_failed(cand->u.srvrflx.stun); } - return; } @@ -733,9 +757,6 @@ static int nr_ice_srvrflx_start_stun(nr_ice_candidate *cand) _status=0; abort: - if(_status){ - cand->state=NR_ICE_CAND_STATE_FAILED; - } return(_status); } @@ -756,8 +777,8 @@ static void nr_ice_start_relay_turn_timer_cb(NR_SOCKET s, int how, void *cb_arg) _status=0; abort: - if(_status){ - cand->state=NR_ICE_CAND_STATE_FAILED; + if(_status && (cand->u.relayed.turn->state==NR_TURN_CLIENT_STATE_ALLOCATING)){ + nr_turn_client_failed(cand->u.relayed.turn); } return; } @@ -781,9 +802,6 @@ static int nr_ice_start_relay_turn(nr_ice_candidate *cand) _status=0; abort: - if(_status){ - cand->state=NR_ICE_CAND_STATE_FAILED; - } return(_status); } #endif /* USE_TURN */ @@ -809,9 +827,8 @@ static void nr_ice_srvrflx_stun_finished_cb(NR_SOCKET sock, int how, void *cb_ar cand->addr.protocol=cand->base.protocol; nr_transport_addr_fmt_addr_string(&cand->addr); nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun); - cand->state=NR_ICE_CAND_STATE_INITIALIZED; - /* Execute the ready callback */ - cand->done_cb(0,0,cand->cb_arg); + nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_INITIALIZED); + cand=0; break; /* This failed, so go to the next STUN server if there is one */ @@ -824,8 +841,7 @@ static void nr_ice_srvrflx_stun_finished_cb(NR_SOCKET sock, int how, void *cb_ar _status = 0; abort: if(_status){ - cand->state=NR_ICE_CAND_STATE_FAILED; - cand->done_cb(0,0,cand->cb_arg); + nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED); } } @@ -862,21 +878,7 @@ static void nr_ice_turn_allocated_cb(NR_SOCKET s, int how, void *cb_arg) RFREE(cand->label); cand->label=label; - cand->state=NR_ICE_CAND_STATE_INITIALIZED; - - /* We also need to activate the associated STUN candidate */ - if(cand->u.relayed.srvflx_candidate){ - nr_ice_candidate *cand2=cand->u.relayed.srvflx_candidate; - - if (r=nr_turn_client_get_mapped_address(cand->u.relayed.turn, &cand2->addr)) - ABORT(r); - - cand2->state=NR_ICE_CAND_STATE_INITIALIZED; - cand2->done_cb(0,0,cand2->cb_arg); - } - - /* Execute the ready callback */ - cand->done_cb(0,0,cand->cb_arg); + nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_INITIALIZED); cand = 0; break; @@ -900,15 +902,7 @@ static void nr_ice_turn_allocated_cb(NR_SOCKET s, int how, void *cb_arg) if (cand) { r_log(NR_LOG_TURN, LOG_WARNING, "ICE-CANDIDATE(%s): nr_turn_allocated_cb failed", cand->label); - cand->state=NR_ICE_CAND_STATE_FAILED; - cand->done_cb(0,0,cand->cb_arg); - - if(cand->u.relayed.srvflx_candidate){ - nr_ice_candidate *cand2=cand->u.relayed.srvflx_candidate; - - cand2->state=NR_ICE_CAND_STATE_FAILED; - cand2->done_cb(0,0,cand2->cb_arg); - } + nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED); } } } @@ -921,6 +915,7 @@ int nr_ice_format_candidate_attribute(nr_ice_candidate *cand, char *attr, int ma char addr[64]; int port; int len; + nr_transport_addr *raddr; assert(!strcmp(nr_ice_candidate_type_names[HOST], "host")); assert(!strcmp(nr_ice_candidate_type_names[RELAYED], "relay")); @@ -929,6 +924,9 @@ int nr_ice_format_candidate_attribute(nr_ice_candidate *cand, char *attr, int ma ABORT(r); if(r=nr_transport_addr_get_port(&cand->addr,&port)) ABORT(r); + /* https://tools.ietf.org/html/rfc6544#section-4.5 */ + if (cand->base.protocol==IPPROTO_TCP && cand->tcp_type==TCP_TYPE_ACTIVE) + port=9; snprintf(attr,maxlen,"candidate:%s %d %s %u %s %d typ %s", cand->foundation, cand->component_id, cand->addr.protocol==IPPROTO_UDP?"UDP":"TCP",cand->priority, addr, port, nr_ctype_name(cand->type)); @@ -936,23 +934,27 @@ int nr_ice_format_candidate_attribute(nr_ice_candidate *cand, char *attr, int ma len=strlen(attr); attr+=len; maxlen-=len; /* raddr, rport */ + raddr = (cand->stream->ctx->flags & + (NR_ICE_CTX_FLAGS_RELAY_ONLY | + NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS)) ? + &cand->addr : &cand->base; + switch(cand->type){ case HOST: break; case SERVER_REFLEXIVE: case PEER_REFLEXIVE: - if(r=nr_transport_addr_get_addrstring(&cand->base,addr,sizeof(addr))) + if(r=nr_transport_addr_get_addrstring(raddr,addr,sizeof(addr))) ABORT(r); - if(r=nr_transport_addr_get_port(&cand->base,&port)) + if(r=nr_transport_addr_get_port(raddr,&port)) ABORT(r); - snprintf(attr,maxlen," raddr %s rport %d",addr,port); break; case RELAYED: // comes from XorMappedAddress via AllocateResponse - if(r=nr_transport_addr_get_addrstring(&cand->base,addr,sizeof(addr))) + if(r=nr_transport_addr_get_addrstring(raddr,addr,sizeof(addr))) ABORT(r); - if(r=nr_transport_addr_get_port(&cand->base,&port)) + if(r=nr_transport_addr_get_port(raddr,&port)) ABORT(r); snprintf(attr,maxlen," raddr %s rport %d",addr,port); diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h index 096ca956d0..16be402761 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h +++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.h @@ -76,6 +76,9 @@ struct nr_ice_candidate_ { struct { nr_stun_client_ctx *stun; void *stun_handle; + /* If this is a srflx that is piggybacking on a relay candidate, this is + * a back pointer to that relay candidate. */ + nr_ice_candidate *relay_candidate; } srvrflx; struct { nr_turn_client_ctx *turn; diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c index edf227b487..2314e16736 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.c @@ -125,7 +125,7 @@ int nr_ice_candidate_pair_create(nr_ice_peer_ctx *pctx, nr_ice_candidate *lcand, /* TODO(ekr@rtfm.com): Do we need to frob this when we change role. Bug 890667 */ pair->stun_client->params.ice_binding_request.control = pctx->controlling? NR_ICE_CONTROLLING:NR_ICE_CONTROLLED; - pair->stun_client->params.ice_use_candidate.priority=t_priority; + pair->stun_client->params.ice_binding_request.priority=t_priority; pair->stun_client->params.ice_binding_request.tiebreaker=pctx->tiebreaker; @@ -247,7 +247,7 @@ static void nr_ice_candidate_pair_stun_cb(NR_SOCKET s, int how, void *cb_arg) else if(!nr_transport_addr_cmp(&pair->local->addr,&pair->stun_client->results.ice_binding_response.mapped_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_SUCCEEDED); } - else{ + else if(pair->stun_client->state == NR_STUN_CLIENT_STATE_DONE) { /* OK, this didn't correspond to a pair on the check list, but it probably matches one of our candidates */ @@ -260,7 +260,12 @@ static void nr_ice_candidate_pair_stun_cb(NR_SOCKET s, int how, void *cb_arg) } /* OK, nothing found, must be peer reflexive */ - if(!cand){ + if(!cand) { + if (pair->pctx->ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) { + /* Any STUN response with a reflexive address in it is unwanted + when we'll send on relay only. Bail since cand is used below. */ + goto done; + } if(r=nr_ice_candidate_create(pair->pctx->ctx, pair->local->component,pair->local->isock,pair->local->osock, PEER_REFLEXIVE,pair->local->tcp_type,0,pair->local->component->component_id,&cand)) @@ -389,32 +394,82 @@ int nr_ice_candidate_pair_start(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair) return(_status); } +static int nr_ice_candidate_copy_for_triggered_check(nr_ice_cand_pair *pair) + { + int r,_status; + nr_ice_cand_pair *copy; + + if(r=nr_ice_candidate_pair_create(pair->pctx, pair->local, pair->remote, ©)) + ABORT(r); + + /* Preserve nomination status */ + copy->peer_nominated= pair->peer_nominated; + copy->nominated = pair->nominated; + + r_log(LOG_ICE,LOG_INFO,"CAND-PAIR(%s): Adding pair to check list and trigger check queue: %s",pair->codeword,pair->as_string); + if(r=nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,copy)) + ABORT(r); + nr_ice_candidate_pair_trigger_check_append(&pair->remote->stream->trigger_check_queue,copy); + + copy->triggered = 1; + nr_ice_candidate_pair_set_state(copy->pctx,copy,NR_ICE_PAIR_STATE_WAITING); + + _status=0; + abort: + return(_status); +} int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair) { int r,_status; - r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): triggered check on %s",pctx->label,pair->codeword,pair->as_string); - - switch(pair->state){ - case NR_ICE_PAIR_STATE_FROZEN: - nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_WAITING); - /* Fall through */ - case NR_ICE_PAIR_STATE_WAITING: - /* Start the checks */ - if(r=nr_ice_candidate_pair_start(pctx,pair)) - ABORT(r); - break; - case NR_ICE_PAIR_STATE_IN_PROGRESS: - if(r=nr_stun_client_force_retransmit(pair->stun_client)) - ABORT(r); - break; - default: - break; + if(pair->state==NR_ICE_PAIR_STATE_CANCELLED) { + r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): Ignoring matching but canceled pair",pctx->label,pair->codeword); + return(0); + } else if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED) { + r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): No new trigger check for succeeded pair",pctx->label,pair->codeword); + return(0); } - /* Activate the media stream if required */ - if(pair->remote->stream->ice_state==NR_ICE_MEDIA_STREAM_CHECKS_FROZEN){ + /* Do not run this logic more than once on a given pair */ + if(!pair->triggered){ + r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): triggered check on %s",pctx->label,pair->codeword,pair->as_string); + + pair->triggered=1; + + switch(pair->state){ + case NR_ICE_PAIR_STATE_FAILED: + /* OK, there was a pair, it's just invalid: According to Section + * 7.2.1.4, we need to resurrect it */ + r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): received STUN check on failed pair, resurrecting: %s",pctx->label,pair->codeword,pair->as_string); + /* fall through */ + case NR_ICE_PAIR_STATE_FROZEN: + nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_WAITING); + /* fall through even further */ + case NR_ICE_PAIR_STATE_WAITING: + /* Append it additionally to the trigger check queue */ + r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): Inserting pair to trigger check queue: %s",pctx->label,pair->codeword,pair->as_string); + nr_ice_candidate_pair_trigger_check_append(&pair->remote->stream->trigger_check_queue,pair); + break; + case NR_ICE_PAIR_STATE_IN_PROGRESS: + /* Instead of trying to maintain two stun contexts on the same pair, + * and handling heterogenous responses and error conditions, we instead + * create a second pair that is identical except that it has the + * |triggered| bit set. We also cancel the original pair, but it can + * still succeed on its own in the special waiting state. */ + if(r=nr_ice_candidate_copy_for_triggered_check(pair)) + ABORT(r); + nr_ice_candidate_pair_cancel(pair->pctx,pair,1); + break; + default: + /* all states are handled - a new/unknown state should not + * automatically enter the start_checks() below */ + assert(0); + break; + } + + /* Ensure that the timers are running to start checks on the topmost entry + * of the triggered check queue. */ if(r=nr_ice_media_stream_start_checks(pair->pctx,pair->remote->stream)) ABORT(r); } @@ -424,12 +479,16 @@ int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_ return(_status); } -int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair) +int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair, int move_to_wait_state) { if(pair->state != NR_ICE_PAIR_STATE_FAILED){ /* If it's already running we need to terminate the stun */ if(pair->state==NR_ICE_PAIR_STATE_IN_PROGRESS){ - nr_stun_client_cancel(pair->stun_client); + if(move_to_wait_state) { + nr_stun_client_wait(pair->stun_client); + } else { + nr_stun_client_cancel(pair->stun_client); + } } nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_CANCELLED); } @@ -501,7 +560,8 @@ int nr_ice_candidate_pair_set_state(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pai pair->state=state; - if(pair->state==NR_ICE_PAIR_STATE_FAILED){ + if(pair->state==NR_ICE_PAIR_STATE_FAILED || + pair->state==NR_ICE_PAIR_STATE_CANCELLED){ if(r=nr_ice_component_failed_pair(pair->remote->component, pair)) ABORT(r); } @@ -519,6 +579,17 @@ int nr_ice_candidate_pair_dump_state(nr_ice_cand_pair *pair, FILE *out) } +int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair) + { + if(pair->triggered_check_queue_entry.tqe_next || + pair->triggered_check_queue_entry.tqe_prev) + return(0); + + TAILQ_INSERT_TAIL(head,pair,triggered_check_queue_entry); + + return(0); + } + int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair) { nr_ice_cand_pair *c1; @@ -526,13 +597,13 @@ int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *p c1=TAILQ_FIRST(head); while(c1){ if(c1->priority < pair->priority){ - TAILQ_INSERT_BEFORE(c1,pair,entry); + TAILQ_INSERT_BEFORE(c1,pair,check_queue_entry); break; } - c1=TAILQ_NEXT(c1,entry); + c1=TAILQ_NEXT(c1,check_queue_entry); } - if(!c1) TAILQ_INSERT_TAIL(head,pair,entry); + if(!c1) TAILQ_INSERT_TAIL(head,pair,check_queue_entry); return(0); } diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h index a995669231..171ded4a03 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h +++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate_pair.h @@ -56,6 +56,8 @@ struct nr_ice_cand_pair_ { on this check */ UCHAR nominated; /* Is this nominated or not */ + UCHAR triggered; /* Ignore further trigger check requests */ + UINT8 priority; /* The priority for this pair */ nr_ice_candidate *local; /* The local candidate */ nr_ice_candidate *remote; /* The remote candidate */ @@ -68,7 +70,8 @@ struct nr_ice_cand_pair_ { void *restart_role_change_cb_timer; void *restart_nominated_cb_timer; - TAILQ_ENTRY(nr_ice_cand_pair_) entry; + TAILQ_ENTRY(nr_ice_cand_pair_) check_queue_entry; /* the check list */ + TAILQ_ENTRY(nr_ice_cand_pair_) triggered_check_queue_entry; /* the trigger check queue */ }; int nr_ice_candidate_pair_create(nr_ice_peer_ctx *pctx, nr_ice_candidate *lcand,nr_ice_candidate *rcand,nr_ice_cand_pair **pairp); @@ -76,10 +79,11 @@ int nr_ice_candidate_pair_unfreeze(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair int nr_ice_candidate_pair_start(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair); int nr_ice_candidate_pair_set_state(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair, int state); int nr_ice_candidate_pair_dump_state(nr_ice_cand_pair *pair, FILE *out); -int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair); +int nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair, int move_to_wait_state); int nr_ice_candidate_pair_select(nr_ice_cand_pair *pair); int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair); int nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair); +int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair); void nr_ice_candidate_pair_restart_stun_nominated_cb(NR_SOCKET s, int how, void *cb_arg); int nr_ice_candidate_pair_destroy(nr_ice_cand_pair **pairp); void nr_ice_candidate_pair_role_change(nr_ice_cand_pair *pair); diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_component.c b/media/mtransport/third_party/nICEr/src/ice/ice_component.c index 2f079ef7e3..2f80596870 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c @@ -226,49 +226,59 @@ static int nr_ice_component_initialize_udp(struct nr_ice_ctx_ *ctx,nr_ice_compon if(r=nr_ice_socket_create(ctx,component,sock,NR_ICE_SOCKET_TYPE_DGRAM,&isock)) ABORT(r); - /* Create one host candidate */ - if(r=nr_ice_candidate_create(ctx,component,isock,sock,HOST,0,0, - component->component_id,&cand)) - ABORT(r); - TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp); - component->candidate_ct++; - cand=0; - - /* And a srvrflx candidate for each STUN server */ - for(j=0;jstun_server_ct;j++){ - if(r=nr_ice_candidate_create(ctx,component, - isock,sock,SERVER_REFLEXIVE,0, - &ctx->stun_servers[j],component->component_id,&cand)) + if (!(ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY)) { + /* Create one host candidate */ + if(r=nr_ice_candidate_create(ctx,component,isock,sock,HOST,0,0, + component->component_id,&cand)) ABORT(r); + TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp); component->candidate_ct++; cand=0; + + /* And a srvrflx candidate for each STUN server */ + for(j=0;jstun_server_ct;j++){ + /* Skip non-UDP */ + if(ctx->stun_servers[j].transport!=IPPROTO_UDP) + continue; + + if(r=nr_ice_candidate_create(ctx,component, + isock,sock,SERVER_REFLEXIVE,0, + &ctx->stun_servers[j],component->component_id,&cand)) + ABORT(r); + TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp); + component->candidate_ct++; + cand=0; + } } #ifdef USE_TURN - /* And both a srvrflx and relayed candidate for each TURN server */ + /* And both a srvrflx and relayed candidate for each TURN server (unless + we're in relay-only mode, in which case just the relayed one) */ for(j=0;jturn_server_ct;j++){ nr_socket *turn_sock; - nr_ice_candidate *srvflx_cand; + nr_ice_candidate *srvflx_cand=0; /* Skip non-UDP */ if (ctx->turn_servers[j].turn_server.transport != IPPROTO_UDP) continue; - /* srvrflx */ - if(r=nr_ice_candidate_create(ctx,component, - isock,sock,SERVER_REFLEXIVE,0, - &ctx->turn_servers[j].turn_server,component->component_id,&cand)) - ABORT(r); - cand->state=NR_ICE_CAND_STATE_INITIALIZING; /* Don't start */ - cand->done_cb=nr_ice_gather_finished_cb; - cand->cb_arg=cand; - - TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp); - component->candidate_ct++; - srvflx_cand=cand; + if (!(ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY)) { + /* srvrflx */ + if(r=nr_ice_candidate_create(ctx,component, + isock,sock,SERVER_REFLEXIVE,0, + &ctx->turn_servers[j].turn_server,component->component_id,&cand)) + ABORT(r); + cand->state=NR_ICE_CAND_STATE_INITIALIZING; /* Don't start */ + cand->done_cb=nr_ice_gather_finished_cb; + cand->cb_arg=cand; + TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp); + component->candidate_ct++; + srvflx_cand=cand; + cand=0; + } /* relayed*/ if(r=nr_socket_turn_create(sock, &turn_sock)) ABORT(r); @@ -276,7 +286,10 @@ static int nr_ice_component_initialize_udp(struct nr_ice_ctx_ *ctx,nr_ice_compon isock,turn_sock,RELAYED,0, &ctx->turn_servers[j].turn_server,component->component_id,&cand)) ABORT(r); - cand->u.relayed.srvflx_candidate=srvflx_cand; + if (srvflx_cand) { + cand->u.relayed.srvflx_candidate=srvflx_cand; + srvflx_cand->u.srvrflx.relay_candidate=cand; + } cand->u.relayed.server=&ctx->turn_servers[j]; TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp); component->candidate_ct++; @@ -340,7 +353,7 @@ static int nr_ice_component_create_tcp_host_candidate(struct nr_ice_ctx_ *ctx, /* It would be better to stop trying if there is error other than port already used, but it'd require significant work to support this. */ - r=nr_socket_multi_tcp_create(ctx,&addr,tcp_type,so_sock_ct,1,NR_STUN_MAX_MESSAGE_SIZE,&nrsock); + r=nr_socket_multi_tcp_create(ctx,&addr,tcp_type,so_sock_ct,NR_STUN_MAX_MESSAGE_SIZE,&nrsock); } while(r); @@ -404,6 +417,9 @@ static int nr_ice_component_initialize_tcp(struct nr_ice_ctx_ *ctx,nr_ice_compon if (r != R_NOT_FOUND) ABORT(r); } + if (ctx->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) { + ice_tcp_disabled = 1; + } for(i=0;ilabel,r); /* And the TCP candidates */ if (r=nr_ice_component_initialize_tcp(ctx, component, addrs, addr_ct, lufrag, &pwd)) - ABORT(r); + r_log(LOG_ICE,LOG_INFO,"ICE(%s): failed to create TCP candidates with error %d",ctx->label,r); /* count the candidates that will be initialized */ cand=TAILQ_FIRST(&component->candidates); if(!cand){ - r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): couldn't create any valid candidates",ctx->label); + r_log(LOG_ICE,LOG_ERR,"ICE(%s): couldn't create any valid candidates",ctx->label); ABORT(R_NOT_FOUND); } @@ -611,12 +627,7 @@ int nr_ice_component_initialize(struct nr_ice_ctx_ *ctx,nr_ice_component *compon cand=TAILQ_FIRST(&component->candidates); while(cand){ if(cand->state!=NR_ICE_CAND_STATE_INITIALIZING){ - if(r=nr_ice_candidate_initialize(cand,nr_ice_gather_finished_cb,cand)){ - if(r!=R_WOULDBLOCK){ - ctx->uninitialized_candidates--; - cand->state=NR_ICE_CAND_STATE_FAILED; - } - } + nr_ice_candidate_initialize(cand,nr_ice_gather_finished_cb,cand); } cand=TAILQ_NEXT(cand,entry_comp); } @@ -654,8 +665,9 @@ int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *co !nr_transport_addr_cmp(&c1->addr,&c2->addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){ if((c1->type == c2->type) || - (c1->type==HOST && c2->type == SERVER_REFLEXIVE) || - (c2->type==HOST && c1->type == SERVER_REFLEXIVE)){ + (!(ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) && + ((c1->type==HOST && c2->type == SERVER_REFLEXIVE) || + (c2->type==HOST && c1->type == SERVER_REFLEXIVE)))){ /* These are redundant. Remove the lower pri one, or if pairing has @@ -692,6 +704,57 @@ int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *co return 0; } +static int nr_ice_component_pair_matches_check(nr_ice_component *comp, nr_ice_cand_pair *pair, nr_transport_addr *local_addr, nr_stun_server_request *req) + { + if(pair->remote->component->component_id!=comp->component_id) + return(0); + + if(nr_transport_addr_cmp(&pair->local->base,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + return(0); + + if(nr_transport_addr_cmp(&pair->remote->addr,&req->src_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + return(0); + + return(1); + } + +static int nr_ice_component_handle_triggered_check(nr_ice_component *comp, nr_ice_cand_pair *pair, nr_stun_server_request *req, int *error) + { + nr_stun_message *sreq=req->request; + int r=0,_status; + + if(nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_USE_CANDIDATE,0)){ + if(comp->stream->pctx->controlling){ + r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND_PAIR(%s): Peer sent USE-CANDIDATE but is controlled",comp->stream->pctx->label, pair->codeword); + } + else{ + /* If this is the first time we've noticed this is nominated...*/ + pair->peer_nominated=1; + + if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED && !pair->nominated){ + pair->nominated=1; + + if(r=nr_ice_component_nominated_pair(pair->remote->component, pair)) { + *error=(r==R_NO_MEMORY)?500:400; + ABORT(r); + } + } + } + } + + /* Note: the RFC says to trigger first and then nominate. But in that case + * the canceled trigger pair would get nominated and the cloned trigger pair + * would not get the nomination status cloned with it.*/ + if(r=nr_ice_candidate_pair_do_triggered_check(comp->stream->pctx,pair)) { + *error=(r==R_NO_MEMORY)?500:400; + ABORT(r); + } + + _status=0; + abort: + return(r); + } + /* Section 7.2.1 */ static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_transport_addr *local_addr, nr_stun_server_request *req, int *error) { @@ -699,12 +762,8 @@ static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_tr nr_ice_candidate *pcand=0; nr_stun_message *sreq=req->request; nr_stun_message_attribute *attr; - int component_id_matched; - int local_addr_matched; - int remote_addr_matched; - nr_ice_cand_pair *found_invalid=0; int r=0,_status; - int new_pcand_created=0; + int found_valid=0; r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): received request from %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,req->src_addr.as_string); @@ -736,7 +795,7 @@ static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_tr /* OK, there is a conflict. Who's right? */ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): role conflict, both controlled",comp->stream->pctx->label); - if(attr->u.ice_controlling < comp->stream->pctx->tiebreaker){ + if(attr->u.ice_controlled < comp->stream->pctx->tiebreaker){ /* Update the peer ctx. This will propagate to all candidate pairs in the context. */ nr_ice_peer_ctx_switch_controlling_role(comp->stream->pctx); @@ -755,156 +814,84 @@ static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_tr pair=TAILQ_FIRST(&comp->stream->check_list); while(pair){ - component_id_matched = 0; - local_addr_matched = 0; - remote_addr_matched = 0; - - if(pair->remote->component->component_id!=comp->component_id) - goto next_pair; - component_id_matched = 1; - - if(nr_transport_addr_cmp(&pair->local->base,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) - goto next_pair; - local_addr_matched=1; - - - if(nr_transport_addr_cmp(&pair->remote->addr,&req->src_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) - goto next_pair; - remote_addr_matched = 1; - - if(pair->state==NR_ICE_PAIR_STATE_FAILED){ - found_invalid=pair; - goto next_pair; - } - - if (local_addr_matched && remote_addr_matched){ + /* Since triggered checks create duplicate pairs (in this implementation) + * we are willing to handle multiple matches here. */ + if(nr_ice_component_pair_matches_check(comp, pair, local_addr, req)){ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): Found a matching pair for received check: %s",comp->stream->pctx->label,pair->codeword,pair->as_string); - break; /* OK, this is a known pair */ - } - - next_pair: - pair=TAILQ_NEXT(pair,entry); - } - - if(!pair){ - if(!found_invalid){ - /* First find our local component candidate */ - nr_ice_candidate *cand; - - r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): no matching pair",comp->stream->pctx->label); - cand=TAILQ_FIRST(&comp->local_component->candidates); - while(cand){ - if(!nr_transport_addr_cmp(&cand->addr,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) - break; - - cand=TAILQ_NEXT(cand,entry_comp); - } - - /* Well, this really shouldn't happen, but it's an error from the - other side, so we just throw an error and keep going */ - if(!cand){ - r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): stun request to unknown local address %s, discarding",comp->stream->pctx->label,local_addr->as_string); - - *error=400; - ABORT(R_NOT_FOUND); - } - - /* Try to find matching peer active tcp candidate */ - pcand=TAILQ_FIRST(&comp->candidates); - while(pcand){ - if(pcand->tcp_type == TCP_TYPE_ACTIVE) { - if(!nr_transport_addr_cmp(&pcand->addr,&req->src_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) - break; - } - - pcand=TAILQ_NEXT(pcand,entry_comp); - } - - if (!pcand){ - /* We now need to make a peer reflexive */ - if(r=nr_ice_peer_peer_rflx_candidate_create(comp->stream->pctx->ctx,"prflx",comp,&req->src_addr,&pcand)) { - *error=(r==R_NO_MEMORY)?500:400; - ABORT(r); - } - new_pcand_created=1; - if(!nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_PRIORITY,&attr)){ - r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): Rejecting stun request without priority",comp->stream->pctx->label); - *error=487; - ABORT(R_BAD_DATA); - } - pcand->priority=attr->u.priority; - } - - pcand->state=NR_ICE_CAND_PEER_CANDIDATE_PAIRED; - - if(r=nr_ice_candidate_pair_create(comp->stream->pctx,cand,pcand, - &pair)) { - *error=(r==R_NO_MEMORY)?500:400; + if(r=nr_ice_component_handle_triggered_check(comp, pair, req, error)) ABORT(r); - } - nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FROZEN); - - if(r=nr_ice_component_insert_pair(comp,pair)) { - *error=(r==R_NO_MEMORY)?500:400; - ABORT(r); - } - - /* Do this last, since any call to ABORT will destroy pcand */ - if (new_pcand_created){ - TAILQ_INSERT_TAIL(&comp->candidates,pcand,entry_comp); - pcand=0; - } - } - else{ - /* OK, there was a pair, it's just invalid: According to Section - 7.2.1.4, we need to resurrect it - */ - if(found_invalid->state == NR_ICE_PAIR_STATE_FAILED){ - pair=found_invalid; - - r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): received STUN check on invalid pair, resurrecting: %s",comp->stream->pctx->label,pair->codeword,pair->as_string); - nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_WAITING); - } - else{ - /* This shouldn't happen */ - r_log(LOG_ICE,LOG_ERR,"ICE-PEER(%s)/CAND-PAIR(%s): received STUN check on invalid pair that was not in state FAILED; this should not happen: %s",comp->stream->pctx->label,pair->codeword,pair->as_string); - *error=500; - ABORT(R_BAD_DATA); - } + ++found_valid; } + pair=TAILQ_NEXT(pair,check_queue_entry); } - /* OK, we've got a pair to work with. Turn it on */ - assert(pair); - if(nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_USE_CANDIDATE,0)){ - if(comp->stream->pctx->controlling){ - r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND_PAIR(%s): Peer sent USE-CANDIDATE but is controlled",comp->stream->pctx->label, pair->codeword); + if(!found_valid){ + /* There were no matching pairs, so we need to create a new peer + * reflexive candidate pair. */ + + if(!nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_PRIORITY,&attr)){ + r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): Rejecting stun request without priority",comp->stream->pctx->label); + *error=400; + ABORT(R_BAD_DATA); } - else{ - /* If this is the first time we've noticed this is nominated...*/ - pair->peer_nominated=1; - if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED && !pair->nominated){ - pair->nominated=1; + /* Find our local component candidate */ + nr_ice_candidate *cand; - if(r=nr_ice_component_nominated_pair(pair->remote->component, pair)) { - *error=(r==R_NO_MEMORY)?500:400; - ABORT(r); - } - } + r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): no matching pair",comp->stream->pctx->label); + cand=TAILQ_FIRST(&comp->local_component->candidates); + while(cand){ + if(!nr_transport_addr_cmp(&cand->addr,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) + break; + + cand=TAILQ_NEXT(cand,entry_comp); } - } - if(r=nr_ice_candidate_pair_do_triggered_check(comp->stream->pctx,pair)) { - *error=(r==R_NO_MEMORY)?500:400; - ABORT(r); + /* Well, this really shouldn't happen, but it's an error from the + other side, so we just throw an error and keep going */ + if(!cand){ + r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): stun request to unknown local address %s, discarding",comp->stream->pctx->label,local_addr->as_string); + + *error=400; + ABORT(R_NOT_FOUND); + } + + /* Now make a peer reflexive (remote) candidate */ + if(r=nr_ice_peer_peer_rflx_candidate_create(comp->stream->pctx->ctx,"prflx",comp,&req->src_addr,&pcand)) { + *error=(r==R_NO_MEMORY)?500:400; + ABORT(r); + } + pcand->priority=attr->u.priority; + pcand->state=NR_ICE_CAND_PEER_CANDIDATE_PAIRED; + + /* Finally, create the candidate pair, insert into the check list, and + * apply the incoming check to it. */ + if(r=nr_ice_candidate_pair_create(comp->stream->pctx,cand,pcand, + &pair)) { + *error=(r==R_NO_MEMORY)?500:400; + ABORT(r); + } + + nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FROZEN); + if(r=nr_ice_component_insert_pair(comp,pair)) { + *error=(r==R_NO_MEMORY)?500:400; + nr_ice_candidate_pair_destroy(&pair); + ABORT(r); + } + + /* Do this last, since any call to ABORT will destroy pcand */ + TAILQ_INSERT_TAIL(&comp->candidates,pcand,entry_comp); + pcand=0; + + /* Finally start the trigger check if needed */ + if(r=nr_ice_component_handle_triggered_check(comp, pair, req, error)) + ABORT(r); } _status=0; abort: if(_status){ - if (new_pcand_created) - nr_ice_candidate_destroy(&pcand); + nr_ice_candidate_destroy(&pcand); assert(*error != 0); if(r!=R_NO_MEMORY) assert(*error != 500); } @@ -961,6 +948,46 @@ int nr_ice_component_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ice_c return(_status); } +int nr_ice_component_can_candidate_tcptype_pair(nr_socket_tcp_type left, nr_socket_tcp_type right) + { + if (left && !right) + return(0); + if (!left && right) + return(0); + if (left == TCP_TYPE_ACTIVE && right != TCP_TYPE_PASSIVE) + return(0); + if (left == TCP_TYPE_SO && right != TCP_TYPE_SO) + return(0); + if (left == TCP_TYPE_PASSIVE) + return(0); + + return(1); + } + +/* local vs. remote matters here because we allow private -> public pairing, + * but discourage public -> private pairing. */ +int nr_ice_component_can_candidate_addr_pair(nr_transport_addr *local, nr_transport_addr *remote) + { + int remote_range; + + if(local->ip_version != remote->ip_version) + return(0); + if(nr_transport_addr_is_link_local(local) != + nr_transport_addr_is_link_local(remote)) + return(0); + /* This prevents our ice_unittest (or broken clients) from pairing a + * loopback with a host candidate. */ + if(nr_transport_addr_is_loopback(local) != + nr_transport_addr_is_loopback(remote)) + return(0); + remote_range = nr_transport_addr_get_private_addr_range(remote); + if(remote_range && (nr_transport_addr_get_private_addr_range(local) != + remote_range)) + return(0); + + return(1); + } + int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, nr_ice_candidate *lcand, int pair_all_remote) { int r, _status; @@ -988,17 +1015,9 @@ int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pco } TAILQ_FOREACH(pcand, &pcomp->candidates, entry_comp){ - if (lcand->tcp_type && !pcand->tcp_type) + if(!nr_ice_component_can_candidate_addr_pair(&lcand->addr, &pcand->addr)) continue; - if (!lcand->tcp_type && pcand->tcp_type) - continue; - if (lcand->tcp_type == TCP_TYPE_ACTIVE && pcand->tcp_type != TCP_TYPE_PASSIVE) - continue; - if (lcand->tcp_type == TCP_TYPE_SO && pcand->tcp_type != TCP_TYPE_SO) - continue; - if (lcand->tcp_type == TCP_TYPE_PASSIVE) - continue; - if(pcand->addr.ip_version != lcand->addr.ip_version) + if(!nr_ice_component_can_candidate_tcptype_pair(lcand->tcp_type, pcand->tcp_type)) continue; /* @@ -1111,15 +1130,11 @@ static int nr_ice_component_stun_server_default_cb(void *cb_arg,nr_stun_server_c int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair) { int r,_status; - int fire_cb=0; nr_ice_cand_pair *p2; - if(!comp->nominated) - fire_cb=1; - /* Are we changing what the nominated pair is? */ if(comp->nominated){ - if(comp->nominated->priority > pair->priority) + if(comp->nominated->priority >= pair->priority) return(0); r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): replacing pair %s with CAND-PAIR(%s)",comp->stream->pctx->label,comp->stream->label,comp->component_id,comp->nominated->codeword,comp->nominated->as_string,pair->codeword); } @@ -1133,19 +1148,33 @@ int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pa r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling all pairs but %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,pair->codeword,pair->as_string); /* Cancel checks in WAITING and FROZEN per ICE S 8.1.2 */ + p2=TAILQ_FIRST(&comp->stream->trigger_check_queue); + while(p2){ + if((p2 != pair) && + (p2->remote->component->component_id == comp->component_id)) { + assert(p2->state == NR_ICE_PAIR_STATE_WAITING || + p2->state == NR_ICE_PAIR_STATE_CANCELLED); + r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling FROZEN/WAITING pair %s in trigger check queue because CAND-PAIR(%s) was nominated.",comp->stream->pctx->label,comp->stream->label,comp->component_id,p2->codeword,p2->as_string,pair->codeword); + + if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2,0)) + ABORT(r); + } + + p2=TAILQ_NEXT(p2,triggered_check_queue_entry); + } p2=TAILQ_FIRST(&comp->stream->check_list); while(p2){ if((p2 != pair) && (p2->remote->component->component_id == comp->component_id) && ((p2->state == NR_ICE_PAIR_STATE_FROZEN) || - (p2->state == NR_ICE_PAIR_STATE_WAITING))) { + (p2->state == NR_ICE_PAIR_STATE_WAITING))) { r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling FROZEN/WAITING pair %s because CAND-PAIR(%s) was nominated.",comp->stream->pctx->label,comp->stream->label,comp->component_id,p2->codeword,p2->as_string,pair->codeword); - if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2)) + if(r=nr_ice_candidate_pair_cancel(pair->pctx,p2,0)) ABORT(r); } - p2=TAILQ_NEXT(p2,entry); + p2=TAILQ_NEXT(p2,check_queue_entry); } r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): cancelling done",comp->stream->pctx->label,comp->stream->label,comp->component_id); @@ -1180,7 +1209,7 @@ static int nr_ice_component_have_all_pairs_failed(nr_ice_component *comp) } } - p2=TAILQ_NEXT(p2,entry); + p2=TAILQ_NEXT(p2,check_queue_entry); } return(1); @@ -1220,7 +1249,7 @@ int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp) if (comp->component_id == pair->local->component_id) ct++; - pair=TAILQ_NEXT(pair,entry); + pair=TAILQ_NEXT(pair,check_queue_entry); } /* Make and fill the array */ @@ -1233,7 +1262,7 @@ int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp) if (comp->component_id == pair->local->component_id) pairs[ct++]=pair; - pair=TAILQ_NEXT(pair,entry); + pair=TAILQ_NEXT(pair,check_queue_entry); } if (pctx->handler) { @@ -1355,7 +1384,7 @@ int nr_ice_component_get_default_candidate(nr_ice_component *comp, nr_ice_candid */ cand=TAILQ_FIRST(&comp->candidates); while(cand){ - if (cand->state == NR_ICE_CAND_STATE_INITIALIZED && + if (!nr_ice_ctx_hide_candidate(comp->ctx, cand) && cand->addr.ip_version == ip_version) { if (!best_cand) { best_cand = cand; diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c index 611c24474f..f0bf11e9af 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.c @@ -1,3 +1,4 @@ + /* Copyright (c) 2007, Adobe Systems, Incorporated All rights reserved. @@ -315,7 +316,7 @@ int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out) } #endif /* USE_TURN */ -#define MAXADDRS 100 // Ridiculously high +#define MAXADDRS 100 /* Ridiculously high */ int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp) { nr_ice_ctx *ctx=0; @@ -399,6 +400,14 @@ int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp) if (r=nr_socket_factory_create_int(NULL, &default_socket_factory_vtbl, &ctx->socket_factory)) ABORT(r); + if ((r=NR_reg_get_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, ctx->force_net_interface, sizeof(ctx->force_net_interface)))) { + if (r == R_NOT_FOUND) { + ctx->force_net_interface[0] = 0; + } else { + ABORT(r); + } + } + STAILQ_INIT(&ctx->streams); STAILQ_INIT(&ctx->sockets); STAILQ_INIT(&ctx->foundations); @@ -491,7 +500,7 @@ void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg) ctx->uninitialized_candidates--; - // Avoid the need for yet another initialization function + /* Avoid the need for yet another initialization function */ if (cand->state == NR_ICE_CAND_STATE_INITIALIZING && cand->type == HOST) cand->state = NR_ICE_CAND_STATE_INITIALIZED; @@ -505,7 +514,8 @@ void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg) /* If we are initialized, the candidate wasn't pruned, and we have a trickle ICE callback fire the callback */ - if (ctx->trickle_cb && !was_pruned) { + if (ctx->trickle_cb && !was_pruned && + !nr_ice_ctx_hide_candidate(ctx, cand)) { ctx->trickle_cb(ctx->trickle_cb_arg, ctx, cand->stream, cand->component_id, cand); if (nr_ice_ctx_pair_new_trickle_candidates(ctx, cand)) { @@ -550,21 +560,128 @@ static int nr_ice_ctx_pair_new_trickle_candidates(nr_ice_ctx *ctx, nr_ice_candid return(_status); } +/* Get the default address by doing a connect to a known public IP address, + in this case Google public DNS: -int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg) + IPv4: 8.8.8.8 + IPv6: 2001:4860:4860::8888 + + Then we can do getsockname to get the address. No packets get sent + since this is UDP. It's just a way to get the address. +*/ +static int nr_ice_get_default_address(nr_ice_ctx *ctx, int ip_version, nr_transport_addr* addrp) { int r,_status; - nr_ice_media_stream *stream; - nr_local_addr addrs[MAXADDRS]; + nr_transport_addr addr; + nr_transport_addr remote_addr; + nr_socket *sock=0; + + switch(ip_version) { + case NR_IPV4: + if ((r=nr_str_port_to_transport_addr("0.0.0.0", 0, IPPROTO_UDP, &addr))) + ABORT(r); + if ((r=nr_str_port_to_transport_addr("8.8.8.8", 53, IPPROTO_UDP, &remote_addr))) + ABORT(r); + break; + case NR_IPV6: + if ((r=nr_str_port_to_transport_addr("::0", 0, IPPROTO_UDP, &addr))) + ABORT(r); + if ((r=nr_str_port_to_transport_addr("2001:4860:4860::8888", 53, IPPROTO_UDP, &remote_addr))) + ABORT(r); + break; + default: + assert(0); + ABORT(R_INTERNAL); + } + + if ((r=nr_socket_factory_create_socket(ctx->socket_factory, &addr, &sock))) + ABORT(r); + if ((r=nr_socket_connect(sock, &remote_addr))) + ABORT(r); + if ((r=nr_socket_getaddr(sock, addrp))) + ABORT(r); + + _status=0; + abort: + nr_socket_destroy(&sock); + return(_status); + } + +static int nr_ice_get_default_local_address(nr_ice_ctx *ctx, int ip_version, nr_local_addr* addrs, int addr_ct, nr_local_addr *addrp) + { + int r,_status; + nr_transport_addr default_addr; + int i; + + if ((r=nr_ice_get_default_address(ctx, ip_version, &default_addr))) + ABORT(r); + + for(i=0; ilocal_addrs) { /* First, gather all the local addresses we have */ - if(r=nr_stun_find_local_addresses(addrs,MAXADDRS,&addr_ct)) { + if((r=nr_stun_find_local_addresses(local_addrs,MAXADDRS,&addr_ct))) { r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to find local addresses",ctx->label); ABORT(r); } + if (ctx->force_net_interface[0]) { + /* Limit us to only addresses on a single interface */ + int force_addr_ct = 0; + for(i=0;iforce_net_interface)) { + // copy it down in the array, if needed + if (i != force_addr_ct) { + if (r=nr_local_addr_copy(&local_addrs[force_addr_ct], &local_addrs[i])) { + ABORT(r); + } + } + force_addr_ct++; + } + } + addr_ct = force_addr_ct; + } + + if (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) { + /* Get just the default IPv4 and IPv6 addrs */ + if(!nr_ice_get_default_local_address(ctx, NR_IPV4, local_addrs, addr_ct, + &default_addrs[default_addr_ct])) { + ++default_addr_ct; + } + if(!nr_ice_get_default_local_address(ctx, NR_IPV6, local_addrs, addr_ct, + &default_addrs[default_addr_ct])) { + ++default_addr_ct; + } + addrs = default_addrs; + addr_ct = default_addr_ct; + } + else { + addrs = local_addrs; + } + /* Sort interfaces by preference */ if(ctx->interface_prioritizer) { for(i=0;istreams)) { r_log(LOG_ICE,LOG_ERR,"ICE(%s): Missing streams to initialize",ctx->label); ABORT(R_BAD_ARGS); @@ -696,8 +826,6 @@ static int nr_ice_random_string(char *str, int len) if(needed>sizeof(bytes)) ABORT(R_BAD_ARGS); - //memset(bytes,0,needed); - if(r=nr_crypto_random_bytes(bytes,needed)) ABORT(r); @@ -811,3 +939,17 @@ int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, v return 0; } + +int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand) + { + if (cand->state != NR_ICE_CAND_STATE_INITIALIZED) { + return 1; + } + + if (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS) { + if (cand->type == HOST) + return 1; + } + + return 0; + } diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h index e1db771778..76d7587aa5 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h +++ b/media/mtransport/third_party/nICEr/src/ice/ice_ctx.h @@ -150,6 +150,8 @@ struct nr_ice_ctx_ { nr_ice_trickle_candidate_cb trickle_cb; void *trickle_cb_arg; + + char force_net_interface[MAXIFNAME]; }; int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp); @@ -157,6 +159,8 @@ int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp); #define NR_ICE_CTX_FLAGS_ANSWERER (1<<1) #define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION (1<<2) #define NR_ICE_CTX_FLAGS_LITE (1<<3) +#define NR_ICE_CTX_FLAGS_RELAY_ONLY (1<<4) +#define NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS (1<<5) int nr_ice_ctx_destroy(nr_ice_ctx **ctxp); int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg); @@ -176,6 +180,7 @@ int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritiz int nr_ice_ctx_set_turn_tcp_socket_wrapper(nr_ice_ctx *ctx, nr_socket_wrapper_factory *wrapper); void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory); int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg); +int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand); #define NR_ICE_MAX_ATTRIBUTE_SIZE 256 diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c index 6bb8f994b7..8c808d13a2 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c +++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.c @@ -72,6 +72,7 @@ int nr_ice_media_stream_create(nr_ice_ctx *ctx,char *label,int components, nr_ic } TAILQ_INIT(&stream->check_list); + TAILQ_INIT(&stream->trigger_check_queue); stream->component_ct=components; stream->ice_state = NR_ICE_MEDIA_STREAM_UNPAIRED; @@ -101,8 +102,10 @@ int nr_ice_media_stream_destroy(nr_ice_media_stream **streamp) nr_ice_component_destroy(&c1); } - TAILQ_FOREACH_SAFE(p1, &stream->check_list, entry, p2){ - TAILQ_REMOVE(&stream->check_list,p1,entry); + /* Note: all the entries from the trigger check queue are held in here as + * well, so we only clean up the super set. */ + TAILQ_FOREACH_SAFE(p1, &stream->check_list, check_queue_entry, p2){ + TAILQ_REMOVE(&stream->check_list,p1,check_queue_entry); nr_ice_candidate_pair_destroy(&p1); } @@ -140,6 +143,7 @@ int nr_ice_media_stream_initialize(nr_ice_ctx *ctx, nr_ice_media_stream *stream) return(_status); } + int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attrsp, int *attrctp) { int attrct=0; @@ -157,7 +161,7 @@ int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attr if (comp->state != NR_ICE_COMPONENT_DISABLED) { cand = TAILQ_FIRST(&comp->candidates); while(cand){ - if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) { + if (!nr_ice_ctx_hide_candidate(stream->ctx, cand)) { ++attrct; } @@ -189,7 +193,7 @@ int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attr cand=TAILQ_FIRST(&comp->candidates); while(cand){ - if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) { + if (!nr_ice_ctx_hide_candidate(stream->ctx, cand)) { assert(index < attrct); if (index >= attrct) @@ -307,46 +311,66 @@ int nr_ice_media_stream_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ic return(_status); } -/* S 5.8 -- run the highest priority WAITING pair or if not available - FROZEN pair */ +/* S 5.8 -- run the first pair from the triggered check queue (even after + * checks have completed S 8.1.2) or run the highest priority WAITING pair or + * if not available FROZEN pair from the check queue */ static void nr_ice_media_stream_check_timer_cb(NR_SOCKET s, int h, void *cb_arg) { int r,_status; nr_ice_media_stream *stream=cb_arg; - nr_ice_cand_pair *pair; + nr_ice_cand_pair *pair = 0; int timer_val; + int timer_multiplier; - assert(stream->pctx->active_streams!=0); - - timer_val=stream->pctx->ctx->Ta*stream->pctx->active_streams; - + timer_multiplier=stream->pctx->active_streams; + /* Once the checks are completed we don't have an active streams any more, + * but we still need to process triggered checks. */ if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) { - r_log(LOG_ICE,LOG_ERR,"ICE-PEER(%s): (bug) bogus state for stream %s",stream->pctx->label,stream->label); + assert(timer_multiplier==0); + timer_multiplier=1; } - assert(stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED); + + assert(timer_multiplier!=0); + timer_val=stream->pctx->ctx->Ta*timer_multiplier; r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): check timer expired for media stream %s",stream->pctx->label,stream->label); stream->timer=0; - /* Find the highest priority WAITING check and move it to RUNNING */ - pair=TAILQ_FIRST(&stream->check_list); + /* The trigger check queue has the highest priority */ + pair=TAILQ_FIRST(&stream->trigger_check_queue); while(pair){ - if(pair->state==NR_ICE_PAIR_STATE_WAITING) + if(pair->state==NR_ICE_PAIR_STATE_WAITING){ + /* Remove the pair from he trigger check queue */ + r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): Removing pair from trigger check queue %s",stream->pctx->label,pair->as_string); + TAILQ_REMOVE(&stream->trigger_check_queue,pair,triggered_check_queue_entry); break; - pair=TAILQ_NEXT(pair,entry); + } + pair=TAILQ_NEXT(pair,triggered_check_queue_entry); } - /* Hmmm... No WAITING. Let's look for FROZEN */ - if(!pair){ - pair=TAILQ_FIRST(&stream->check_list); - - while(pair){ - if(pair->state==NR_ICE_PAIR_STATE_FROZEN){ - if(r=nr_ice_candidate_pair_unfreeze(stream->pctx,pair)) - ABORT(r); - break; + if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) { + if(!pair){ + /* Find the highest priority WAITING check and move it to RUNNING */ + pair=TAILQ_FIRST(&stream->check_list); + while(pair){ + if(pair->state==NR_ICE_PAIR_STATE_WAITING) + break; + pair=TAILQ_NEXT(pair,check_queue_entry); + } + } + + /* Hmmm... No WAITING. Let's look for FROZEN */ + if(!pair){ + pair=TAILQ_FIRST(&stream->check_list); + + while(pair){ + if(pair->state==NR_ICE_PAIR_STATE_FROZEN){ + if(r=nr_ice_candidate_pair_unfreeze(stream->pctx,pair)) + ABORT(r); + break; + } + pair=TAILQ_NEXT(pair,check_queue_entry); } - pair=TAILQ_NEXT(pair,entry); } } @@ -363,20 +387,25 @@ static void nr_ice_media_stream_check_timer_cb(NR_SOCKET s, int h, void *cb_arg) return; } - /* Start checks for this media stream (aka check list) */ int nr_ice_media_stream_start_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream) { int r,_status; - /* Don't start the check timer if the stream is done (failed/completed) */ - if (stream->ice_state > NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE) { + /* Don't start the check timer if the stream is failed */ + if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_FAILED) { assert(0); ABORT(R_INTERNAL); } - if(r=nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE)) - ABORT(r); + /* Even if the stream is completed already remote can still create a new + * triggered check request which needs to fire, but not change our stream + * state. */ + if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_COMPLETED) { + if(r=nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE)) { + ABORT(r); + } + } if (!stream->timer) { r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/ICE-STREAM(%s): Starting check timer for stream.",pctx->label,stream->label); @@ -418,7 +447,7 @@ int nr_ice_media_stream_unfreeze_pairs(nr_ice_peer_ctx *pctx, nr_ice_media_strea } /* Already exists... fall through */ - pair=TAILQ_NEXT(pair,entry); + pair=TAILQ_NEXT(pair,check_queue_entry); } _status=0; @@ -441,7 +470,7 @@ static int nr_ice_media_stream_unfreeze_pairs_match(nr_ice_media_stream *stream, ABORT(r); unfroze++; } - pair=TAILQ_NEXT(pair,entry); + pair=TAILQ_NEXT(pair,check_queue_entry); } if(!unfroze) @@ -531,7 +560,7 @@ int nr_ice_media_stream_dump_state(nr_ice_peer_ctx *pctx, nr_ice_media_stream *s while(pair){ nr_ice_candidate_pair_dump_state(pair,out); - pair=TAILQ_NEXT(pair,entry); + pair=TAILQ_NEXT(pair,check_queue_entry); } return(0); @@ -624,10 +653,10 @@ int nr_ice_media_stream_component_failed(nr_ice_media_stream *stream,nr_ice_comp /* OK, we need to cancel off everything on this component */ p2=TAILQ_FIRST(&stream->check_list); while(p2){ - if(r=nr_ice_candidate_pair_cancel(p2->pctx,p2)) + if(r=nr_ice_candidate_pair_cancel(p2->pctx,p2,0)) ABORT(r); - p2=TAILQ_NEXT(p2,entry); + p2=TAILQ_NEXT(p2,check_queue_entry); } /* Cancel our timer */ @@ -822,7 +851,7 @@ int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice ABORT(r); _status=0; - abort: + abort: return(_status); } @@ -845,7 +874,7 @@ int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int compo comp->state = NR_ICE_COMPONENT_DISABLED; _status=0; - abort: + abort: return(_status); } @@ -857,7 +886,7 @@ void nr_ice_media_stream_role_change(nr_ice_media_stream *stream) pair=TAILQ_FIRST(&stream->check_list); while(pair){ nr_ice_candidate_pair_role_change(pair); - pair=TAILQ_NEXT(pair,entry); + pair=TAILQ_NEXT(pair,check_queue_entry); } } diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h index 28f6ccd30a..17c9386c80 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h +++ b/media/mtransport/third_party/nICEr/src/ice/ice_media_stream.h @@ -63,6 +63,7 @@ struct nr_ice_media_stream_ { #define NR_ICE_MEDIA_STREAM_CHECKS_FAILED 5 nr_ice_cand_pair_head check_list; + nr_ice_cand_pair_head trigger_check_queue; void *timer; /* Check list periodic timer */ /* nr_ice_cand_pair_head valid_list; */ diff --git a/media/mtransport/third_party/nICEr/src/ice/ice_reg.h b/media/mtransport/third_party/nICEr/src/ice/ice_reg.h index 4a6429549a..dc0f74172d 100644 --- a/media/mtransport/third_party/nICEr/src/ice/ice_reg.h +++ b/media/mtransport/third_party/nICEr/src/ice/ice_reg.h @@ -70,6 +70,8 @@ extern "C" { #define NR_ICE_REG_KEEPALIVE_TIMER "ice.keepalive_timer" #define NR_ICE_REG_TRICKLE_GRACE_PERIOD "ice.trickle_grace_period" +#define NR_ICE_REG_PREF_FORCE_INTERFACE_NAME "ice.forced_interface_name" + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/media/mtransport/third_party/nICEr/src/net/nr_resolver.h b/media/mtransport/third_party/nICEr/src/net/nr_resolver.h index 602454c53d..376ba9998b 100644 --- a/media/mtransport/third_party/nICEr/src/net/nr_resolver.h +++ b/media/mtransport/third_party/nICEr/src/net/nr_resolver.h @@ -42,7 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define NR_RESOLVE_PROTOCOL_TURN 2 typedef struct nr_resolver_resource_ { - char *domain_name; + const char *domain_name; UINT2 port; int stun_turn; UCHAR transport_protocol; diff --git a/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c b/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c index 3eb3d74b0c..03b40de32c 100644 --- a/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c +++ b/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.c @@ -134,7 +134,6 @@ typedef struct nr_socket_multi_tcp_ { NR_async_cb readable_cb; void *readable_cb_arg; int max_pending; - int use_framing; } nr_socket_multi_tcp; static int nr_socket_multi_tcp_destroy(void **objp); @@ -207,8 +206,7 @@ static int nr_socket_multi_tcp_create_stun_server_socket( int nr_socket_multi_tcp_create(struct nr_ice_ctx_ *ctx, nr_transport_addr *addr, nr_socket_tcp_type tcp_type, - int precreated_so_count, int use_framing, int max_pending, - nr_socket **sockp) + int precreated_so_count, int max_pending, nr_socket **sockp) { int i=0; int r, _status; @@ -224,7 +222,6 @@ int nr_socket_multi_tcp_create(struct nr_ice_ctx_ *ctx, sock->ctx=ctx; sock->max_pending=max_pending; sock->tcp_type=tcp_type; - sock->use_framing=use_framing; nr_transport_addr_copy(&sock->addr, addr); if((tcp_type==TCP_TYPE_PASSIVE) && @@ -261,7 +258,7 @@ int nr_socket_multi_tcp_create(struct nr_ice_ctx_ *ctx, ABORT(r); /* This takes ownership of nrsock whether it fails or not. */ - if ((r=nr_tcp_socket_ctx_create(nrsock, use_framing, max_pending, &tcp_socket_ctx))){ + if ((r=nr_tcp_socket_ctx_create(nrsock, 1, max_pending, &tcp_socket_ctx))){ ABORT(r); } TAILQ_INSERT_TAIL(&sock->sockets, tcp_socket_ctx, entry); @@ -346,8 +343,6 @@ static int nr_socket_multi_tcp_get_sock_connected_to(nr_socket_multi_tcp *sock, /* if active type - create new socket for each new remote addr */ assert(sock->tcp_type == TCP_TYPE_ACTIVE); - /* ICE-TCP active type should always use framing */ - assert(sock->use_framing); if ((r=nr_socket_factory_create_socket(sock->ctx->socket_factory, &sock->addr, &nrsock))) ABORT(r); @@ -569,7 +564,7 @@ static void nr_tcp_multi_lsocket_readable_cb(NR_SOCKET s, int how, void *arg) return; /* This takes ownership of newsock whether it fails or not. */ - if ((r=nr_tcp_socket_ctx_create(newsock, sock->use_framing, sock->max_pending, &tcp_sock_ctx))) + if ((r=nr_tcp_socket_ctx_create(newsock, 1, sock->max_pending, &tcp_sock_ctx))) return; nr_socket_buffered_set_connected_to(tcp_sock_ctx->inner, &remote_addr); diff --git a/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.h b/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.h index 5ad7848fdc..a99e1652d9 100644 --- a/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.h +++ b/media/mtransport/third_party/nICEr/src/net/nr_socket_multi_tcp.h @@ -41,8 +41,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. int nr_socket_multi_tcp_create(struct nr_ice_ctx_ *ctx, nr_transport_addr *addr, nr_socket_tcp_type tcp_type, - int precreated_so_count, int use_framing, int max_pending, - nr_socket **sockp); + int precreated_so_count, int max_pending, nr_socket **sockp); int nr_socket_multi_tcp_set_readable_cb(nr_socket *sock, NR_async_cb readable_cb,void *readable_cb_arg); diff --git a/media/mtransport/third_party/nICEr/src/net/transport_addr.c b/media/mtransport/third_party/nICEr/src/net/transport_addr.c index 2ceeabf2de..cd2ec58d5b 100644 --- a/media/mtransport/third_party/nICEr/src/net/transport_addr.c +++ b/media/mtransport/third_party/nICEr/src/net/transport_addr.c @@ -256,7 +256,7 @@ int nr_ip6_port_to_transport_addr(struct in6_addr* addr6, UINT2 port, int protoc return(_status); } -int nr_transport_addr_get_addrstring(nr_transport_addr *addr, char *str, int maxlen) +int nr_transport_addr_get_addrstring(const nr_transport_addr *addr, char *str, int maxlen) { int _status; const char *res; @@ -395,11 +395,21 @@ int nr_transport_addr_is_loopback(nr_transport_addr *addr) int nr_transport_addr_is_link_local(nr_transport_addr *addr) { - if(addr->ip_version == NR_IPV6){ - UINT4* addrTop = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr); - return ((*addrTop & htonl(0xFFC00000)) == htonl(0xFE800000)); - } else { - assert(0); + switch(addr->ip_version){ + case NR_IPV4: + /* RFC3927: 169.254/16 */ + if ((ntohl(addr->u.addr4.sin_addr.s_addr) & 0xFFFF0000) == 0xA9FE0000) + return(1); + break; + case NR_IPV6: + { + UINT4* addrTop = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr); + if ((*addrTop & htonl(0xFFC00000)) == htonl(0xFE800000)) + return(2); + } + break; + default: + UNIMPLEMENTED; } return(0); @@ -426,3 +436,37 @@ int nr_transport_addr_is_wildcard(nr_transport_addr *addr) return(0); } + +nr_transport_addr_mask nr_private_ipv4_addrs[] = { + /* RFC1918: 10/8 */ + {0x0A000000, 0xFF000000}, + /* RFC1918: 172.16/12 */ + {0xAC100000, 0xFFF00000}, + /* RFC1918: 192.168/16 */ + {0xC0A80000, 0xFFFF0000}, + /* RFC6598: 100.64/10 */ + {0x64400000, 0xFFC00000} +}; + +int nr_transport_addr_get_private_addr_range(nr_transport_addr *addr) + { + switch(addr->ip_version){ + case NR_IPV4: + { + UINT4 ip = ntohl(addr->u.addr4.sin_addr.s_addr); + for (int i=0; i<(sizeof(nr_private_ipv4_addrs)/sizeof(nr_transport_addr_mask)); i++) { + if ((ip & nr_private_ipv4_addrs[i].mask) == nr_private_ipv4_addrs[i].addr) + return i + 1; + } + } + break; + case NR_IPV6: + return(0); + default: + UNIMPLEMENTED; + } + + return(0); + } + + diff --git a/media/mtransport/third_party/nICEr/src/net/transport_addr.h b/media/mtransport/third_party/nICEr/src/net/transport_addr.h index dccc4b16b0..dbaa4a1846 100644 --- a/media/mtransport/third_party/nICEr/src/net/transport_addr.h +++ b/media/mtransport/third_party/nICEr/src/net/transport_addr.h @@ -71,14 +71,19 @@ typedef struct nr_transport_addr_ { char as_string[56]; } nr_transport_addr; +typedef struct nr_transport_addr_mask_ { + UINT4 addr; + UINT4 mask; +} nr_transport_addr_mask; + int nr_sockaddr_to_transport_addr(struct sockaddr *saddr, int protocol, int keep, nr_transport_addr *addr); // addresses, ports in local byte order int nr_ip4_port_to_transport_addr(UINT4 ip4, UINT2 port, int protocol, nr_transport_addr *addr); -int nr_str_port_to_transport_addr(const char *ip4, UINT2 port, int protocol, nr_transport_addr *addr); +int nr_str_port_to_transport_addr(const char *str, UINT2 port, int protocol, nr_transport_addr *addr); int nr_ip6_port_to_transport_addr(struct in6_addr* addr6, UINT2 port, int protocol, nr_transport_addr *addr); -int nr_transport_addr_get_addrstring(nr_transport_addr *addr, char *str, int maxlen); +int nr_transport_addr_get_addrstring(const nr_transport_addr *addr, char *str, int maxlen); int nr_transport_addr_get_port(nr_transport_addr *addr, int *port); int nr_transport_addr_cmp(nr_transport_addr *addr1,nr_transport_addr *addr2,int mode); #define NR_TRANSPORT_ADDR_CMP_MODE_VERSION 1 @@ -88,6 +93,7 @@ int nr_transport_addr_cmp(nr_transport_addr *addr1,nr_transport_addr *addr2,int int nr_transport_addr_is_wildcard(nr_transport_addr *addr); int nr_transport_addr_is_loopback(nr_transport_addr *addr); +int nr_transport_addr_get_private_addr_range(nr_transport_addr *addr); int nr_transport_addr_is_link_local(nr_transport_addr *addr); int nr_transport_addr_copy(nr_transport_addr *to, nr_transport_addr *from); int nr_transport_addr_copy_keep_ifname(nr_transport_addr *to, nr_transport_addr *from); diff --git a/media/mtransport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c b/media/mtransport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c index 595dcb7143..89effe9e32 100644 --- a/media/mtransport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c +++ b/media/mtransport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c @@ -38,6 +38,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include "p_buf.h" #include "nr_socket.h" @@ -85,6 +86,8 @@ static int nr_socket_buffered_stun_close(void *obj); static int nr_socket_buffered_stun_connect(void *sock, nr_transport_addr *addr); static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len, size_t *written); static void nr_socket_buffered_stun_writable_cb(NR_SOCKET s, int how, void *arg); +static int nr_socket_buffered_stun_listen(void *obj, int backlog); +static int nr_socket_buffered_stun_accept(void *obj, nr_transport_addr *addrp, nr_socket **sockp); static nr_socket_vtbl nr_socket_buffered_stun_vtbl={ 2, @@ -97,8 +100,8 @@ static nr_socket_vtbl nr_socket_buffered_stun_vtbl={ 0, 0, nr_socket_buffered_stun_close, - 0, - 0 + nr_socket_buffered_stun_listen, + nr_socket_buffered_stun_accept }; int nr_socket_buffered_set_connected_to(nr_socket *sock, nr_transport_addr *remote_addr) @@ -165,7 +168,7 @@ int nr_socket_buffered_stun_create(nr_socket *inner, int max_pending, _status=0; abort: - if (_status) { + if (_status && sock) { void *sock_v = sock; sock->inner = 0; /* Give up ownership so we don't destroy */ nr_socket_buffered_stun_destroy(&sock_v); @@ -367,6 +370,30 @@ static int nr_socket_buffered_stun_close(void *obj) return nr_socket_close(sock->inner); } +static int nr_socket_buffered_stun_listen(void *obj, int backlog) +{ + int r, _status; + nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj; + + if (!sock->inner) + ABORT(R_FAILED); + + if ((r=nr_socket_listen(sock->inner, backlog))) + ABORT(r); + + _status=0; +abort: + return(_status); +} + + +static int nr_socket_buffered_stun_accept(void *obj, nr_transport_addr *addrp, nr_socket **sockp) +{ + nr_socket_buffered_stun *bsock = (nr_socket_buffered_stun *)obj; + + return nr_socket_accept(bsock->inner, addrp, sockp); +} + static void nr_socket_buffered_stun_connected_cb(NR_SOCKET s, int how, void *arg) { nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)arg; @@ -374,8 +401,10 @@ static void nr_socket_buffered_stun_connected_cb(NR_SOCKET s, int how, void *arg assert(!sock->connected); sock->connected = 1; - if (sock->pending) + if (sock->pending) { + r_log(LOG_GENERIC, LOG_INFO, "Invoking writable_cb on connected (%u)", (uint32_t) sock->pending); nr_socket_buffered_stun_writable_cb(s, how, arg); + } } static int nr_socket_buffered_stun_connect(void *obj, nr_transport_addr *addr) @@ -398,6 +427,7 @@ static int nr_socket_buffered_stun_connect(void *obj, nr_transport_addr *addr) } ABORT(r); } else { + r_log(LOG_GENERIC, LOG_INFO, "Connected without blocking"); sock->connected = 1; } @@ -406,6 +436,21 @@ abort: return(_status); } +static int nr_socket_buffered_stun_arm_writable_cb(nr_socket_buffered_stun *sock) +{ + int r, _status; + NR_SOCKET fd; + + if ((r=nr_socket_getfd(sock->inner, &fd))) + ABORT(r); + + NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_WRITE, nr_socket_buffered_stun_writable_cb, sock); + + _status=0; +abort: + return(_status); +} + static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len, size_t *written) { nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj; @@ -417,7 +462,9 @@ static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len, /* Buffers are close to full, report error. Do this now so we never get partial writes */ if ((sock->pending + len) > sock->max_pending) { - r_log(LOG_GENERIC, LOG_INFO, "Write buffer for %s full", sock->remote_addr.as_string); + r_log(LOG_GENERIC, LOG_INFO, "Write buffer for %s full (%u + %u > %u) - re-arming @%p", + sock->remote_addr.as_string, (uint32_t)sock->pending, (uint32_t)len, (uint32_t)sock->max_pending, + &(sock->pending)); ABORT(R_WOULDBLOCK); } @@ -425,8 +472,13 @@ static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len, if (sock->connected && !sock->pending) { r = nr_socket_write(sock->inner, msg, len, &written2, 0); if (r) { - if (r != R_WOULDBLOCK) + if (r != R_WOULDBLOCK) { + r_log(LOG_GENERIC, LOG_ERR, "Write error for %s - %d", + sock->remote_addr.as_string, r); ABORT(r); + } + r_log(LOG_GENERIC, LOG_INFO, "Write of %" PRIu64 " blocked for %s", + (uint64_t) len, sock->remote_addr.as_string); written2=0; } @@ -439,19 +491,24 @@ static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len, if (len) { if ((r=nr_p_buf_write_to_chain(sock->p_bufs, &sock->pending_writes, - ((UCHAR *)msg) + written2, len))) + ((UCHAR *)msg) + written2, len))) { + r_log(LOG_GENERIC, LOG_ERR, "Write_to_chain error for %s - %d", + sock->remote_addr.as_string, r); + ABORT(r); + } sock->pending += len; } - if (sock->pending && !already_armed) { - NR_SOCKET fd; - - if ((r=nr_socket_getfd(sock->inner, &fd))) - ABORT(r); - - NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_WRITE, nr_socket_buffered_stun_writable_cb, sock); + if (sock->pending) { + if (!already_armed) { + if ((r=nr_socket_buffered_stun_arm_writable_cb(sock))) + ABORT(r); + } + r_log(LOG_GENERIC, LOG_INFO, "Write buffer not empty for %s %u - %s armed (@%p)", + sock->remote_addr.as_string, (uint32_t)sock->pending, + already_armed ? "already" : "", &sock->pending); } *written = original_len; @@ -475,6 +532,8 @@ static void nr_socket_buffered_stun_writable_cb(NR_SOCKET s, int how, void *arg) n1->length - n1->r_offset, &written, 0))) { + r_log(LOG_GENERIC, LOG_ERR, "Write error for %s - %d", + sock->remote_addr.as_string, r); ABORT(r); } @@ -484,6 +543,9 @@ static void nr_socket_buffered_stun_writable_cb(NR_SOCKET s, int how, void *arg) if (n1->r_offset < n1->length) { /* We wrote something, but not everything */ + r_log(LOG_GENERIC, LOG_INFO, "Write in callback didn't write all (remaining %u of %u) for %s", + n1->length - n1->r_offset, n1->length, + sock->remote_addr.as_string); ABORT(R_WOULDBLOCK); } @@ -495,7 +557,12 @@ static void nr_socket_buffered_stun_writable_cb(NR_SOCKET s, int how, void *arg) assert(!sock->pending); _status=0; abort: + r_log(LOG_GENERIC, LOG_INFO, "Writable_cb %s (%u (%p) pending)", + sock->remote_addr.as_string, (uint32_t)sock->pending, &(sock->pending)); if (_status && _status != R_WOULDBLOCK) { + r_log(LOG_GENERIC, LOG_ERR, "Failure in writable_cb: %d", _status); nr_socket_buffered_stun_failed(sock); + } else if (sock->pending) { + nr_socket_buffered_stun_arm_writable_cb(sock); } } diff --git a/media/mtransport/third_party/nICEr/src/stun/stun_build.c b/media/mtransport/third_party/nICEr/src/stun/stun_build.c index 57e5fc75bb..1ee6751798 100644 --- a/media/mtransport/third_party/nICEr/src/stun/stun_build.c +++ b/media/mtransport/third_party/nICEr/src/stun/stun_build.c @@ -219,7 +219,7 @@ nr_stun_build_req_stund_0_96(nr_stun_client_stun_binding_request_stund_0_96_para #ifdef USE_ICE int -nr_stun_build_use_candidate(nr_stun_client_ice_use_candidate_params *params, nr_stun_message **msg) +nr_stun_build_use_candidate(nr_stun_client_ice_binding_request_params *params, nr_stun_message **msg) { int r,_status; nr_stun_message *req = 0; diff --git a/media/mtransport/third_party/nICEr/src/stun/stun_build.h b/media/mtransport/third_party/nICEr/src/stun/stun_build.h index 4793e2f4b4..87bf165a31 100644 --- a/media/mtransport/third_party/nICEr/src/stun/stun_build.h +++ b/media/mtransport/third_party/nICEr/src/stun/stun_build.h @@ -76,16 +76,6 @@ int nr_stun_build_req_stund_0_96(nr_stun_client_stun_binding_request_stund_0_96_ #ifdef USE_ICE -typedef struct nr_stun_client_ice_use_candidate_params_ { - char *username; - Data password; - UINT4 priority; - UINT8 tiebreaker; -} nr_stun_client_ice_use_candidate_params; - -int nr_stun_build_use_candidate(nr_stun_client_ice_use_candidate_params *params, nr_stun_message **msg); - - typedef struct nr_stun_client_ice_binding_request_params_ { char *username; Data password; @@ -96,6 +86,8 @@ typedef struct nr_stun_client_ice_binding_request_params_ { UINT8 tiebreaker; } nr_stun_client_ice_binding_request_params; +int nr_stun_build_use_candidate(nr_stun_client_ice_binding_request_params *params, nr_stun_message **msg); + int nr_stun_build_req_ice(nr_stun_client_ice_binding_request_params *params, nr_stun_message **msg); #endif /* USE_ICE */ diff --git a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c index 4ae93e015b..d9c1268de5 100644 --- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c +++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c @@ -73,33 +73,34 @@ int nr_stun_client_ctx_create(char *label, nr_socket *sock, nr_transport_addr *p nr_socket_getaddr(sock,&ctx->my_addr); nr_transport_addr_copy(&ctx->peer_addr,peer); - if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT, &ctx->rto_ms)) { - if (RTO != 0) - ctx->rto_ms = RTO; - else - ctx->rto_ms = 100; + if (RTO != 0) { + ctx->rto_ms = RTO; + } else if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT, &ctx->rto_ms)) { + ctx->rto_ms = 100; } if (NR_reg_get_double(NR_STUN_REG_PREF_CLNT_RETRANSMIT_BACKOFF, &ctx->retransmission_backoff_factor)) - ctx->retransmission_backoff_factor = 2.0; + ctx->retransmission_backoff_factor = 2.0; if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_MAXIMUM_TRANSMITS, &ctx->maximum_transmits)) - ctx->maximum_transmits = 7; + ctx->maximum_transmits = 7; - if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF, &ctx->final_retransmit_backoff_ms)) - ctx->final_retransmit_backoff_ms = 16 * ctx->rto_ms; + if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF, &ctx->maximum_transmits_timeout_ms)) + ctx->maximum_transmits_timeout_ms = 16 * ctx->rto_ms; - ctx->mapped_addr_check_mask = NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD; - if (NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback) || - !allow_loopback) { - ctx->mapped_addr_check_mask |= NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK; - } + ctx->mapped_addr_check_mask = NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD; + if (NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback) || + !allow_loopback) { + ctx->mapped_addr_check_mask |= NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK; + } - /* If we are doing TCP, compute the maximum timeout as if - we retransmitted and then set the maximum number of - transmits to 1 and the timeout to maximum timeout*/ if (ctx->my_addr.protocol == IPPROTO_TCP) { - ctx->timeout_ms = ctx->final_retransmit_backoff_ms; + /* Because TCP is reliable there is only one final timeout value. + * We store the timeout value for TCP in here, because timeout_ms gets + * reset to 0 in client_reset() which gets called from client_start() */ + ctx->maximum_transmits_timeout_ms = ctx->rto_ms * + pow(ctx->retransmission_backoff_factor, + ctx->maximum_transmits); ctx->maximum_transmits = 1; } @@ -113,6 +114,18 @@ int nr_stun_client_ctx_create(char *label, nr_socket *sock, nr_transport_addr *p return(_status); } +static void nr_stun_client_fire_finished_cb(nr_stun_client_ctx *ctx) + { + if (ctx->finished_cb) { + NR_async_cb finished_cb = ctx->finished_cb; + ctx->finished_cb = 0; /* prevent 2nd call */ + /* finished_cb call must be absolutely last thing in function + * because as a side effect this ctx may be operated on in the + * callback */ + finished_cb(0,0,ctx->cb_arg); + } + } + int nr_stun_client_start(nr_stun_client_ctx *ctx, int mode, NR_async_cb finished_cb, void *cb_arg) { int r,_status; @@ -134,11 +147,7 @@ int nr_stun_client_start(nr_stun_client_ctx *ctx, int mode, NR_async_cb finished _status=0; abort: if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) { - ctx->finished_cb = 0; /* prevent 2nd call */ - /* finished_cb call must be absolutely last thing in function - * because as a side effect this ctx may be operated on in the - * callback */ - finished_cb(0,0,cb_arg); + nr_stun_client_fire_finished_cb(ctx); } return(_status); @@ -254,14 +263,7 @@ static void nr_stun_client_timer_expired_cb(NR_SOCKET s, int b, void *cb_arg) ctx->timer_handle=0; } - if (ctx->finished_cb) { - NR_async_cb finished_cb = ctx->finished_cb; - ctx->finished_cb = 0; /* prevent 2nd call */ - /* finished_cb call must be absolutely last thing in function - * because as a side effect this ctx may be operated on in the - * callback */ - finished_cb(0,0,ctx->cb_arg); - } + nr_stun_client_fire_finished_cb(ctx); } return; } @@ -337,7 +339,7 @@ static int nr_stun_client_send_request(nr_stun_client_ctx *ctx) #ifdef USE_ICE case NR_ICE_CLIENT_MODE_USE_CANDIDATE: - if ((r=nr_stun_build_use_candidate(&ctx->params.ice_use_candidate, &ctx->request))) + if ((r=nr_stun_build_use_candidate(&ctx->params.ice_binding_request, &ctx->request))) ABORT(r); break; case NR_ICE_CLIENT_MODE_BINDING_REQUEST: @@ -380,6 +382,8 @@ static int nr_stun_client_send_request(nr_stun_client_ctx *ctx) snprintf(string, sizeof(string)-1, "STUN-CLIENT(%s): Sending to %s ", ctx->label, ctx->peer_addr.as_string); r_dump(NR_LOG_STUN, LOG_DEBUG, string, (char*)ctx->request->buffer, ctx->request->length); + assert(ctx->my_addr.protocol==ctx->peer_addr.protocol); + if(r=nr_socket_sendto(ctx->sock, ctx->request->buffer, ctx->request->length, 0, &ctx->peer_addr)) ABORT(r); @@ -390,25 +394,32 @@ static int nr_stun_client_send_request(nr_stun_client_ctx *ctx) * response */ } else { - if (ctx->request_ct < ctx->maximum_transmits) { - ctx->timeout_ms *= ctx->retransmission_backoff_factor; - ctx->timeout_ms += ctx->rto_ms; + if (ctx->request_ct >= ctx->maximum_transmits) { + /* Reliable transport only get here once. Unreliable get here for + * their final timeout. */ + ctx->timeout_ms += ctx->maximum_transmits_timeout_ms; + } + else if (ctx->timeout_ms) { + /* exponential backoff */ + ctx->timeout_ms *= ctx->retransmission_backoff_factor; } else { - ctx->timeout_ms += ctx->final_retransmit_backoff_ms; + /* initial timeout unreliable transports */ + ctx->timeout_ms = ctx->rto_ms; } r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Next timer will fire in %u ms",ctx->label, ctx->timeout_ms); gettimeofday(&ctx->timer_set, 0); + assert(ctx->timeout_ms); NR_ASYNC_TIMER_SET(ctx->timeout_ms, nr_stun_client_timer_expired_cb, ctx, &ctx->timer_handle); } _status=0; abort: if (_status) { - ctx->state=NR_STUN_CLIENT_STATE_FAILED; + nr_stun_client_failed(ctx); } return(_status); } @@ -444,15 +455,16 @@ int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len UCHAR hmac_key_d[16]; Data hmac_key; int compute_lt_key=0; + /* TODO(bcampen@mozilla.com): Bug 1023619, refactor this. */ + int response_matched=0; ATTACH_DATA(hmac_key, hmac_key_d); - if(ctx->state==NR_STUN_CLIENT_STATE_CANCELLED) - ABORT(R_REJECTED); - if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) + if ((ctx->state != NR_STUN_CLIENT_STATE_RUNNING) && + (ctx->state != NR_STUN_CLIENT_STATE_WAITING)) ABORT(R_REJECTED); - r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Received check response (my_addr=%s,peer_addr=%s)",ctx->label,ctx->my_addr.as_string,peer_addr->as_string); + r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Inspecting STUN response (my_addr=%s, peer_addr=%s)",ctx->label,ctx->my_addr.as_string,peer_addr->as_string); snprintf(string, sizeof(string)-1, "STUN-CLIENT(%s): Received ", ctx->label); r_dump(NR_LOG_STUN, LOG_DEBUG, string, (char*)msg, len); @@ -535,6 +547,7 @@ int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len nr_stun_message_destroy(&ctx->response); } + /* TODO(bcampen@mozilla.com): Bug 1023619, refactor this. */ if ((r=nr_stun_message_create2(&ctx->response, msg, len))) ABORT(r); @@ -546,12 +559,13 @@ int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len /* This will return an error if request and response don't match, which is how we reject responses that match other contexts. */ if ((r=nr_stun_receive_message(ctx->request, ctx->response))) { - r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): error receiving response",ctx->label); + r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Response is not for us",ctx->label); ABORT(r); } - r_log(NR_LOG_STUN,LOG_DEBUG, - "STUN-CLIENT(%s): successfully received response; processing",ctx->label); + r_log(NR_LOG_STUN,LOG_INFO, + "STUN-CLIENT(%s): Received response; processing",ctx->label); + response_matched=1; /* TODO: !nn! currently using password!=0 to mean that auth is required, * TODO: !nn! but we should probably pass that in explicitly via the @@ -731,22 +745,19 @@ int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len _status=0; abort: - if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) { + if(_status && response_matched){ + r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): Error processing response: %s, stun error code %d.", ctx->label, nr_strerror(_status), (int)ctx->error_code); + } + + if ((ctx->state != NR_STUN_CLIENT_STATE_RUNNING) && + (ctx->state != NR_STUN_CLIENT_STATE_WAITING)) { /* Cancel the timer firing */ if (ctx->timer_handle) { NR_async_timer_cancel(ctx->timer_handle); ctx->timer_handle = 0; } - /* Fire the callback */ - if (ctx->finished_cb) { - NR_async_cb finished_cb = ctx->finished_cb; - ctx->finished_cb = 0; /* prevent 2nd call */ - /* finished_cb call must be absolutely last thing in function - * because as a side effect this ctx may be operated on in the - * callback */ - finished_cb(0,0,ctx->cb_arg); - } + nr_stun_client_fire_finished_cb(ctx); } return(_status); @@ -784,6 +795,25 @@ int nr_stun_client_cancel(nr_stun_client_ctx *ctx) /* Mark cancelled so we ignore any returned messsages */ ctx->state=NR_STUN_CLIENT_STATE_CANCELLED; + return(0); +} + +int nr_stun_client_wait(nr_stun_client_ctx *ctx) + { + nr_stun_client_cancel(ctx); + ctx->state=NR_STUN_CLIENT_STATE_WAITING; + + ctx->request_ct = ctx->maximum_transmits; + ctx->timeout_ms = ctx->maximum_transmits_timeout_ms; + NR_ASYNC_TIMER_SET(ctx->timeout_ms, nr_stun_client_timer_expired_cb, ctx, &ctx->timer_handle); return(0); } + +int nr_stun_client_failed(nr_stun_client_ctx *ctx) + { + nr_stun_client_cancel(ctx); + ctx->state=NR_STUN_CLIENT_STATE_FAILED; + nr_stun_client_fire_finished_cb(ctx); + return(0); + } diff --git a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h index b666ea1264..179f16cbaa 100644 --- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h +++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h @@ -65,7 +65,6 @@ typedef union nr_stun_client_params_ { #endif /* USE_STUND_0_96 */ #ifdef USE_ICE - nr_stun_client_ice_use_candidate_params ice_use_candidate; nr_stun_client_ice_binding_request_params ice_binding_request; #endif /* USE_ICE */ @@ -138,6 +137,7 @@ struct nr_stun_client_ctx_ { #define NR_STUN_CLIENT_STATE_FAILED 3 #define NR_STUN_CLIENT_STATE_TIMED_OUT 4 #define NR_STUN_CLIENT_STATE_CANCELLED 5 +#define NR_STUN_CLIENT_STATE_WAITING 6 int mode; #define NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH 1 @@ -171,7 +171,7 @@ struct nr_stun_client_ctx_ { UINT4 rto_ms; /* retransmission time out */ double retransmission_backoff_factor; UINT4 maximum_transmits; - UINT4 final_retransmit_backoff_ms; + UINT4 maximum_transmits_timeout_ms; UINT4 mapped_addr_check_mask; /* What checks to run on mapped addresses */ int timeout_ms; struct timeval timer_set; @@ -192,6 +192,8 @@ int nr_stun_client_ctx_destroy(nr_stun_client_ctx **ctxp); int nr_stun_transport_addr_check(nr_transport_addr* addr, UINT4 mask); int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *peer_addr); int nr_stun_client_cancel(nr_stun_client_ctx *ctx); +int nr_stun_client_wait(nr_stun_client_ctx *ctx); +int nr_stun_client_failed(nr_stun_client_ctx *ctx); #endif diff --git a/media/mtransport/third_party/nICEr/src/stun/stun_codec.c b/media/mtransport/third_party/nICEr/src/stun/stun_codec.c index 8fb02deeef..6faab70de6 100644 --- a/media/mtransport/third_party/nICEr/src/stun/stun_codec.c +++ b/media/mtransport/third_party/nICEr/src/stun/stun_codec.c @@ -1322,7 +1322,7 @@ nr_stun_encode_message(nr_stun_message *msg) ABORT(R_INTERNAL); } - attr_info->name = attr_info->name; + attr->name = attr_info->name; attr->type_name = attr_info->codec->name; attr->encoding = (nr_stun_encoded_attribute*)&msg->buffer[msg->length]; @@ -1355,6 +1355,9 @@ nr_stun_encode_message(nr_stun_message *msg) length_offset = length_offset_hold; (void)nr_stun_encode_htons(msg->header.length, sizeof(msg->buffer), msg->buffer, &length_offset); } + else { + r_log(NR_LOG_STUN, LOG_WARNING, "Missing encode function for attribute: %s", attr_info->name); + } } r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded Length: %d", msg->header.length); diff --git a/media/mtransport/third_party/nICEr/src/stun/stun_msg.c b/media/mtransport/third_party/nICEr/src/stun/stun_msg.c index 1247c3e4ff..c34bb46b04 100644 --- a/media/mtransport/third_party/nICEr/src/stun/stun_msg.c +++ b/media/mtransport/third_party/nICEr/src/stun/stun_msg.c @@ -186,7 +186,9 @@ nr_stun_message_has_attribute(nr_stun_message *msg, UINT2 type, nr_stun_message_ { __code } \ _status=0; \ abort: \ - if (_status) RFREE(attr); \ + if (_status){ \ + nr_stun_message_attribute_destroy(msg, &attr); \ + } \ return(_status); \ } diff --git a/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c b/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c index 9635fe2e58..b766f776cd 100644 --- a/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c +++ b/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c @@ -79,7 +79,6 @@ static int nr_turn_stun_set_auth_params(nr_turn_stun_ctx *ctx, static void nr_turn_client_refresh_timer_cb(NR_SOCKET s, int how, void *arg); static int nr_turn_client_refresh_setup(nr_turn_client_ctx *ctx, nr_turn_stun_ctx **sctx); -static int nr_turn_client_failed(nr_turn_client_ctx *ctx); static int nr_turn_client_start_refresh_timer(nr_turn_client_ctx *ctx, nr_turn_stun_ctx *sctx, UINT4 lifetime); @@ -497,7 +496,16 @@ abort: return(_status); } -static int nr_turn_client_failed(nr_turn_client_ctx *ctx) +static void nr_turn_client_fire_finished_cb(nr_turn_client_ctx *ctx) + { + if (ctx->finished_cb) { + NR_async_cb finished_cb=ctx->finished_cb; + ctx->finished_cb=0; + finished_cb(0, 0, ctx->cb_arg); + } + } + +int nr_turn_client_failed(nr_turn_client_ctx *ctx) { if (ctx->state == NR_TURN_CLIENT_STATE_FAILED || ctx->state == NR_TURN_CLIENT_STATE_CANCELLED) @@ -506,9 +514,7 @@ static int nr_turn_client_failed(nr_turn_client_ctx *ctx) r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s) failed", ctx->label); nr_turn_client_cancel(ctx); ctx->state = NR_TURN_CLIENT_STATE_FAILED; - if (ctx->finished_cb) { - ctx->finished_cb(0, 0, ctx->cb_arg); - } + nr_turn_client_fire_finished_cb(ctx); return(0); } @@ -549,7 +555,6 @@ static void nr_turn_client_allocate_cb(NR_SOCKET s, int how, void *arg) { nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg; nr_turn_stun_ctx *refresh_ctx; - NR_async_cb tmp_finished_cb; int r,_status; ctx->tctx->state = NR_TURN_CLIENT_STATE_ALLOCATED; @@ -578,14 +583,12 @@ static void nr_turn_client_allocate_cb(NR_SOCKET s, int how, void *arg) ctx->tctx->relay_addr.as_string, ctx->stun->results.allocate_response.lifetime_secs); + nr_turn_client_fire_finished_cb(ctx->tctx); _status=0; abort: if (_status) { nr_turn_client_failed(ctx->tctx); } - tmp_finished_cb = ctx->tctx->finished_cb; - ctx->tctx->finished_cb = 0; /* So we don't call it again */ - tmp_finished_cb(0, 0, ctx->tctx->cb_arg); } static void nr_turn_client_error_cb(NR_SOCKET s, int how, void *arg) diff --git a/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.h b/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.h index 8ba67d370d..c50e79d3b4 100644 --- a/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.h +++ b/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.h @@ -120,6 +120,7 @@ int nr_turn_client_process_response(nr_turn_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *turn_server_addr); int nr_turn_client_cancel(nr_turn_client_ctx *ctx); +int nr_turn_client_failed(nr_turn_client_ctx *ctx); int nr_turn_client_deallocate(nr_turn_client_ctx *ctx); int nr_turn_client_send_indication(nr_turn_client_ctx *ctx, const UCHAR *msg, size_t len, diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp index 6d949e8813..d755c42f93 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -623,12 +623,6 @@ PeerConnectionConfiguration::AddIceServer(const RTCIceServer &aServer) NS_ConvertUTF16toUTF8 credential(aServer.mCredential); NS_ConvertUTF16toUTF8 username(aServer.mUsername); - // Bug 1039655 - TURN TCP is not e10s ready - if ((transport == kNrIceTransportTcp) && - (!XRE_IsParentProcess())) { - continue; - } - if (!addTurnServer(host.get(), port, username.get(), credential.get(), diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp index 5e2bdb6292..bc434f2d72 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp @@ -294,18 +294,22 @@ nsresult PeerConnectionMedia::Init(const std::vector& stun_serv #if !defined(MOZILLA_EXTERNAL_LINKAGE) bool ice_tcp = Preferences::GetBool("media.peerconnection.ice.tcp", false); + bool default_address_only = Preferences::GetBool( + "media.peerconnection.ice.default_address_only", false); #else bool ice_tcp = false; + bool default_address_only = false; #endif + // TODO(ekr@rtfm.com): need some way to set not offerer later // Looks like a bug in the NrIceCtx API. mIceCtx = NrIceCtx::Create("PC:" + mParentName, true, // Offerer - true, // Explicitly set priorities mParent->GetAllowIceLoopback(), ice_tcp, mParent->GetAllowIceLinkLocal(), + default_address_only, policy); if(!mIceCtx) { CSFLogError(logTag, "%s: Failed to create Ice Context", __FUNCTION__); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index b4d6dadecf..8e1a8f2973 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -321,12 +321,12 @@ pref("media.wakelock_timeout", 2000); // opened as top-level documents, as opposed to inside a media element. pref("media.play-stand-alone", true); -pref("media.hardware-video-decoding.enabled", true); - // Whether we should delay actioning a "play()" JS function call and autoplay // attribute until the media element's owner document is visible. pref("media.block-play-until-visible", false); +pref("media.hardware-video-decoding.enabled", true); + pref("media.decoder.heuristic.dormant.enabled", true); pref("media.decoder.heuristic.dormant.timeout", 60000); @@ -457,13 +457,17 @@ pref("media.navigator.permission.disabled", false); pref("media.peerconnection.default_iceservers", "[]"); pref("media.peerconnection.ice.loopback", false); // Set only for testing in offline environments. pref("media.peerconnection.ice.tcp", false); +pref("media.peerconnection.ice.tcp_so_sock_count", 0); // Disable SO gathering pref("media.peerconnection.ice.link_local", false); // Set only for testing IPV6 in networks that don't assign IPV6 addresses +pref("media.peerconnection.ice.force_interface", ""); // Limit to only a single interface pref("media.peerconnection.ice.relay_only", false); // Limit candidates to TURN pref("media.peerconnection.use_document_iceservers", true); pref("media.peerconnection.identity.enabled", true); pref("media.peerconnection.identity.timeout", 10000); pref("media.peerconnection.ice.stun_client_maximum_transmits", 7); pref("media.peerconnection.ice.trickle_grace_period", 5000); +pref("media.peerconnection.ice.default_address_only", false); + // These values (aec, agc, and noice) are from media/webrtc/trunk/webrtc/common_types.h // kXxxUnchanged = 0, kXxxDefault = 1, and higher values are specific to each // setting (for Xxx = Ec, Agc, or Ns). Defaults are all set to kXxxDefault here. @@ -2954,7 +2958,7 @@ pref("font.minimum-size.ko", 0); pref("font.size.variable.ko", 16); pref("font.size.fixed.ko", 16); -pref("font.default.th", "serif"); +pref("font.default.th", "sans-serif"); pref("font.minimum-size.th", 0); pref("font.size.variable.th", 16); pref("font.size.fixed.th", 13); @@ -3071,7 +3075,10 @@ pref("font.size.fixed.zh-TW", 16); // mathml.css sets font-size to "inherit" and font-family to "serif" so only // font.name.*.x-math and font.minimum-size.x-math are really relevant. +pref("font.default.x-math", "serif"); pref("font.minimum-size.x-math", 0); +pref("font.size.variable.x-math", 16); +pref("font.size.fixed.x-math", 13); /* * A value greater than zero enables font size inflation for @@ -4616,6 +4623,9 @@ pref("layers.tiled-drawtarget.enabled", true); pref("layers.tiles.edge-padding", false); #endif +#ifdef MOZ_WIDGET_GONK +pref("layers.tiled-drawtarget.enabled", true); +#endif #ifdef MOZ_WIDGET_ANDROID pref("layers.tiled-drawtarget.enabled", true); @@ -5304,7 +5314,7 @@ pref("browser.search.official", true); //pref("media.gmp-manager.url.override", ""); // Update service URL for GMP install/updates: -pref("media.gmp-manager.url", "https://aus4.mozilla.org/update/3/GMP/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml"); +pref("media.gmp-manager.url", "https://aus5.mozilla.org/update/3/GMP/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml"); // When |media.gmp-manager.cert.requireBuiltIn| is true or not specified the // final certificate and all certificates the connection is redirected to before @@ -5329,10 +5339,10 @@ pref("media.gmp-manager.cert.requireBuiltIn", true); // IMPORTANT! app.update.certs.* prefs should also be updated if these // are updated. pref("media.gmp-manager.cert.checkAttributes", true); -pref("media.gmp-manager.certs.1.issuerName", "CN=DigiCert Secure Server CA,O=DigiCert Inc,C=US"); -pref("media.gmp-manager.certs.1.commonName", "aus4.mozilla.org"); -pref("media.gmp-manager.certs.2.issuerName", "CN=Thawte SSL CA,O=\"Thawte, Inc.\",C=US"); -pref("media.gmp-manager.certs.2.commonName", "aus4.mozilla.org"); +pref("media.gmp-manager.certs.1.issuerName", "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US"); +pref("media.gmp-manager.certs.1.commonName", "aus5.mozilla.org"); +pref("media.gmp-manager.certs.2.issuerName", "CN=thawte SSL CA - G2,O=\"thawte, Inc.\",C=US"); +pref("media.gmp-manager.certs.2.commonName", "aus5.mozilla.org"); #endif // Whether or not to perform reader mode article parsing on page load. @@ -5418,4 +5428,7 @@ pref("dom.requestcontext.enabled", false); pref("dom.mozKillSwitch.enabled", false); +// Allow customization of the fallback directory for file uploads +pref("dom.input.fallbackUploadDir", ""); + pref("devtools.serviceWorkers.testing.enabled", false); diff --git a/mozglue/misc/StackWalk.cpp b/mozglue/misc/StackWalk.cpp index bdca7d3882..2f60734542 100644 --- a/mozglue/misc/StackWalk.cpp +++ b/mozglue/misc/StackWalk.cpp @@ -896,15 +896,7 @@ MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, StackWalkInitCriticalAddress(); // Get the frame pointer - void** bp; -#if defined(__i386) - __asm__("movl %%ebp, %0" : "=g"(bp)); -#else - // It would be nice if this worked uniformly, but at least on i386 and - // x86_64, it stopped working with gcc 4.1, because it points to the - // end of the saved registers instead of the start. - bp = (void**)__builtin_frame_address(0); -#endif + void** bp = (void**)__builtin_frame_address(0); void* stackEnd; #if HAVE___LIBC_STACK_END diff --git a/mozglue/misc/TimeStamp_posix.cpp b/mozglue/misc/TimeStamp_posix.cpp index e9c4620d6b..9126b5678c 100644 --- a/mozglue/misc/TimeStamp_posix.cpp +++ b/mozglue/misc/TimeStamp_posix.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #if defined(__DragonFly__) || defined(__FreeBSD__) \ || defined(__NetBSD__) || defined(__OpenBSD__) @@ -205,7 +206,7 @@ TimeStamp::Now(bool aHighResolution) return TimeStamp(ClockTimeNs()); } -#if defined(LINUX) || defined(ANDROID) +#if defined(XP_LINUX) || defined(ANDROID) // Calculates the amount of jiffies that have elapsed since boot and up to the // starttime value of a specific process as found in its /proc/*/stat file. diff --git a/python/mozversioncontrol/mozversioncontrol/__init__.py b/python/mozversioncontrol/mozversioncontrol/__init__.py index cb2b7bf4d2..b3a3e9d640 100644 --- a/python/mozversioncontrol/mozversioncontrol/__init__.py +++ b/python/mozversioncontrol/mozversioncontrol/__init__.py @@ -7,9 +7,30 @@ from __future__ import unicode_literals import os import re import subprocess +import which from distutils.version import LooseVersion +def get_hg_path(): + """Obtain the path of the Mercurial client.""" + + # We use subprocess in places, which expects a Win32 executable or + # batch script. On some versions of MozillaBuild, we have "hg.exe", + # "hg.bat," and "hg" (a Python script). "which" will happily return the + # Python script, which will cause subprocess to choke. Explicitly favor + # the Windows version over the plain script. + try: + return which.which('hg.exe') + except which.WhichError: + try: + return which.which('hg') + except which.WhichError as e: + print(e) + + raise Exception('Unable to obtain Mercurial path. Try running ' + + '|mach bootstrap| to ensure your environment is up to ' + + 'date.') + def get_hg_version(hg): """Obtain the version of the Mercurial client.""" diff --git a/security/manager/tools/PreloadedHPKPins.json b/security/manager/tools/PreloadedHPKPins.json new file mode 100644 index 0000000000..50d24fa199 --- /dev/null +++ b/security/manager/tools/PreloadedHPKPins.json @@ -0,0 +1,236 @@ +// -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// The top-level element is a dictionary with two keys: "pinsets" maps details +// of certificate pinning to a name and "entries" contains the HPKP details for +// each host. +// +// "pinsets" is a list of objects. Each object has the following members: +// name: (string) the name of the pinset +// sha256_hashes: (list of strings) the set of allowed SPKIs hashes +// +// For a given pinset, a certificate is accepted if at least one of the +// Subject Public Key Infos (SPKIs) is found in the chain. SPKIs are specified +// as names, which must match up with the name given in the Mozilla root store. +// +// "entries" is a list of objects. Each object has the following members: +// name: (string) the DNS name of the host in question +// include_subdomains: (optional bool) whether subdomains of |name| are also covered +// pins: (string) the |name| member of an object in |pinsets| +// +// "extra_certs" is a list of base64-encoded certificates. These are used in +// pinsets that reference certificates not in our root program (for example, +// Facebook). + +// equifax -> aus3 +// Geotrust Primary -> www.mozilla.org +// Geotrust Global -> *. addons.mozilla.org +{ + "chromium_data" : { + "cert_file_url": "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.certs?format=TEXT", + "json_file_url": "https://chromium.googlesource.com/chromium/src/net/+/master/http/transport_security_state_static.json?format=TEXT", + "substitute_pinsets": { + // Use the larger google_root_pems pinset instead of google + "google": "google_root_pems" + }, + "production_pinsets": [ + "google_root_pems", + "facebook" + ], + "production_domains": [ + // Chrome's test domain. + "pinningtest.appspot.com", + // Dropbox + "dropbox.com", + "www.dropbox.com", + // Twitter + "api.twitter.com", + "business.twitter.com", + "dev.twitter.com", + "mobile.twitter.com", + "oauth.twitter.com", + "platform.twitter.com", + "twimg.com", + "www.twitter.com", + // Tor + "torproject.org", + "blog.torproject.org", + "check.torproject.org", + "dist.torproject.org", + "www.torproject.org", + // SpiderOak + "spideroak.com" + ], + "exclude_domains" : [ + // Chrome's entry for twitter.com doesn't include subdomains, so replace + // it with our own entry below which also uses an expanded pinset. + "twitter.com" + ] + }, + "pinsets": [ + { + // From bug 772756, mozilla uses GeoTrust, Digicert and Thawte. Our + // cdn sites use Verisign and Baltimore. We exclude 1024-bit root certs + // from all providers. geotrust ca info: + // http://www.geotrust.com/resources/root-certificates/index.html + "name": "mozilla", + "sha256_hashes": [ + "Baltimore CyberTrust Root", + "DigiCert Assured ID Root CA", + "DigiCert Global Root CA", + "DigiCert High Assurance EV Root CA", + "GeoTrust Global CA", + "GeoTrust Global CA 2", + "GeoTrust Primary Certification Authority", + "GeoTrust Primary Certification Authority - G2", + "GeoTrust Primary Certification Authority - G3", + "GeoTrust Universal CA", + "GeoTrust Universal CA 2", + "thawte Primary Root CA", + "thawte Primary Root CA - G2", + "thawte Primary Root CA - G3", + "Verisign Class 1 Public Primary Certification Authority - G3", + "Verisign Class 2 Public Primary Certification Authority - G3", + "Verisign Class 3 Public Primary Certification Authority - G3", + "VeriSign Class 3 Public Primary Certification Authority - G4", + "VeriSign Class 3 Public Primary Certification Authority - G5", + "Verisign Class 4 Public Primary Certification Authority - G3", + "VeriSign Universal Root Certification Authority" + ] + }, + { + "name": "mozilla_services", + "sha256_hashes": [ + "DigiCert Global Root CA" + ] + }, + // For pinning tests on pinning.example.com, the certificate must be 'End + // Entity Test Cert' + { + "name": "mozilla_test", + "sha256_hashes": [ + "End Entity Test Cert" + ] + }, + // Google's root PEMs. Chrome pins only to their intermediate certs, but + // they'd like us to be more liberal. For the initial list, we are using + // the certs from http://pki.google.com/roots.pem. + // We have no built-in for commented out CAs. + { + "name": "google_root_pems", + "sha256_hashes": [ + "AddTrust External Root", + "AddTrust Low-Value Services Root", + "AddTrust Public Services Root", + "AddTrust Qualified Certificates Root", + "AffirmTrust Commercial", + "AffirmTrust Networking", + "AffirmTrust Premium", + "AffirmTrust Premium ECC", + // "America Online Root Certification Authority 1", + // "America Online Root Certification Authority 2", + "Baltimore CyberTrust Root", + "Comodo AAA Services root", + "COMODO Certification Authority", + "COMODO ECC Certification Authority", + "Comodo Secure Services root", + "Comodo Trusted Services root", + "Cybertrust Global Root", + "DigiCert Assured ID Root CA", + "DigiCert Global Root CA", + "DigiCert High Assurance EV Root CA", + "Entrust.net Premium 2048 Secure Server CA", + // "Entrust.net Secure Server CA", + "Entrust Root Certification Authority", + "Equifax Secure CA", + "Equifax Secure eBusiness CA 1", + // "Equifax Secure eBusiness CA 2", + "Equifax Secure Global eBusiness CA", + "GeoTrust Global CA", + "GeoTrust Global CA 2", + "GeoTrust Primary Certification Authority", + "GeoTrust Primary Certification Authority - G2", + "GeoTrust Primary Certification Authority - G3", + "GeoTrust Universal CA", + "GeoTrust Universal CA 2", + "GlobalSign Root CA", + "GlobalSign Root CA - R2", + "GlobalSign Root CA - R3", + "Go Daddy Class 2 CA", + "Go Daddy Root Certificate Authority - G2", + // "GTE CyberTrust Global Root", + "Network Solutions Certificate Authority", + // "RSA Root Certificate 1", + "Starfield Class 2 CA", + "Starfield Root Certificate Authority - G2", + "Starfield Services Root Certificate Authority - G2", + "StartCom Certification Authority", + "StartCom Certification Authority", + "StartCom Certification Authority G2", + "TC TrustCenter Class 2 CA II", + "TC TrustCenter Class 3 CA II", + "TC TrustCenter Universal CA I", + "TC TrustCenter Universal CA III", + // "Thawte Premium Server CA", + "thawte Primary Root CA", + "thawte Primary Root CA - G2", + "thawte Primary Root CA - G3", + // "Thawte Server CA", + "UTN DATACorp SGC Root CA", + "UTN USERFirst Hardware Root CA", + // "ValiCert Class 1 VA", + // "ValiCert Class 2 VA", + "Verisign Class 3 Public Primary Certification Authority", + "Verisign Class 3 Public Primary Certification Authority", + "Verisign Class 3 Public Primary Certification Authority - G2", + "Verisign Class 3 Public Primary Certification Authority - G3", + "VeriSign Class 3 Public Primary Certification Authority - G4", + "VeriSign Class 3 Public Primary Certification Authority - G5", + "Verisign Class 4 Public Primary Certification Authority - G3", + "VeriSign Universal Root Certification Authority", + "XRamp Global CA Root" + ] + } + ], + + "entries": [ + // Only domains that are operationally crucial to Firefox can have per-host + // telemetry reporting (the "id") field + { "name": "addons.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": false, "id": 1 }, + { "name": "addons.mozilla.net", "include_subdomains": true, + "pins": "mozilla", "test_mode": false, "id": 2 }, + { "name": "aus4.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": true, "id": 3 }, + { "name": "accounts.firefox.com", "include_subdomains": true, + "pins": "mozilla_services", "test_mode": false, "id": 4 }, + { "name": "api.accounts.firefox.com", "include_subdomains": true, + "pins": "mozilla_services", "test_mode": false, "id": 5 }, + { "name": "cdn.mozilla.net", "include_subdomains": true, + "pins": "mozilla", "test_mode": false }, + { "name": "cdn.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": false }, + { "name": "services.mozilla.com", "include_subdomains": true, + "pins": "mozilla_services", "test_mode": false, "id": 6 }, + { "name": "include-subdomains.pinning.example.com", + "include_subdomains": true, "pins": "mozilla_test", + "test_mode": false }, + // Example domain to collect per-host stats for telemetry tests. + { "name": "exclude-subdomains.pinning.example.com", + "include_subdomains": false, "pins": "mozilla_test", + "test_mode": false, "id": 0 }, + { "name": "test-mode.pinning.example.com", "include_subdomains": true, + "pins": "mozilla_test", "test_mode": true }, + // Expand twitter's pinset to include all of *.twitter.com and use + // twitterCDN. More specific rules take precedence because we search for + // exact domain name first. + { "name": "twitter.com", "include_subdomains": true, + "pins": "twitterCDN", "test_mode": false }, + { "name": "aus5.mozilla.org", "include_subdomains": true, + "pins": "mozilla", "test_mode": true, "id": 7 } + ], + + "extra_certificates": [] +} diff --git a/tools/memory-profiler/MemoryProfiler.cpp b/tools/memory-profiler/MemoryProfiler.cpp index 6f7a0b8894..d367432d6c 100644 --- a/tools/memory-profiler/MemoryProfiler.cpp +++ b/tools/memory-profiler/MemoryProfiler.cpp @@ -46,7 +46,7 @@ ProfilerImpl::GetStacktrace() profiler_get_backtrace_noalloc(output, BACKTRACE_BUFFER_SIZE); for (const char* p = output; *p; p += strlen(p) + 1) { - trace.AppendElement(nsDependentCString(p)); + trace.AppendElement()->Assign(p); } return trace; diff --git a/tools/mercurial/hgsetup/config.py b/tools/mercurial/hgsetup/config.py index 9123c1d9d8..da2bc1b442 100644 --- a/tools/mercurial/hgsetup/config.py +++ b/tools/mercurial/hgsetup/config.py @@ -11,50 +11,58 @@ import os HOST_FINGERPRINTS = { - 'bitbucket.org': '45:ad:ae:1a:cf:0e:73:47:06:07:e0:88:f5:cc:10:e5:fa:1c:f7:99', - 'bugzilla.mozilla.org': '47:13:a2:14:0c:46:45:53:12:0d:e5:36:16:a5:60:26:3e:da:3a:60', + 'bitbucket.org': '46:de:34:e7:9b:18:cd:7f:ae:fd:8b:e3:bc:f4:1a:5e:38:d7:ac:24', + 'bugzilla.mozilla.org': '7c:7a:c4:6c:91:3b:6b:89:cf:f2:8c:13:b8:02:c4:25:bd:1e:25:17', 'hg.mozilla.org': 'af:27:b9:34:47:4e:e5:98:01:f6:83:2b:51:c9:aa:d8:df:fb:1a:27', } -class HgIncludeException(Exception): - pass +def config_file(files): + """Select the most appropriate config file from a list.""" + if not files: + return None + + if len(files) > 1: + picky = [(os.path.getsize(f), f) for f in files if os.path.isfile(f)] + if picky: + return max(picky)[1] + + return files[0] + + +class ParseException(Exception): + def __init__(self, line, msg): + self.line = line + super(Exception, self).__init__(msg) class MercurialConfig(object): """Interface for manipulating a Mercurial config file.""" - def __init__(self, infiles=None): + def __init__(self, path=None): """Create a new instance, optionally from an existing hgrc file.""" - if infiles: - # If multiple files were specified, figure out which file we're using: - if len(infiles) > 1: - picky_infiles = filter(os.path.isfile, infiles) - if picky_infiles: - picky_infiles = [(os.path.getsize(path), path) for path in picky_infiles] - infiles = [max(picky_infiles)[1]] - - infile = infiles[0] - self.config_path = infile - else: - infile = None + self.config_path = path # Mercurial configuration files allow an %include directive to include # other files, this is not supported by ConfigObj, so throw a useful # error saying this. - if os.path.exists(infile): - with codecs.open(infile, 'r', encoding='utf-8') as f: - for line in f: + if os.path.exists(path): + with codecs.open(path, 'r', encoding='utf-8') as f: + for i, line in enumerate(f): if line.startswith('%include'): - raise HgIncludeException( + raise ParseException(i + 1, '%include directive is not supported by MercurialConfig') + if line.startswith(';'): + raise ParseException(i + 1, + 'semicolon (;) comments are not supported; ' + 'use # instead') # write_empty_values is necessary to prevent built-in extensions (which # have no value) from being dropped on write. # list_values aren't needed by Mercurial and disabling them prevents # quotes from being added. - self._c = ConfigObj(infile=infile, encoding='utf-8', + self._c = ConfigObj(infile=path, encoding='utf-8', write_empty_values=True, list_values=False) @property @@ -175,14 +183,80 @@ class MercurialConfig(object): def get_bugzilla_credentials(self): if 'bugzilla' not in self._c: - return None, None + return None, None, None, None, None b = self._c['bugzilla'] - return b.get('username', None), b.get('password', None) + return ( + b.get('username', None), + b.get('password', None), + b.get('userid', None), + b.get('cookie', None), + b.get('apikey', None), + ) - def set_bugzilla_credentials(self, username, password): + def set_bugzilla_credentials(self, username, api_key): b = self._c.setdefault('bugzilla', {}) if username: b['username'] = username - if password: - b['password'] = password + if api_key: + b['apikey'] = api_key + + def clear_legacy_bugzilla_credentials(self): + if 'bugzilla' not in self._c: + return + + b = self._c['bugzilla'] + for k in ('password', 'userid', 'cookie'): + if k in b: + del b[k] + + def have_clonebundles(self): + return 'clonebundles' in self._c.get('experimental', {}) + + def activate_clonebundles(self): + exp = self._c.setdefault('experimental', {}) + exp['clonebundles'] = 'true' + + # bundleclone is redundant with clonebundles. Remove it if it + # is installed. + ext = self._c.get('extensions', {}) + try: + del ext['bundleclone'] + except KeyError: + pass + + def have_wip(self): + return 'wip' in self._c.get('alias', {}) + + def install_wip_alias(self): + """hg wip shows a concise view of work in progress.""" + alias = self._c.setdefault('alias', {}) + alias['wip'] = 'log --graph --rev=wip --template=wip' + + revsetalias = self._c.setdefault('revsetalias', {}) + revsetalias['wip'] = ('(' + 'parents(not public()) ' + 'or not public() ' + 'or . ' + 'or (head() and branch(default))' + ') and (not obsolete() or unstable()^) ' + 'and not closed()') + + templates = self._c.setdefault('templates', {}) + templates['wip'] = ("'" + # prefix with branch name + '{label("log.branch", branches)} ' + # rev:node + '{label("changeset.{phase}", rev)}' + '{label("changeset.{phase}", ":")}' + '{label("changeset.{phase}", short(node))} ' + # just the username part of the author, for brevity + '{label("grep.user", author|user)}' + # tags and bookmarks + '{label("log.tag", if(tags," {tags}"))} ' + '{label("log.bookmark", if(bookmarks," {bookmarks}"))}' + '\\n' + # first line of commit message + '{label(ifcontains(rev, revset("."), "desc.here"),desc|firstline)}' + "'" + ) diff --git a/tools/mercurial/hgsetup/update.py b/tools/mercurial/hgsetup/update.py index f73c20dfea..916d1a7596 100644 --- a/tools/mercurial/hgsetup/update.py +++ b/tools/mercurial/hgsetup/update.py @@ -4,17 +4,12 @@ from __future__ import unicode_literals -import errno import os -import which - -from configobj import ConfigObjError +from mozversioncontrol import get_hg_path from mozversioncontrol.repoupdate import update_mercurial_repo from .config import ( - HgIncludeException, - MercurialConfig, HOST_FINGERPRINTS, ) @@ -27,53 +22,27 @@ class MercurialUpdater(object): def __init__(self, state_dir): self.state_dir = os.path.normpath(state_dir) - self.ext_dir = os.path.join(self.state_dir, 'mercurial', 'extensions') self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools') + self.hgwatchman_dir = os.path.join(self.state_dir, 'hgwatchman') - def update_all(self, config_paths): - try: - os.makedirs(self.ext_dir) - except OSError as e: - if e.errno != errno.EEXIST: - raise + def update_all(self): + hg = get_hg_path() - try: - hg = which.which('hg') - except which.WhichError as e: - print(e) - print('Try running |mach bootstrap| to ensure your environment is ' - 'up to date.') - return 1 - - try: - c = MercurialConfig(config_paths) - except ConfigObjError as e: - print('Error importing existing Mercurial config!\n') - for error in e.errors: - print(error.message) - - return 1 - except HgIncludeException as e: - print(e.message) - - return 1 - - if 'mqext' in c.extensions: - self.update_mercurial_repo( - hg, - 'https://bitbucket.org/sfink/mqext', - os.path.join(self.ext_dir, 'mqext'), - 'default', - 'Ensuring mqext is up to date...') - - if os.path.isdir(self.vcs_tools_dir): - self.update_mercurial_repo( - hg, - 'https://hg.mozilla.org/hgcustom/version-control-tools', - self.vcs_tools_dir, - 'default', - 'Ensuring version-control-tools is up to date...') - print(FINISHED) + repo_existed = os.path.isdir(self.vcs_tools_dir) + self.update_mercurial_repo( + hg, + 'https://hg.mozilla.org/hgcustom/version-control-tools', + self.vcs_tools_dir, + 'default', + 'Ensuring version-control-tools is up to date...') + self.update_mercurial_repo( + hg, + 'https://bitbucket.org/facebook/hgwatchman', + self.hgwatchman_dir, + 'default', + 'Ensuring hgwatchman is up to date...') + if repo_existed: + print(FINISHED) return 0 def update_mercurial_repo(self, hg, url, dest, branch, msg): diff --git a/tools/mercurial/hgsetup/wizard.py b/tools/mercurial/hgsetup/wizard.py index 0f99213e39..cb64d50ce7 100644 --- a/tools/mercurial/hgsetup/wizard.py +++ b/tools/mercurial/hgsetup/wizard.py @@ -8,8 +8,9 @@ import difflib import errno import os import shutil +import ssl +import stat import sys -import which import subprocess from distutils.version import LooseVersion @@ -17,12 +18,13 @@ from distutils.version import LooseVersion from configobj import ConfigObjError from StringIO import StringIO -from mozversioncontrol import get_hg_version +from mozversioncontrol import get_hg_path, get_hg_version from .update import MercurialUpdater from .config import ( - HgIncludeException, + config_file, MercurialConfig, + ParseException, ) @@ -40,7 +42,7 @@ are up to date and you won't have to do anything. To begin, press the enter/return key. '''.strip() -OLDEST_NON_LEGACY_VERSION = LooseVersion('3.0') +OLDEST_NON_LEGACY_VERSION = LooseVersion('3.5.2') LEGACY_MERCURIAL = ''' You are running an out of date Mercurial client (%s). @@ -52,11 +54,15 @@ MISSING_USERNAME = ''' You don't have a username defined in your Mercurial config file. In order to send patches to Mozilla, you'll need to attach a name and email address. If you aren't comfortable giving us your full name, pseudonames are acceptable. + +(Relevant config option: ui.username) '''.strip() BAD_DIFF_SETTINGS = ''' Mozilla developers produce patches in a standard format, but your Mercurial is not configured to produce patches in that format. + +(Relevant config options: diff.git, diff.showfunc, diff.unified) '''.strip() MQ_INFO = ''' @@ -66,6 +72,8 @@ alternative to the recommended bookmark-based development workflow. If you are a newcomer to Mercurial or are coming from Git, it is recommended to avoid mq. +(Relevant config option: extensions.mq) + Would you like to activate the mq extension '''.strip() @@ -75,13 +83,17 @@ bzexport that makes it easy to upload patches from the command line via the |hg bzexport| command. More info is available at https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/bzexport/README +(Relevant config option: extensions.bzexport) + Would you like to activate bzexport '''.strip() MQEXT_INFO = ''' -The mqext extension (https://bitbucket.org/sfink/mqext) provides a number of -useful abilities to Mercurial, including automatically committing changes to -your mq patch queue. +The mqext extension adds a number of features, including automatically committing +changes to your mq patch queue. More info is available at +https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/mqext/README.txt + +(Relevant config option: extensions.mqext) Would you like to activate mqext '''.strip() @@ -92,6 +104,8 @@ The qimportbz extension import patches from Bugzilla using a friendly bz:// URL handler. e.g. |hg qimport bz://123456|. +(Relevant config option: extensions.qimportbz) + Would you like to activate qimportbz '''.strip() @@ -106,7 +120,7 @@ Your Mercurial should now be properly configured and recommended extensions should be up to date! '''.strip() -REVIEWBOARD_MINIMUM_VERSION = LooseVersion('3.0.1') +REVIEWBOARD_MINIMUM_VERSION = LooseVersion('3.3') REVIEWBOARD_INCOMPATIBLE = ''' Your Mercurial is too old to use the reviewboard extension, which is necessary @@ -116,26 +130,50 @@ Please upgrade to Mercurial %s or newer to use this extension. '''.strip() MISSING_BUGZILLA_CREDENTIALS = ''' -You do not have your Bugzilla credentials defined in your Mercurial config. +You do not have your Bugzilla API Key defined in your Mercurial config. -Various extensions make use of your Bugzilla credentials to interface with +Various extensions make use of a Bugzilla API Key to interface with Bugzilla to enrich your development experience. -Bugzilla credentials are optional. If you do not provide them, associated -functionality will not be enabled or you will be prompted for your -Bugzilla credentials when they are needed. +The Bugzilla API Key is optional. If you do not provide one, associated +functionality will not be enabled, we will attempt to find a Bugzilla cookie +from a Firefox profile, or you will be prompted for your Bugzilla credentials +when they are needed. + +You should only need to configure a Bugzilla API Key once. '''.lstrip() -BZPOST_MINIMUM_VERSION = LooseVersion('3.0') +BUGZILLA_API_KEY_INSTRUCTIONS = ''' +Bugzilla API Keys can only be obtained through the Bugzilla web interface. + +Please perform the following steps: + + 1) Open https://bugzilla.mozilla.org/userprefs.cgi?tab=apikey + 2) Generate a new API Key + 3) Copy the generated key and paste it here +'''.lstrip() + +LEGACY_BUGZILLA_CREDENTIALS_DETECTED = ''' +Your existing Mercurial config uses a legacy method for defining Bugzilla +credentials. Bugzilla API Keys are the most secure and preferred method +for defining Bugzilla credentials. Bugzilla API Keys are also required +if you have enabled 2 Factor Authentication in Bugzilla. + +All consumers formerly looking at these options should support API Keys. +'''.lstrip() + +BZPOST_MINIMUM_VERSION = LooseVersion('3.3') BZPOST_INFO = ''' The bzpost extension automatically records the URLs of pushed commits to referenced Bugzilla bugs after push. +(Relevant config option: extensions.bzpost) + Would you like to activate bzpost '''.strip() -FIREFOXTREE_MINIMUM_VERSION = LooseVersion('3.0') +FIREFOXTREE_MINIMUM_VERSION = LooseVersion('3.3') FIREFOXTREE_INFO = ''' The firefoxtree extension makes interacting with the multiple Firefox @@ -156,9 +194,107 @@ The firefoxtree extension is *strongly* recommended if you: a) aggregate multiple Firefox repositories into a single local repo b) perform head/bookmark-based development (as opposed to mq) +(Relevant config option: extensions.firefoxtree) + Would you like to activate firefoxtree '''.strip() +PUSHTOTRY_MINIMUM_VERSION = LooseVersion('3.3') + +PUSHTOTRY_INFO = ''' +The push-to-try extension generates a temporary commit with a given +try syntax and pushes it to the try server. The extension is intended +to be used in concert with other tools generating try syntax so that +they can push to try without depending on mq or other workarounds. + +(Relevant config option: extensions.push-to-try) + +Would you like to activate push-to-try +'''.strip() + +CLONEBUNDLES_INFO = ''' +Mercurial 3.6 and hg.mozilla.org support transparently cloning from a CDN, +making clones faster and more reliable. + +(Relevant config option: experimental.clonebundles) + +Would you like to activate this feature and have faster clones +'''.strip() + +BUNDLECLONE_MINIMUM_VERSION = LooseVersion('3.1') + +BUNDLECLONE_INFO = ''' +The bundleclone extension makes cloning faster and saves server resources. + +We highly recommend you activate this extension. + +(Relevant config option: extensions.bundleclone) + +Would you like to activate bundleclone +'''.strip() + +WIP_INFO = ''' +It is common to want a quick view of changesets that are in progress. + +The ``hg wip`` command provides should a view. + +Example Usage: + + $ hg wip + o 4084:fcfa34d0387b dminor @ + | mozreview: use repository name when displaying treeherder results (bug 1230548) r=mcote + | @ 4083:786baf6d476a gps + | | mozreview: create child review requests from batch API + | o 4082:3f100fa4a94f gps + | | mozreview: copy more read-only processing code; r?smacleod + | o 4081:939417680cbe gps + |/ mozreview: add web API to submit an entire series of commits (bug 1229468); r?smacleod + +(Not shown are the colors that help denote the state each changeset +is in.) + +(Relevant config options: alias.wip, revsetalias.wip, templates.wip) + +Would you like to install the `hg wip` alias? +'''.strip() + +HGWATCHMAN_MINIMUM_VERSION = LooseVersion('3.5.2') + +HGWATCHMAN_INFO = ''' +The hgwatchman extension integrates the watchman filesystem watching +tool with Mercurial. Commands like `hg status`, `hg diff`, and +`hg commit` that need to examine filesystem state can query watchman +and obtain filesystem state nearly instantaneously. The result is much +faster command execution. + +When installed, the hgwatchman extension will launch a background +watchman file watching daemon for accessed Mercurial repositories. It +should "just work." + +Would you like to install hgwatchman +'''.strip() + +FILE_PERMISSIONS_WARNING = ''' +Your hgrc file is currently readable by others. + +Sensitive information such as your Bugzilla credentials could be +stolen if others have access to this file/machine. +'''.strip() + +MULTIPLE_VCT = ''' +*** WARNING *** + +Multiple version-control-tools repositories are referenced in your +Mercurial config. Extensions and other code within the +version-control-tools repository could run with inconsistent results. + +Please manually edit the following file to reference a single +version-control-tools repository: + + %s +'''.lstrip() + + class MercurialSetupWizard(object): """Command-line wizard to help users configure Mercurial.""" @@ -168,7 +304,6 @@ class MercurialSetupWizard(object): self.state_dir = os.path.normpath(state_dir) self.ext_dir = os.path.join(self.state_dir, 'mercurial', 'extensions') self.vcs_tools_dir = os.path.join(self.state_dir, 'version-control-tools') - self.update_vcs_tools = False self.updater = MercurialUpdater(state_dir) def run(self, config_paths): @@ -178,35 +313,25 @@ class MercurialSetupWizard(object): if e.errno != errno.EEXIST: raise - # We use subprocess in places, which expects a Win32 executable or - # batch script. On some versions of MozillaBuild, we have "hg.exe", - # "hg.bat," and "hg" (a Python script). "which" will happily return the - # Python script, which will cause subprocess to choke. Explicitly favor - # the Windows version over the plain script. - try: - hg = which.which('hg.exe') - except which.WhichError: - try: - hg = which.which('hg') - except which.WhichError as e: - print(e) - print('Try running |mach bootstrap| to ensure your environment is ' - 'up to date.') - return 1 + hg = get_hg_path() + config_path = config_file(config_paths) try: - c = MercurialConfig(config_paths) + c = MercurialConfig(config_path) except ConfigObjError as e: - print('Error importing existing Mercurial config!\n') + print('Error importing existing Mercurial config: %s\n' % config_path) for error in e.errors: print(error.message) return 1 - except HgIncludeException as e: - print(e.message) + except ParseException as e: + print('Error importing existing Mercurial config: %s\n' % config_path) + print('Line %d: %s' % (e.line, e.message)) return 1 + self.updater.update_all() + print(INITIAL_MESSAGE) raw_input() @@ -246,8 +371,10 @@ class MercurialSetupWizard(object): print('Fixed patch settings.') print('') - self.prompt_native_extension(c, 'progress', - 'Would you like to see progress bars during Mercurial operations') + # Progress is built into core and enabled by default in Mercurial 3.5. + if hg_version < LooseVersion('3.5'): + self.prompt_native_extension(c, 'progress', + 'Would you like to see progress bars during Mercurial operations') self.prompt_native_extension(c, 'color', 'Would you like Mercurial to colorize output to your terminal') @@ -261,9 +388,29 @@ class MercurialSetupWizard(object): 'rewriting via the "histedit" command (similar to ' '`git rebase -i`)') - self.prompt_native_extension(c, 'mq', MQ_INFO) + # hgwatchman is provided by MozillaBuild and we don't yet support + # Linux/BSD. + if ('hgwatchman' not in c.extensions + and sys.platform.startswith('darwin') + and hg_version >= HGWATCHMAN_MINIMUM_VERSION + and self._prompt_yn(HGWATCHMAN_INFO)): + # Unlike other extensions, we need to run an installer + # to compile a Python C extension. + try: + subprocess.check_output( + ['make', 'local'], + cwd=self.updater.hgwatchman_dir, + stderr=subprocess.STDOUT) - self.prompt_external_extension(c, 'bzexport', BZEXPORT_INFO) + ext_path = os.path.join(self.updater.hgwatchman_dir, + 'hgwatchman') + if self.can_use_extension(c, 'hgwatchman', ext_path): + c.activate_extension('hgwatchman', ext_path) + except subprocess.CalledProcessError as e: + print('Error compiling hgwatchman; will not install hgwatchman') + print(e.output) + + self.prompt_native_extension(c, 'mq', MQ_INFO) if 'reviewboard' not in c.extensions: if hg_version < REVIEWBOARD_MINIMUM_VERSION: @@ -277,23 +424,32 @@ class MercurialSetupWizard(object): 'projects', path=p) + self.prompt_external_extension(c, 'bzexport', BZEXPORT_INFO) + if hg_version >= BZPOST_MINIMUM_VERSION: self.prompt_external_extension(c, 'bzpost', BZPOST_INFO) if hg_version >= FIREFOXTREE_MINIMUM_VERSION: self.prompt_external_extension(c, 'firefoxtree', FIREFOXTREE_INFO) - if 'mq' in c.extensions: - self.prompt_external_extension(c, 'mqext', MQEXT_INFO, - os.path.join(self.ext_dir, 'mqext')) + # Functionality from bundleclone is experimental in Mercurial 3.6. + # There was a bug in 3.6, so look for 3.6.1. + if hg_version >= LooseVersion('3.6.1'): + if not c.have_clonebundles() and self._prompt_yn(CLONEBUNDLES_INFO): + c.activate_clonebundles() + print('Enabled the clonebundles feature.\n') + elif hg_version >= BUNDLECLONE_MINIMUM_VERSION: + self.prompt_external_extension(c, 'bundleclone', BUNDLECLONE_INFO) - if 'mqext' in c.extensions: - self.updater.update_mercurial_repo( - hg, - 'https://bitbucket.org/sfink/mqext', - os.path.join(self.ext_dir, 'mqext'), - 'default', - 'Ensuring mqext extension is up to date...') + if hg_version >= PUSHTOTRY_MINIMUM_VERSION: + self.prompt_external_extension(c, 'push-to-try', PUSHTOTRY_INFO) + + if not c.have_wip(): + if self._prompt_yn(WIP_INFO): + c.install_wip_alias() + + if 'mq' in c.extensions: + self.prompt_external_extension(c, 'mqext', MQEXT_INFO) if 'mqext' in c.extensions and not c.have_mqext_autocommit_mq(): if self._prompt_yn('Would you like to configure mqext to ' @@ -312,32 +468,36 @@ class MercurialSetupWizard(object): print('') if 'reviewboard' in c.extensions or 'bzpost' in c.extensions: - bzuser, bzpass = c.get_bugzilla_credentials() + bzuser, bzpass, bzuserid, bzcookie, bzapikey = c.get_bugzilla_credentials() - if not bzuser or not bzpass: + if not bzuser or not bzapikey: print(MISSING_BUGZILLA_CREDENTIALS) if not bzuser: - bzuser = self._prompt('What is your Bugzilla email address?', + bzuser = self._prompt('What is your Bugzilla email address? (optional)', allow_empty=True) - if bzuser and not bzpass: - bzpass = self._prompt('What is your Bugzilla password?', + if bzuser and not bzapikey: + print(BUGZILLA_API_KEY_INSTRUCTIONS) + bzapikey = self._prompt('Please enter a Bugzilla API Key: (optional)', allow_empty=True) - if bzuser or bzpass: - c.set_bugzilla_credentials(bzuser, bzpass) + if bzuser or bzapikey: + c.set_bugzilla_credentials(bzuser, bzapikey) - if self.update_vcs_tools: - self.updater.update_mercurial_repo( - hg, - 'https://hg.mozilla.org/hgcustom/version-control-tools', - self.vcs_tools_dir, - 'default', - 'Ensuring version-control-tools is up to date...') + if bzpass or bzuserid or bzcookie: + print(LEGACY_BUGZILLA_CREDENTIALS_DETECTED) + + # Clear legacy credentials automatically if an API Key is + # found as it supercedes all other credentials. + if bzapikey: + print('The legacy credentials have been removed.\n') + c.clear_legacy_bugzilla_credentials() + elif self._prompt_yn('Remove legacy credentials'): + c.clear_legacy_bugzilla_credentials() # Look for and clean up old extensions. - for ext in {'bzexport', 'qimportbz'}: + for ext in {'bzexport', 'qimportbz', 'mqext'}: path = os.path.join(self.ext_dir, ext) if os.path.exists(path): if self._prompt_yn('Would you like to remove the old and no ' @@ -345,7 +505,32 @@ class MercurialSetupWizard(object): print('Cleaning up old repository: %s' % path) shutil.rmtree(path) - c.add_mozilla_host_fingerprints() + # Python + Mercurial didn't have terrific TLS handling until Python + # 2.7.9 and Mercurial 3.4. For this reason, it was recommended to pin + # certificates in Mercurial config files. In modern versions of + # Mercurial, the system CA store is used and old, legacy TLS protocols + # are disabled. The default connection/security setting should + # be sufficient and pinning certificates is no longer needed. + have_modern_ssl = hasattr(ssl, 'SSLContext') + if hg_version < LooseVersion('3.4') or not have_modern_ssl: + c.add_mozilla_host_fingerprints() + + # References to multiple version-control-tools checkouts can confuse + # version-control-tools, since various Mercurial extensions resolve + # dependencies via __file__ and repos could reference another copy. + seen_vct = set() + for k, v in c.config.get('extensions', {}).items(): + if 'version-control-tools' not in v: + continue + + i = v.index('version-control-tools') + vct = v[0:i + len('version-control-tools')] + seen_vct.add(os.path.realpath(os.path.expanduser(vct))) + + if len(seen_vct) > 1: + print(MULTIPLE_VCT % c.config_path) + + # At this point the config should be finalized. b = StringIO() c.write(b) @@ -379,6 +564,20 @@ class MercurialSetupWizard(object): c.write(sys.stdout) return 1 + if sys.platform != 'win32': + # Config file may contain sensitive content, such as passwords. + # Prompt to remove global permissions. + mode = os.stat(config_path).st_mode + if mode & (stat.S_IRWXG | stat.S_IRWXO): + print(FILE_PERMISSIONS_WARNING) + if self._prompt_yn('Remove permissions for others to ' + 'read your hgrc file'): + # We don't care about sticky and set UID bits because + # this is a regular file. + mode = mode & stat.S_IRWXU + print('Changing permissions of %s' % config_path) + os.chmod(config_path, mode) + print(FINISHED) return 0 @@ -398,7 +597,7 @@ class MercurialSetupWizard(object): '--config', 'extensions.testmodule=%s' % path, '--config', 'ui.traceback=true'], stderr=subprocess.STDOUT) - return "Traceback" not in result + return b"Traceback" not in result def prompt_external_extension(self, c, name, prompt_text, path=None): # Ask the user if the specified extension should be enabled. Defaults @@ -414,11 +613,13 @@ class MercurialSetupWizard(object): if not self._prompt_yn(prompt_text): print('') return - print('Activated %s extension.\n' % name) if not path: - path = os.path.join(self.vcs_tools_dir, 'hgext', name) - self.update_vcs_tools = True + # We replace the user's home directory with ~ so the + # config file doesn't depend on the path to the home + # directory + path = os.path.join(self.vcs_tools_dir.replace(os.path.expanduser('~'), '~'), 'hgext', name) c.activate_extension(name, path) + print('Activated %s extension.\n' % name) def _prompt(self, msg, allow_empty=False): print(msg) diff --git a/tools/mercurial/mach_commands.py b/tools/mercurial/mach_commands.py index ddba279148..fcc39419c4 100644 --- a/tools/mercurial/mach_commands.py +++ b/tools/mercurial/mach_commands.py @@ -35,7 +35,7 @@ class VersionControlCommands(object): if update_only: from hgsetup.update import MercurialUpdater updater = MercurialUpdater(self._context.state_dir) - result = updater.update_all(map(os.path.expanduser, config_paths)) + result = updater.update_all() else: from hgsetup.wizard import MercurialSetupWizard wizard = MercurialSetupWizard(self._context.state_dir) diff --git a/tools/profiler/core/EHABIStackWalk.cpp b/tools/profiler/core/EHABIStackWalk.cpp new file mode 100644 index 0000000000..20f4663a74 --- /dev/null +++ b/tools/profiler/core/EHABIStackWalk.cpp @@ -0,0 +1,678 @@ +/* -*- 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/. */ + +/* + * This is an implementation of stack unwinding according to a subset + * of the ARM Exception Handling ABI, as described in: + * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf + * + * This handles only the ARM-defined "personality routines" (chapter + * 9), and don't track the value of FP registers, because profiling + * needs only chain of PC/SP values. + * + * Because the exception handling info may not be accurate for all + * possible places where an async signal could occur (e.g., in a + * prologue or epilogue), this bounds-checks all stack accesses. + * + * This file uses "struct" for structures in the exception tables and + * "class" otherwise. We should avoid violating the C++11 + * standard-layout rules in the former. + */ + +#include "EHABIStackWalk.h" + +#include "shared-libraries.h" +#include "platform.h" + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Endian.h" + +#include +#include +#include +#include +#include + +#ifndef PT_ARM_EXIDX +#define PT_ARM_EXIDX 0x70000001 +#endif + +// Bug 1082817: ICS B2G has a buggy linker that doesn't always ensure +// that the EXIDX is sorted by address, as the spec requires. So in +// that case we build and sort an array of pointers into the index, +// and binary-search that; otherwise, we search the index in place +// (avoiding the time and space overhead of the indirection). +#if defined(ANDROID_VERSION) && ANDROID_VERSION < 16 +#define HAVE_UNSORTED_EXIDX +#endif + +namespace mozilla { + +struct PRel31 { + uint32_t mBits; + bool topBit() const { return mBits & 0x80000000; } + uint32_t value() const { return mBits & 0x7fffffff; } + int32_t offset() const { return (static_cast(mBits) << 1) >> 1; } + const void *compute() const { + return reinterpret_cast(this) + offset(); + } +private: + PRel31(const PRel31 &copied) = delete; + PRel31() = delete; +}; + +struct EHEntry { + PRel31 startPC; + PRel31 exidx; +private: + EHEntry(const EHEntry &copied) = delete; + EHEntry() = delete; +}; + +class EHState { + // Note that any core register can be used as a "frame pointer" to + // influence the unwinding process, so this must track all of them. + uint32_t mRegs[16]; +public: + bool unwind(const EHEntry *aEntry, const void *stackBase); + uint32_t &operator[](int i) { return mRegs[i]; } + const uint32_t &operator[](int i) const { return mRegs[i]; } + EHState(const mcontext_t &); +}; + +enum { + R_SP = 13, + R_LR = 14, + R_PC = 15 +}; + +#ifdef HAVE_UNSORTED_EXIDX +class EHEntryHandle { + const EHEntry *mValue; +public: + EHEntryHandle(const EHEntry *aEntry) : mValue(aEntry) { } + const EHEntry *value() const { return mValue; } +}; + +bool operator<(const EHEntryHandle &lhs, const EHEntryHandle &rhs) { + return lhs.value()->startPC.compute() < rhs.value()->startPC.compute(); +} +#endif + +class EHTable { + uint32_t mStartPC; + uint32_t mEndPC; + uint32_t mLoadOffset; +#ifdef HAVE_UNSORTED_EXIDX + // In principle we should be able to binary-search the index section in + // place, but the ICS toolchain's linker is noncompliant and produces + // indices that aren't entirely sorted (e.g., libc). So we have this: + std::vector mEntries; + typedef std::vector::const_iterator EntryIterator; + EntryIterator entriesBegin() const { return mEntries.begin(); } + EntryIterator entriesEnd() const { return mEntries.end(); } + static const EHEntry* entryGet(EntryIterator aEntry) { + return aEntry->value(); + } +#else + typedef const EHEntry *EntryIterator; + EntryIterator mEntriesBegin, mEntriesEnd; + EntryIterator entriesBegin() const { return mEntriesBegin; } + EntryIterator entriesEnd() const { return mEntriesEnd; } + static const EHEntry* entryGet(EntryIterator aEntry) { return aEntry; } +#endif + std::string mName; +public: + EHTable(const void *aELF, size_t aSize, const std::string &aName); + const EHEntry *lookup(uint32_t aPC) const; + bool isValid() const { return entriesEnd() != entriesBegin(); } + const std::string &name() const { return mName; } + uint32_t startPC() const { return mStartPC; } + uint32_t endPC() const { return mEndPC; } + uint32_t loadOffset() const { return mLoadOffset; } +}; + +class EHAddrSpace { + std::vector mStarts; + std::vector mTables; + static mozilla::Atomic sCurrent; +public: + explicit EHAddrSpace(const std::vector& aTables); + const EHTable *lookup(uint32_t aPC) const; + static void Update(); + static const EHAddrSpace *Get(); +}; + + +void EHABIStackWalkInit() +{ + EHAddrSpace::Update(); +} + +size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase, + void **aSPs, void **aPCs, const size_t aNumFrames) +{ + const EHAddrSpace *space = EHAddrSpace::Get(); + EHState state(aContext); + size_t count = 0; + + while (count < aNumFrames) { + uint32_t pc = state[R_PC], sp = state[R_SP]; + aPCs[count] = reinterpret_cast(pc); + aSPs[count] = reinterpret_cast(sp); + count++; + + if (!space) + break; + // TODO: cache these lookups. Binary-searching libxul is + // expensive (possibly more expensive than doing the actual + // unwind), and even a small cache should help. + const EHTable *table = space->lookup(pc); + if (!table) + break; + const EHEntry *entry = table->lookup(pc); + if (!entry) + break; + if (!state.unwind(entry, stackBase)) + break; + } + + return count; +} + + +class EHInterp { +public: + // Note that stackLimit is exclusive and stackBase is inclusive + // (i.e, stackLimit < SP <= stackBase), following the convention + // set by the AAPCS spec. + EHInterp(EHState &aState, const EHEntry *aEntry, + uint32_t aStackLimit, uint32_t aStackBase) + : mState(aState), + mStackLimit(aStackLimit), + mStackBase(aStackBase), + mNextWord(0), + mWordsLeft(0), + mFailed(false) + { + const PRel31 &exidx = aEntry->exidx; + uint32_t firstWord; + + if (exidx.mBits == 1) { // EXIDX_CANTUNWIND + mFailed = true; + return; + } + if (exidx.topBit()) { + firstWord = exidx.mBits; + } else { + mNextWord = reinterpret_cast(exidx.compute()); + firstWord = *mNextWord++; + } + + switch (firstWord >> 24) { + case 0x80: // short + mWord = firstWord << 8; + mBytesLeft = 3; + break; + case 0x81: case 0x82: // long; catch descriptor size ignored + mWord = firstWord << 16; + mBytesLeft = 2; + mWordsLeft = (firstWord >> 16) & 0xff; + break; + default: + // unknown personality + mFailed = true; + } + } + + bool unwind(); + +private: + // TODO: GCC has been observed not CSEing repeated reads of + // mState[R_SP] with writes to mFailed between them, suggesting that + // it hasn't determined that they can't alias and is thus missing + // optimization opportunities. So, we may want to flatten EHState + // into this class; this may also make the code simpler. + EHState &mState; + uint32_t mStackLimit; + uint32_t mStackBase; + const uint32_t *mNextWord; + uint32_t mWord; + uint8_t mWordsLeft; + uint8_t mBytesLeft; + bool mFailed; + + enum { + I_ADDSP = 0x00, // 0sxxxxxx (subtract if s) + M_ADDSP = 0x80, + I_POPMASK = 0x80, // 1000iiii iiiiiiii (if any i set) + M_POPMASK = 0xf0, + I_MOVSP = 0x90, // 1001nnnn + M_MOVSP = 0xf0, + I_POPN = 0xa0, // 1010lnnn + M_POPN = 0xf0, + I_FINISH = 0xb0, // 10110000 + I_POPLO = 0xb1, // 10110001 0000iiii (if any i set) + I_ADDSPBIG = 0xb2, // 10110010 uleb128 + I_POPFDX = 0xb3, // 10110011 sssscccc + I_POPFDX8 = 0xb8, // 10111nnn + M_POPFDX8 = 0xf8, + // "Intel Wireless MMX" extensions omitted. + I_POPFDD = 0xc8, // 1100100h sssscccc + M_POPFDD = 0xfe, + I_POPFDD8 = 0xd0, // 11010nnn + M_POPFDD8 = 0xf8 + }; + + uint8_t next() { + if (mBytesLeft == 0) { + if (mWordsLeft == 0) { + return I_FINISH; + } + mWordsLeft--; + mWord = *mNextWord++; + mBytesLeft = 4; + } + mBytesLeft--; + mWord = (mWord << 8) | (mWord >> 24); // rotate + return mWord; + } + + uint32_t &vSP() { return mState[R_SP]; } + uint32_t *ptrSP() { return reinterpret_cast(vSP()); } + + void checkStackBase() { if (vSP() > mStackBase) mFailed = true; } + void checkStackLimit() { if (vSP() <= mStackLimit) mFailed = true; } + void checkStackAlign() { if ((vSP() & 3) != 0) mFailed = true; } + void checkStack() { + checkStackBase(); + checkStackLimit(); + checkStackAlign(); + } + + void popRange(uint8_t first, uint8_t last, uint16_t mask) { + bool hasSP = false; + uint32_t tmpSP; + if (mask == 0) + mFailed = true; + for (uint8_t r = first; r <= last; ++r) { + if (mask & 1) { + if (r == R_SP) { + hasSP = true; + tmpSP = *ptrSP(); + } else + mState[r] = *ptrSP(); + vSP() += 4; + checkStackBase(); + if (mFailed) + return; + } + mask >>= 1; + } + if (hasSP) { + vSP() = tmpSP; + checkStack(); + } + } +}; + + +bool EHState::unwind(const EHEntry *aEntry, const void *stackBasePtr) { + // The unwinding program cannot set SP to less than the initial value. + uint32_t stackLimit = mRegs[R_SP] - 4; + uint32_t stackBase = reinterpret_cast(stackBasePtr); + EHInterp interp(*this, aEntry, stackLimit, stackBase); + return interp.unwind(); +} + +bool EHInterp::unwind() { + mState[R_PC] = 0; + checkStack(); + while (!mFailed) { + uint8_t insn = next(); +#if DEBUG_EHABI_UNWIND + LOGF("unwind insn = %02x", (unsigned)insn); +#endif + // Try to put the common cases first. + + // 00xxxxxx: vsp = vsp + (xxxxxx << 2) + 4 + // 01xxxxxx: vsp = vsp - (xxxxxx << 2) - 4 + if ((insn & M_ADDSP) == I_ADDSP) { + uint32_t offset = ((insn & 0x3f) << 2) + 4; + if (insn & 0x40) { + vSP() -= offset; + checkStackLimit(); + } else { + vSP() += offset; + checkStackBase(); + } + continue; + } + + // 10100nnn: Pop r4-r[4+nnn] + // 10101nnn: Pop r4-r[4+nnn], r14 + if ((insn & M_POPN) == I_POPN) { + uint8_t n = (insn & 0x07) + 1; + bool lr = insn & 0x08; + uint32_t *ptr = ptrSP(); + vSP() += (n + (lr ? 1 : 0)) * 4; + checkStackBase(); + for (uint8_t r = 4; r < 4 + n; ++r) + mState[r] = *ptr++; + if (lr) + mState[R_LR] = *ptr++; + continue; + } + + // 1011000: Finish + if (insn == I_FINISH) { + if (mState[R_PC] == 0) { + mState[R_PC] = mState[R_LR]; + // Non-standard change (bug 916106): Prevent the caller from + // re-using LR. Since the caller is by definition not a leaf + // routine, it will have to restore LR from somewhere to + // return to its own caller, so we can safely zero it here. + // This makes a difference only if an error in unwinding + // (e.g., caused by starting from within a prologue/epilogue) + // causes us to load a pointer to a leaf routine as LR; if we + // don't do something, we'll go into an infinite loop of + // "returning" to that same function. + mState[R_LR] = 0; + } + return true; + } + + // 1001nnnn: Set vsp = r[nnnn] + if ((insn & M_MOVSP) == I_MOVSP) { + vSP() = mState[insn & 0x0f]; + checkStack(); + continue; + } + + // 11001000 sssscccc: Pop VFP regs D[16+ssss]-D[16+ssss+cccc] (as FLDMFDD) + // 11001001 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDD) + if ((insn & M_POPFDD) == I_POPFDD) { + uint8_t n = (next() & 0x0f) + 1; + // Note: if the 16+ssss+cccc > 31, the encoding is reserved. + // As the space is currently unused, we don't try to check. + vSP() += 8 * n; + checkStackBase(); + continue; + } + + // 11010nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDD) + if ((insn & M_POPFDD8) == I_POPFDD8) { + uint8_t n = (insn & 0x07) + 1; + vSP() += 8 * n; + checkStackBase(); + continue; + } + + // 10110010 uleb128: vsp = vsp + 0x204 + (uleb128 << 2) + if (insn == I_ADDSPBIG) { + uint32_t acc = 0; + uint8_t shift = 0; + uint8_t byte; + do { + if (shift >= 32) + return false; + byte = next(); + acc |= (byte & 0x7f) << shift; + shift += 7; + } while (byte & 0x80); + uint32_t offset = 0x204 + (acc << 2); + // The calculations above could have overflowed. + // But the one we care about is this: + if (vSP() + offset < vSP()) + mFailed = true; + vSP() += offset; + // ...so that this is the only other check needed: + checkStackBase(); + continue; + } + + // 1000iiii iiiiiiii (i not all 0): Pop under masks {r15-r12}, {r11-r4} + if ((insn & M_POPMASK) == I_POPMASK) { + popRange(4, 15, ((insn & 0x0f) << 8) | next()); + continue; + } + + // 1011001 0000iiii (i not all 0): Pop under mask {r3-r0} + if (insn == I_POPLO) { + popRange(0, 3, next() & 0x0f); + continue; + } + + // 10110011 sssscccc: Pop VFP regs D[ssss]-D[ssss+cccc] (as FLDMFDX) + if (insn == I_POPFDX) { + uint8_t n = (next() & 0x0f) + 1; + vSP() += 8 * n + 4; + checkStackBase(); + continue; + } + + // 10111nnn: Pop VFP regs D[8]-D[8+nnn] (as FLDMFDX) + if ((insn & M_POPFDX8) == I_POPFDX8) { + uint8_t n = (insn & 0x07) + 1; + vSP() += 8 * n + 4; + checkStackBase(); + continue; + } + + // unhandled instruction +#ifdef DEBUG_EHABI_UNWIND + LOGF("Unhandled EHABI instruction 0x%02x", insn); +#endif + mFailed = true; + } + return false; +} + + +bool operator<(const EHTable &lhs, const EHTable &rhs) { + return lhs.startPC() < rhs.endPC(); +} + +// Async signal unsafe. +EHAddrSpace::EHAddrSpace(const std::vector& aTables) + : mTables(aTables) +{ + std::sort(mTables.begin(), mTables.end()); + DebugOnly lastEnd = 0; + for (std::vector::iterator i = mTables.begin(); + i != mTables.end(); ++i) { + MOZ_ASSERT(i->startPC() >= lastEnd); + mStarts.push_back(i->startPC()); + lastEnd = i->endPC(); + } +} + +const EHTable *EHAddrSpace::lookup(uint32_t aPC) const { + ptrdiff_t i = (std::upper_bound(mStarts.begin(), mStarts.end(), aPC) + - mStarts.begin()) - 1; + + if (i < 0 || aPC >= mTables[i].endPC()) + return 0; + return &mTables[i]; +} + + +const EHEntry *EHTable::lookup(uint32_t aPC) const { + MOZ_ASSERT(aPC >= mStartPC); + if (aPC >= mEndPC) + return nullptr; + + EntryIterator begin = entriesBegin(); + EntryIterator end = entriesEnd(); + MOZ_ASSERT(begin < end); + if (aPC < reinterpret_cast(entryGet(begin)->startPC.compute())) + return nullptr; + + while (end - begin > 1) { +#ifdef EHABI_UNWIND_MORE_ASSERTS + if (entryGet(end - 1)->startPC.compute() + < entryGet(begin)->startPC.compute()) { + MOZ_CRASH("unsorted exidx"); + } +#endif + EntryIterator mid = begin + (end - begin) / 2; + if (aPC < reinterpret_cast(entryGet(mid)->startPC.compute())) + end = mid; + else + begin = mid; + } + return entryGet(begin); +} + + +#if MOZ_LITTLE_ENDIAN +static const unsigned char hostEndian = ELFDATA2LSB; +#elif MOZ_BIG_ENDIAN +static const unsigned char hostEndian = ELFDATA2MSB; +#else +#error "No endian?" +#endif + +// Async signal unsafe: std::vector::reserve, std::string copy ctor. +EHTable::EHTable(const void *aELF, size_t aSize, const std::string &aName) + : mStartPC(~0), // largest uint32_t + mEndPC(0), +#ifndef HAVE_UNSORTED_EXIDX + mEntriesBegin(nullptr), + mEntriesEnd(nullptr), +#endif + mName(aName) +{ + const uint32_t base = reinterpret_cast(aELF); + + if (aSize < sizeof(Elf32_Ehdr)) + return; + + const Elf32_Ehdr &file = *(reinterpret_cast(base)); + if (memcmp(&file.e_ident[EI_MAG0], ELFMAG, SELFMAG) != 0 || + file.e_ident[EI_CLASS] != ELFCLASS32 || + file.e_ident[EI_DATA] != hostEndian || + file.e_ident[EI_VERSION] != EV_CURRENT || + file.e_ident[EI_OSABI] != ELFOSABI_SYSV || +#ifdef EI_ABIVERSION + file.e_ident[EI_ABIVERSION] != 0 || +#endif + file.e_machine != EM_ARM || + file.e_version != EV_CURRENT) + // e_flags? + return; + + MOZ_ASSERT(file.e_phoff + file.e_phnum * file.e_phentsize <= aSize); + const Elf32_Phdr *exidxHdr = 0, *zeroHdr = 0; + for (unsigned i = 0; i < file.e_phnum; ++i) { + const Elf32_Phdr &phdr = + *(reinterpret_cast(base + file.e_phoff + + i * file.e_phentsize)); + if (phdr.p_type == PT_ARM_EXIDX) { + exidxHdr = &phdr; + } else if (phdr.p_type == PT_LOAD) { + if (phdr.p_offset == 0) { + zeroHdr = &phdr; + } + if (phdr.p_flags & PF_X) { + mStartPC = std::min(mStartPC, phdr.p_vaddr); + mEndPC = std::max(mEndPC, phdr.p_vaddr + phdr.p_memsz); + } + } + } + if (!exidxHdr) + return; + if (!zeroHdr) + return; + mLoadOffset = base - zeroHdr->p_vaddr; + mStartPC += mLoadOffset; + mEndPC += mLoadOffset; + + // Create a sorted index of the index to work around linker bugs. + const EHEntry *startTable = + reinterpret_cast(mLoadOffset + exidxHdr->p_vaddr); + const EHEntry *endTable = + reinterpret_cast(mLoadOffset + exidxHdr->p_vaddr + + exidxHdr->p_memsz); +#ifdef HAVE_UNSORTED_EXIDX + mEntries.reserve(endTable - startTable); + for (const EHEntry *i = startTable; i < endTable; ++i) + mEntries.push_back(i); + std::sort(mEntries.begin(), mEntries.end()); +#else + mEntriesBegin = startTable; + mEntriesEnd = endTable; +#endif +} + + +mozilla::Atomic EHAddrSpace::sCurrent(nullptr); + +// Async signal safe; can fail if Update() hasn't returned yet. +const EHAddrSpace *EHAddrSpace::Get() { + return sCurrent; +} + +// Collect unwinding information from loaded objects. Calls after the +// first have no effect. Async signal unsafe. +void EHAddrSpace::Update() { + const EHAddrSpace *space = sCurrent; + if (space) + return; + + SharedLibraryInfo info = SharedLibraryInfo::GetInfoForSelf(); + std::vector tables; + + for (size_t i = 0; i < info.GetSize(); ++i) { + const SharedLibrary &lib = info.GetEntry(i); + if (lib.GetOffset() != 0) + // TODO: if it has a name, and we haven't seen a mapping of + // offset 0 for that file, try opening it and reading the + // headers instead. The only thing I've seen so far that's + // linked so as to need that treatment is the dynamic linker + // itself. + continue; + EHTable tab(reinterpret_cast(lib.GetStart()), + lib.GetEnd() - lib.GetStart(), lib.GetName()); + if (tab.isValid()) + tables.push_back(tab); + } + space = new EHAddrSpace(tables); + + if (!sCurrent.compareExchange(nullptr, space)) { + delete space; + space = sCurrent; + } +} + + +EHState::EHState(const mcontext_t &context) { +#ifdef linux + mRegs[0] = context.arm_r0; + mRegs[1] = context.arm_r1; + mRegs[2] = context.arm_r2; + mRegs[3] = context.arm_r3; + mRegs[4] = context.arm_r4; + mRegs[5] = context.arm_r5; + mRegs[6] = context.arm_r6; + mRegs[7] = context.arm_r7; + mRegs[8] = context.arm_r8; + mRegs[9] = context.arm_r9; + mRegs[10] = context.arm_r10; + mRegs[11] = context.arm_fp; + mRegs[12] = context.arm_ip; + mRegs[13] = context.arm_sp; + mRegs[14] = context.arm_lr; + mRegs[15] = context.arm_pc; +#else +# error "Unhandled OS for ARM EHABI unwinding" +#endif +} + +} // namespace mozilla + diff --git a/tools/profiler/core/EHABIStackWalk.h b/tools/profiler/core/EHABIStackWalk.h new file mode 100644 index 0000000000..5529d9511f --- /dev/null +++ b/tools/profiler/core/EHABIStackWalk.h @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +/* + * This is an implementation of stack unwinding according to a subset + * of the ARM Exception Handling ABI; see the comment at the top of + * the .cpp file for details. + */ + +#ifndef mozilla_EHABIStackWalk_h__ +#define mozilla_EHABIStackWalk_h__ + +#include +#include + +namespace mozilla { + +void EHABIStackWalkInit(); + +size_t EHABIStackWalk(const mcontext_t &aContext, void *stackBase, + void **aSPs, void **aPCs, size_t aNumFrames); + +} + +#endif diff --git a/tools/profiler/core/GeckoSampler.cpp b/tools/profiler/core/GeckoSampler.cpp index 42f71e335f..7959de3c44 100644 --- a/tools/profiler/core/GeckoSampler.cpp +++ b/tools/profiler/core/GeckoSampler.cpp @@ -971,6 +971,75 @@ void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSampl } #endif + +#ifdef USE_EHABI_STACKWALK +void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) +{ + void *pc_array[1000]; + void *sp_array[1000]; + NativeStack nativeStack = { + pc_array, + sp_array, + mozilla::ArrayLength(pc_array), + 0 + }; + + const mcontext_t *mcontext = &reinterpret_cast(aSample->context)->uc_mcontext; + mcontext_t savedContext; + PseudoStack *pseudoStack = aProfile.GetPseudoStack(); + + nativeStack.count = 0; + // The pseudostack contains an "EnterJIT" frame whenever we enter + // JIT code with profiling enabled; the stack pointer value points + // the saved registers. We use this to unwind resume unwinding + // after encounting JIT code. + for (uint32_t i = pseudoStack->stackSize(); i > 0; --i) { + // The pseudostack grows towards higher indices, so we iterate + // backwards (from callee to caller). + volatile StackEntry &entry = pseudoStack->mStack[i - 1]; + if (!entry.isJs() && strcmp(entry.label(), "EnterJIT") == 0) { + // Found JIT entry frame. Unwind up to that point (i.e., force + // the stack walk to stop before the block of saved registers; + // note that it yields nondecreasing stack pointers), then restore + // the saved state. + uint32_t *vSP = reinterpret_cast(entry.stackAddress()); + + nativeStack.count += EHABIStackWalk(*mcontext, + /* stackBase = */ vSP, + sp_array + nativeStack.count, + pc_array + nativeStack.count, + nativeStack.size - nativeStack.count); + + memset(&savedContext, 0, sizeof(savedContext)); + // See also: struct EnterJITStack in js/src/jit/arm/Trampoline-arm.cpp + savedContext.arm_r4 = *vSP++; + savedContext.arm_r5 = *vSP++; + savedContext.arm_r6 = *vSP++; + savedContext.arm_r7 = *vSP++; + savedContext.arm_r8 = *vSP++; + savedContext.arm_r9 = *vSP++; + savedContext.arm_r10 = *vSP++; + savedContext.arm_fp = *vSP++; + savedContext.arm_lr = *vSP++; + savedContext.arm_sp = reinterpret_cast(vSP); + savedContext.arm_pc = savedContext.arm_lr; + mcontext = &savedContext; + } + } + + // Now unwind whatever's left (starting from either the last EnterJIT + // frame or, if no EnterJIT was found, the original registers). + nativeStack.count += EHABIStackWalk(*mcontext, + aProfile.GetStackTop(), + sp_array + nativeStack.count, + pc_array + nativeStack.count, + nativeStack.size - nativeStack.count); + + mergeStacksIntoProfile(aProfile, aSample, nativeStack); +} +#endif + + #ifdef USE_LUL_STACKWALK void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSample) { @@ -1075,6 +1144,7 @@ void GeckoSampler::doNativeBacktrace(ThreadProfile &aProfile, TickSample* aSampl } #endif + static void doSampleStackTrace(ThreadProfile &aProfile, TickSample *aSample, bool aAddLeafAddresses) { @@ -1110,7 +1180,8 @@ void GeckoSampler::InplaceTick(TickSample* sample) PseudoStack* stack = currThreadProfile.GetPseudoStack(); -#if defined(USE_NS_STACKWALK) +#if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) || \ + defined(USE_LUL_STACKWALK) if (mUseStackWalk) { doNativeBacktrace(currThreadProfile, sample); } else { @@ -1177,7 +1248,7 @@ SyncProfile* NewSyncProfile() return profile; } -} // anonymous namespace +} // namespace SyncProfile* GeckoSampler::GetBacktrace() { diff --git a/tools/profiler/core/GeckoSampler.h b/tools/profiler/core/GeckoSampler.h index e8598499a4..aaba3961c0 100644 --- a/tools/profiler/core/GeckoSampler.h +++ b/tools/profiler/core/GeckoSampler.h @@ -7,6 +7,7 @@ #define GeckoSampler_h #include "platform.h" +#include "ProfileEntry.h" #include "mozilla/Vector.h" #include "ThreadProfile.h" #include "ThreadInfo.h" diff --git a/tools/profiler/core/ProfileEntry.cpp b/tools/profiler/core/ProfileEntry.cpp index 166c1a4a4c..ddf2a7d970 100644 --- a/tools/profiler/core/ProfileEntry.cpp +++ b/tools/profiler/core/ProfileEntry.cpp @@ -177,27 +177,101 @@ public: } }; +// As mentioned in ProfileEntry.h, the JSON format contains many arrays whose +// elements are laid out according to various schemas to help +// de-duplication. This RAII class helps write these arrays by keeping track of +// the last non-null element written and adding the appropriate number of null +// elements when writing new non-null elements. It also automatically opens and +// closes an array element on the given JSON writer. +// +// Example usage: +// +// // Define the schema of elements in this type of array: [FOO, BAR, BAZ] +// enum Schema : uint32_t { +// FOO = 0, +// BAR = 1, +// BAZ = 2 +// }; +// +// AutoArraySchemaWriter writer(someJsonWriter, someUniqueStrings); +// if (shouldWriteFoo) { +// writer.IntElement(FOO, getFoo()); +// } +// ... etc ... +class MOZ_RAII AutoArraySchemaWriter +{ + friend class AutoObjectWriter; + + SpliceableJSONWriter& mJSONWriter; + UniqueJSONStrings* mStrings; + uint32_t mNextFreeIndex; + +public: + AutoArraySchemaWriter(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aStrings) + : mJSONWriter(aWriter) + , mStrings(&aStrings) + , mNextFreeIndex(0) + { + mJSONWriter.StartArrayElement(); + } + + // If you don't have access to a UniqueStrings, you had better not try and + // write a string element down the line! + explicit AutoArraySchemaWriter(SpliceableJSONWriter& aWriter) + : mJSONWriter(aWriter) + , mStrings(nullptr) + , mNextFreeIndex(0) + { + mJSONWriter.StartArrayElement(); + } + + ~AutoArraySchemaWriter() { + mJSONWriter.EndArray(); + } + + void FillUpTo(uint32_t aIndex) { + MOZ_ASSERT(aIndex >= mNextFreeIndex); + mJSONWriter.NullElements(aIndex - mNextFreeIndex); + mNextFreeIndex = aIndex + 1; + } + + void IntElement(uint32_t aIndex, uint32_t aValue) { + FillUpTo(aIndex); + mJSONWriter.IntElement(aValue); + } + + void DoubleElement(uint32_t aIndex, double aValue) { + FillUpTo(aIndex); + mJSONWriter.DoubleElement(aValue); + } + + void StringElement(uint32_t aIndex, const char* aValue) { + MOZ_RELEASE_ASSERT(mStrings); + FillUpTo(aIndex); + mStrings->WriteElement(mJSONWriter, aValue); + } +}; + class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp { - JSONWriter& mWriter; + SpliceableJSONWriter& mWriter; UniqueJSONStrings& mUniqueStrings; public: - StreamOptimizationAttemptsOp(JSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings) + StreamOptimizationAttemptsOp(SpliceableJSONWriter& aWriter, UniqueJSONStrings& aUniqueStrings) : mWriter(aWriter), mUniqueStrings(aUniqueStrings) { } void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override { - // Schema: - // [strategy, outcome] + enum Schema : uint32_t { + STRATEGY = 0, + OUTCOME = 1 + }; - mWriter.StartArrayElement(); - { - mUniqueStrings.WriteElement(mWriter, JS::TrackedStrategyString(strategy)); - mUniqueStrings.WriteElement(mWriter, JS::TrackedOutcomeString(outcome)); - } - mWriter.EndArray(); + AutoArraySchemaWriter writer(mWriter, mUniqueStrings); + writer.StringElement(STRATEGY, JS::TrackedStrategyString(strategy)); + writer.StringElement(OUTCOME, JS::TrackedOutcomeString(outcome)); } }; @@ -432,64 +506,63 @@ void UniqueStacks::SpliceStackTableElements(SpliceableJSONWriter& aWriter) void UniqueStacks::StreamStack(const StackKey& aStack) { - // Schema: - // [prefix, frame] + enum Schema : uint32_t { + PREFIX = 0, + FRAME = 1 + }; - mStackTableWriter.StartArrayElement(); - { - if (aStack.mPrefix.isSome()) { - mStackTableWriter.IntElement(*aStack.mPrefix); - } else { - mStackTableWriter.NullElement(); - } - mStackTableWriter.IntElement(aStack.mFrame); + AutoArraySchemaWriter writer(mStackTableWriter, mUniqueStrings); + if (aStack.mPrefix.isSome()) { + writer.IntElement(PREFIX, *aStack.mPrefix); } - mStackTableWriter.EndArray(); + writer.IntElement(FRAME, aStack.mFrame); } void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame) { - // Schema: - // [location, implementation, optimizations, line, category] + enum Schema : uint32_t { + LOCATION = 0, + IMPLEMENTATION = 1, + OPTIMIZATIONS = 2, + LINE = 3, + CATEGORY = 4 + }; + + AutoArraySchemaWriter writer(mFrameTableWriter, mUniqueStrings); - mFrameTableWriter.StartArrayElement(); #ifndef SPS_STANDALONE if (!aFrame.mJITFrameHandle) { #else { #endif #ifdef SPS_STANDALONE - mUniqueStrings.WriteElement(mFrameTableWriter, aFrame.mLocation.c_str()); + writer.StringElement(LOCATION, aFrame.mLocation.c_str()); #else - mUniqueStrings.WriteElement(mFrameTableWriter, aFrame.mLocation.get()); + writer.StringElement(LOCATION, aFrame.mLocation.get()); #endif if (aFrame.mLine.isSome()) { - mFrameTableWriter.NullElement(); // implementation - mFrameTableWriter.NullElement(); // optimizations - mFrameTableWriter.IntElement(*aFrame.mLine); + writer.IntElement(LINE, *aFrame.mLine); } if (aFrame.mCategory.isSome()) { - if (aFrame.mLine.isNothing()) { - mFrameTableWriter.NullElement(); // line - } - mFrameTableWriter.IntElement(*aFrame.mCategory); + writer.IntElement(CATEGORY, *aFrame.mCategory); } } #ifndef SPS_STANDALONE else { const JS::ForEachProfiledFrameOp::FrameHandle& jitFrame = *aFrame.mJITFrameHandle; - mUniqueStrings.WriteElement(mFrameTableWriter, jitFrame.label()); + writer.StringElement(LOCATION, jitFrame.label()); JS::ProfilingFrameIterator::FrameKind frameKind = jitFrame.frameKind(); MOZ_ASSERT(frameKind == JS::ProfilingFrameIterator::Frame_Ion || frameKind == JS::ProfilingFrameIterator::Frame_Baseline); - mUniqueStrings.WriteElement(mFrameTableWriter, - frameKind == JS::ProfilingFrameIterator::Frame_Ion - ? "ion" - : "baseline"); + writer.StringElement(IMPLEMENTATION, + frameKind == JS::ProfilingFrameIterator::Frame_Ion + ? "ion" + : "baseline"); if (jitFrame.hasTrackedOptimizations()) { + writer.FillUpTo(OPTIMIZATIONS); mFrameTableWriter.StartObjectElement(); { mFrameTableWriter.StartArrayProperty("types"); @@ -533,7 +606,6 @@ void UniqueStacks::StreamFrame(const OnStackFrameKey& aFrame) } } #endif - mFrameTableWriter.EndArray(); } struct ProfileSample @@ -549,62 +621,43 @@ struct ProfileSample static void WriteSample(SpliceableJSONWriter& aWriter, ProfileSample& aSample) { - // Schema: - // [stack, time, responsiveness, rss, uss, frameNumber, power] + enum Schema : uint32_t { + STACK = 0, + TIME = 1, + RESPONSIVENESS = 2, + RSS = 3, + USS = 4, + FRAME_NUMBER = 5, + POWER = 6 + }; - aWriter.StartArrayElement(); - { - // The last non-null index is tracked to save space in the JSON by avoid - // emitting 'null's at the end of the array, as they're only needed if - // followed by non-null elements. - uint32_t index = 0; - uint32_t lastNonNullIndex = 0; + AutoArraySchemaWriter writer(aWriter); - aWriter.IntElement(aSample.mStack); - index++; + writer.IntElement(STACK, aSample.mStack); - if (aSample.mTime.isSome()) { - lastNonNullIndex = index; - aWriter.DoubleElement(*aSample.mTime); - } - index++; - - if (aSample.mResponsiveness.isSome()) { - aWriter.NullElements(index - lastNonNullIndex - 1); - lastNonNullIndex = index; - aWriter.DoubleElement(*aSample.mResponsiveness); - } - index++; - - if (aSample.mRSS.isSome()) { - aWriter.NullElements(index - lastNonNullIndex - 1); - lastNonNullIndex = index; - aWriter.DoubleElement(*aSample.mRSS); - } - index++; - - if (aSample.mUSS.isSome()) { - aWriter.NullElements(index - lastNonNullIndex - 1); - lastNonNullIndex = index; - aWriter.DoubleElement(*aSample.mUSS); - } - index++; - - if (aSample.mFrameNumber.isSome()) { - aWriter.NullElements(index - lastNonNullIndex - 1); - lastNonNullIndex = index; - aWriter.IntElement(*aSample.mFrameNumber); - } - index++; - - if (aSample.mPower.isSome()) { - aWriter.NullElements(index - lastNonNullIndex - 1); - lastNonNullIndex = index; - aWriter.DoubleElement(*aSample.mPower); - } - index++; + if (aSample.mTime.isSome()) { + writer.DoubleElement(TIME, *aSample.mTime); + } + + if (aSample.mResponsiveness.isSome()) { + writer.DoubleElement(RESPONSIVENESS, *aSample.mResponsiveness); + } + + if (aSample.mRSS.isSome()) { + writer.DoubleElement(RSS, *aSample.mRSS); + } + + if (aSample.mUSS.isSome()) { + writer.DoubleElement(USS, *aSample.mUSS); + } + + if (aSample.mFrameNumber.isSome()) { + writer.IntElement(FRAME_NUMBER, *aSample.mFrameNumber); + } + + if (aSample.mPower.isSome()) { + writer.DoubleElement(POWER, *aSample.mPower); } - aWriter.EndArray(); } void ProfileBuffer::StreamSamplesToJSON(SpliceableJSONWriter& aWriter, int aThreadId, diff --git a/tools/profiler/core/ProfileEntry.h b/tools/profiler/core/ProfileEntry.h index abba84e819..8b3214a39b 100644 --- a/tools/profiler/core/ProfileEntry.h +++ b/tools/profiler/core/ProfileEntry.h @@ -33,7 +33,10 @@ class ThreadProfile; +// NB: Packing this structure has been shown to cause SIGBUS issues on ARM. +#ifndef __arm__ #pragma pack(push, 1) +#endif class ProfileEntry { @@ -81,7 +84,9 @@ private: char mTagName; }; +#ifndef __arm__ #pragma pack(pop) +#endif class UniqueJSONStrings { diff --git a/tools/profiler/core/ProfileJSONWriter.cpp b/tools/profiler/core/ProfileJSONWriter.cpp index 0d0a48d7fe..65a9425a32 100644 --- a/tools/profiler/core/ProfileJSONWriter.cpp +++ b/tools/profiler/core/ProfileJSONWriter.cpp @@ -12,6 +12,7 @@ ChunkedJSONWriteFunc::Write(const char* aStr) { MOZ_ASSERT(mChunkPtr >= mChunkList.back().get() && mChunkPtr <= mChunkEnd); MOZ_ASSERT(mChunkEnd >= mChunkList.back().get() + mChunkLengths.back()); + MOZ_ASSERT(*mChunkPtr == '\0'); size_t len = strlen(aStr); @@ -20,17 +21,18 @@ ChunkedJSONWriteFunc::Write(const char* aStr) // than a chunk, allocate its own chunk. char* newPtr; if (len >= kChunkSize) { - AllocChunk(len); + AllocChunk(len + 1); newPtr = mChunkPtr + len; } else { newPtr = mChunkPtr + len; - if (newPtr > mChunkEnd) { + if (newPtr >= mChunkEnd) { AllocChunk(kChunkSize); newPtr = mChunkPtr + len; } } memcpy(mChunkPtr, aStr, len); + *newPtr = '\0'; mChunkPtr = newPtr; mChunkLengths.back() += len; } @@ -77,6 +79,7 @@ ChunkedJSONWriteFunc::AllocChunk(size_t aChunkSize) mozilla::UniquePtr newChunk = mozilla::MakeUnique(aChunkSize); mChunkPtr = newChunk.get(); mChunkEnd = mChunkPtr + aChunkSize; + *mChunkPtr = '\0'; MOZ_ALWAYS_TRUE(mChunkLengths.append(0)); MOZ_ALWAYS_TRUE(mChunkList.append(mozilla::Move(newChunk))); } diff --git a/tools/profiler/core/StackTop.cpp b/tools/profiler/core/StackTop.cpp new file mode 100644 index 0000000000..1f7944e5e9 --- /dev/null +++ b/tools/profiler/core/StackTop.cpp @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifdef XP_MACOSX +#include +#include +#include +#elif XP_WIN +#include +#endif + +#include "StackTop.h" + +void *GetStackTop(void *guess) { +#if defined(XP_MACOSX) + pthread_t thread = pthread_self(); + return pthread_get_stackaddr_np(thread); +#elif defined(XP_WIN) +#if defined(_MSC_VER) && defined(_M_IX86) + // offset 0x18 from the FS segment register gives a pointer to + // the thread information block for the current thread + NT_TIB* pTib; + __asm { + MOV EAX, FS:[18h] + MOV pTib, EAX + } + return static_cast(pTib->StackBase); +#elif defined(__GNUC__) && defined(i386) + // offset 0x18 from the FS segment register gives a pointer to + // the thread information block for the current thread + NT_TIB* pTib; + asm ( "movl %%fs:0x18, %0\n" + : "=r" (pTib) + ); + return static_cast(pTib->StackBase); +#elif defined(_M_X64) || defined(__x86_64) + PNT_TIB64 pTib = reinterpret_cast(NtCurrentTeb()); + return reinterpret_cast(pTib->StackBase); +#else +#error Need a way to get the stack bounds on this platform (Windows) +#endif +#else + return guess; +#endif +} diff --git a/tools/profiler/core/SyncProfile.cpp b/tools/profiler/core/SyncProfile.cpp index a020233fa0..4c4742f348 100644 --- a/tools/profiler/core/SyncProfile.cpp +++ b/tools/profiler/core/SyncProfile.cpp @@ -9,7 +9,6 @@ SyncProfile::SyncProfile(ThreadInfo* aInfo, int aEntrySize) : ThreadProfile(aInfo, new ProfileBuffer(aEntrySize)) , mOwnerState(REFERENCED) - , mUtb(nullptr) { MOZ_COUNT_CTOR(SyncProfile); } diff --git a/tools/profiler/core/SyncProfile.h b/tools/profiler/core/SyncProfile.h index e8f2b78915..58f6b0d818 100644 --- a/tools/profiler/core/SyncProfile.h +++ b/tools/profiler/core/SyncProfile.h @@ -7,6 +7,7 @@ #ifndef __SYNCPROFILE_H #define __SYNCPROFILE_H +#include "ProfileEntry.h" #include "ThreadProfile.h" class SyncProfile : public ThreadProfile diff --git a/tools/profiler/core/platform-linux.cc b/tools/profiler/core/platform-linux.cc index 81a617864d..b98d284d8c 100644 --- a/tools/profiler/core/platform-linux.cc +++ b/tools/profiler/core/platform-linux.cc @@ -70,14 +70,14 @@ #include "mozilla/Atomics.h" #include "mozilla/LinuxSignal.h" #include "mozilla/TimeStamp.h" +#include "mozilla/DebugOnly.h" +#include "ProfileEntry.h" #include "nsThreadUtils.h" #include "GeckoSampler.h" #include "ThreadResponsiveness.h" #if defined(__ARM_EABI__) && defined(MOZ_WIDGET_GONK) // Should also work on other Android and ARM Linux, but not tested there yet. -#define USE_EHABI_STACKWALK -#include "EHABIStackWalk.h" # define USE_EHABI_STACKWALK # include "EHABIStackWalk.h" #elif defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux) @@ -118,8 +118,6 @@ static void sLUL_initialization_routine(void) } #endif -using namespace mozilla; - /* static */ Thread::tid_t Thread::GetCurrentId() { @@ -361,8 +359,9 @@ static void* SignalSender(void* arg) { printf_stderr("profiler failed to signal tid=%d\n", threadId); #ifdef DEBUG abort(); -#endif +#else continue; +#endif } // Wait for the signal handler to run before moving on to the next one diff --git a/tools/profiler/core/platform-win32.cc b/tools/profiler/core/platform-win32.cc index 5388848eb1..d91918febc 100644 --- a/tools/profiler/core/platform-win32.cc +++ b/tools/profiler/core/platform-win32.cc @@ -32,6 +32,7 @@ #include "platform.h" #include "GeckoSampler.h" #include "ThreadResponsiveness.h" +#include "ProfileEntry.h" // Memory profile #include "nsMemoryReporterManager.h" diff --git a/tools/profiler/core/platform.cpp b/tools/profiler/core/platform.cpp index 3b8411d592..7fb57d6ef8 100644 --- a/tools/profiler/core/platform.cpp +++ b/tools/profiler/core/platform.cpp @@ -8,6 +8,7 @@ #include #include "platform.h" +#include "PlatformMacros.h" #include "mozilla/ArrayUtils.h" #include "mozilla/UniquePtr.h" #include "GeckoProfiler.h" @@ -38,7 +39,11 @@ #include "AndroidBridge.h" #endif -#ifdef SPS_STANDALONE +#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) +#include "GeneratedJNINatives.h" +#endif + +#ifndef SPS_STANDALONE #if defined(SPS_PLAT_amd64_linux) || defined(SPS_PLAT_x86_linux) # define USE_LUL_STACKWALK # include "lul/LulMain.h" @@ -46,6 +51,22 @@ #endif #endif +#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) +class GeckoJavaSampler : public widget::GeckoJavaSampler::Natives +{ +private: + GeckoJavaSampler(); + +public: + static double GetProfilerTime() { + if (!profiler_is_active()) { + return 0.0; + } + return profiler_time(); + }; +}; +#endif + mozilla::ThreadLocal tlsPseudoStack; mozilla::ThreadLocal tlsTicker; mozilla::ThreadLocal tlsStackTop; @@ -78,7 +99,6 @@ const char* PROFILER_FEATURES = "MOZ_PROFILING_FEATURES"; * a problem if 2^32 events happen between samples that we need * to know are associated with different events */ - // Values harvested from env vars, that control the profiler. static int sUnwindInterval; /* in milliseconds */ static int sUnwindStackScan; /* max # of dubious frames allowed */ @@ -102,7 +122,6 @@ void Sampler::Startup() { sRegisteredThreads = new std::vector(); sRegisteredThreadsMutex = OS::CreateMutex("sRegisteredThreads mutex"); - // We could create the sLUL object and read unwind info into it at // this point. That would match the lifetime implied by destruction // of it in Sampler::Shutdown just below. However, that gives a big @@ -209,8 +228,6 @@ void ProfilerMarker::StreamJSON(SpliceableJSONWriter& aWriter, aWriter.EndArray(); } - -#if !defined(ANDROID) /* Has MOZ_PROFILER_VERBOSE been set? */ // Verbosity control for the profiler. The aim is to check env var @@ -234,7 +251,6 @@ bool moz_profiler_verbose() return profiler_verbosity == ProfilerVerbosity::VERBOSE; } -#endif void moz_profiler_set_verbosity(ProfilerVerbosity pv) { @@ -243,6 +259,7 @@ void moz_profiler_set_verbosity(ProfilerVerbosity pv) profiler_verbosity = pv; } + bool set_profiler_interval(const char* interval) { if (interval) { errno = 0; @@ -349,6 +366,10 @@ void profiler_usage() { LOG( "SPS: MOZ_PROFILER_STACK_SCAN= (default is zero)"); LOG( "SPS: The number of dubious (stack-scanned) frames allowed"); LOG( "SPS: "); + LOG( "SPS: MOZ_PROFILER_LUL_TEST"); + LOG( "SPS: If set to any value, runs LUL unit tests at startup of"); + LOG( "SPS: the unwinder thread, and prints a short summary of results."); + LOG( "SPS: "); LOGF("SPS: This platform %s native unwinding.", is_native_unwinding_avail() ? "supports" : "does not support"); LOG( "SPS: "); @@ -480,6 +501,12 @@ void mozilla_sampler_init(void* stackTop) set_stderr_callback(mozilla_sampler_log); #endif +#if defined(SPS_OS_android) && !defined(MOZ_WIDGET_GONK) + if (mozilla::jni::IsAvailable()) { + GeckoJavaSampler::Init(); + } +#endif + // We can't open pref so we use an environment variable // to know if we should trigger the profiler on startup // NOTE: Default @@ -741,6 +768,8 @@ void mozilla_sampler_start(int aProfileEntries, double aInterval, const char** aThreadNameFilters, uint32_t aFilterCount) { + LOG("BEGIN mozilla_sampler_start"); + if (!stack_key_initialized) profiler_init(nullptr); @@ -839,18 +868,23 @@ void mozilla_sampler_start(int aProfileEntries, double aInterval, os->NotifyObservers(params, "profiler-started", nullptr); } } +#endif + + LOG("END mozilla_sampler_start"); } void mozilla_sampler_stop() { + LOG("BEGIN mozilla_sampler_stop"); + if (!stack_key_initialized) return; GeckoSampler *t = tlsTicker.get(); if (!t) { + LOG("END mozilla_sampler_stop-early"); return; } -#endif bool disableJS = t->ProfileJS(); @@ -884,6 +918,7 @@ void mozilla_sampler_stop() } #endif + LOG("END mozilla_sampler_stop"); } bool mozilla_sampler_is_paused() { @@ -960,11 +995,6 @@ void mozilla_sampler_frame_number(int frameNumber) sFrameNumber = frameNumber; } -void mozilla_sampler_print_location2() -{ - // FIXME -} - void mozilla_sampler_lock() { profiler_stop(); diff --git a/tools/profiler/core/platform.h b/tools/profiler/core/platform.h index f3e50d08a2..4d6354addf 100644 --- a/tools/profiler/core/platform.h +++ b/tools/profiler/core/platform.h @@ -76,22 +76,29 @@ static inline pid_t gettid() #define ASSERT(a) MOZ_ASSERT(a) +bool moz_profiler_verbose(); + #ifdef ANDROID # if defined(__arm__) || defined(__thumb__) # define ENABLE_SPS_LEAF_DATA # define ENABLE_ARM_LR_SAVING # endif # define LOG(text) \ - __android_log_write(ANDROID_LOG_ERROR, "Profiler", text) -# define LOGF(format, ...) \ - __android_log_print(ANDROID_LOG_ERROR, "Profiler", format, __VA_ARGS__) -#else -extern bool moz_profiler_verbose(); -# define LOG(text) \ - do { if (moz_profiler_verbose()) printf("Profiler: %s\n", text); \ + do { if (moz_profiler_verbose()) \ + __android_log_write(ANDROID_LOG_ERROR, "Profiler", text); \ } while (0) # define LOGF(format, ...) \ - do { if (moz_profiler_verbose()) printf("Profiler: " format \ + do { if (moz_profiler_verbose()) \ + __android_log_print(ANDROID_LOG_ERROR, "Profiler", format, \ + __VA_ARGS__); \ + } while (0) + +#else +# define LOG(text) \ + do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: %s\n", text); \ + } while (0) +# define LOGF(format, ...) \ + do { if (moz_profiler_verbose()) fprintf(stderr, "Profiler: " format \ "\n", __VA_ARGS__); \ } while (0) @@ -259,6 +266,8 @@ bool set_profiler_entries(const char*); bool set_profiler_scan(const char*); bool is_native_unwinding_avail(); +void set_tls_stack_top(void* stackTop); + // ---------------------------------------------------------------------------- // Sampler // @@ -368,7 +377,7 @@ class Sampler { static bool RegisterCurrentThread(const char* aName, PseudoStack* aPseudoStack, - bool aIsMainThread); + bool aIsMainThread, void* stackTop); static void UnregisterCurrentThread(); static void Startup(); diff --git a/tools/profiler/core/shared-libraries-linux.cc b/tools/profiler/core/shared-libraries-linux.cc index b998fbe693..3b67d3b2c9 100644 --- a/tools/profiler/core/shared-libraries-linux.cc +++ b/tools/profiler/core/shared-libraries-linux.cc @@ -15,6 +15,34 @@ #include "platform.h" #include "shared-libraries.h" +#include "common/linux/file_id.h" +#include + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +// Get the breakpad Id for the binary file pointed by bin_name +static std::string getId(const char *bin_name) +{ + using namespace google_breakpad; + using namespace std; + + uint8_t identifier[kMDGUIDSize]; + char id_str[37]; // magic number from file_id.h + + FileID file_id(bin_name); + if (file_id.ElfFileIdentifier(identifier)) { + FileID::ConvertIdentifierToString(identifier, id_str, ARRAY_SIZE(id_str)); + // ConvertIdentifierToString converts the identifier to a string with + // some dashes (don't ask me why), but we need it raw, so remove the dashes. + char *id_end = remove(id_str, id_str + strlen(id_str), '-'); + // Also add an extra "0" by the end. google-breakpad does it for + // consistency with PDB files so we need to do too. + return string(id_str, id_end) + '0'; + } + + return ""; +} + #if !defined(MOZ_WIDGET_GONK) // TODO fix me with proper include #include "nsDebug.h" @@ -56,7 +84,8 @@ int dl_iterate_callback(struct dl_phdr_info *dl_info, size_t size, void *data) if (end > libEnd) libEnd = end; } - SharedLibrary shlib(libStart, libEnd, 0, "", dl_info->dlpi_name); + const char *name = dl_info->dlpi_name; + SharedLibrary shlib(libStart, libEnd, 0, getId(name), name); info.AddSharedLibrary(shlib); return 0; @@ -68,6 +97,15 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() SharedLibraryInfo info; #if !defined(MOZ_WIDGET_GONK) +#ifdef ANDROID + if (!dl_iterate_phdr) { + // On ARM Android, dl_iterate_phdr is provided by the custom linker. + // So if libxul was loaded by the system linker (e.g. as part of + // xpcshell when running tests), it won't be available and we should + // not call it. + return info; + } +#endif dl_iterate_phdr(dl_iterate_callback, &info); #ifndef ANDROID return info; @@ -112,7 +150,7 @@ SharedLibraryInfo SharedLibraryInfo::GetInfoForSelf() continue; } #endif - SharedLibrary shlib(start, end, offset, "", name); + SharedLibrary shlib(start, end, offset, getId(name), name); info.AddSharedLibrary(shlib); if (count > 10000) { LOG("Get maps failed"); diff --git a/tools/profiler/core/shared-libraries-macos.cc b/tools/profiler/core/shared-libraries-macos.cc index aa6cff01a1..e218d2280d 100644 --- a/tools/profiler/core/shared-libraries-macos.cc +++ b/tools/profiler/core/shared-libraries-macos.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include "shared-libraries.h" @@ -55,25 +56,41 @@ void addSharedLibrary(const platform_mach_header* header, char *name, SharedLibr const struct load_command *cmd = reinterpret_cast(header + 1); - seg_size size; + seg_size size = 0; + unsigned long long start = reinterpret_cast(header); // Find the cmd segment in the macho image. It will contain the offset we care about. - for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) { + const uint8_t *uuid_bytes = nullptr; + for (unsigned int i = 0; + cmd && (i < header->ncmds) && (uuid_bytes == nullptr || size == 0); + ++i) { if (cmd->cmd == CMD_SEGMENT) { const mach_segment_command_type *seg = reinterpret_cast(cmd); if (!strcmp(seg->segname, "__TEXT")) { size = seg->vmsize; - unsigned long long start = reinterpret_cast(header); - info.AddSharedLibrary(SharedLibrary(start, start+seg->vmsize, 0, - "", name)); - return; } + } else if (cmd->cmd == LC_UUID) { + const uuid_command *ucmd = reinterpret_cast(cmd); + uuid_bytes = ucmd->uuid; } cmd = reinterpret_cast (reinterpret_cast(cmd) + cmd->cmdsize); } + + std::stringstream uuid; + uuid << std::hex << std::uppercase; + if (uuid_bytes != nullptr) { + for (int i = 0; i < 16; ++i) { + uuid << ((uuid_bytes[i] & 0xf0) >> 4); + uuid << (uuid_bytes[i] & 0xf); + } + uuid << '0'; + } + + info.AddSharedLibrary(SharedLibrary(start, start + size, 0, uuid.str(), + name)); } // Use dyld to inspect the macho image information. We can build the SharedLibraryEntry structure diff --git a/tools/profiler/gecko/ProfileGatherer.cpp b/tools/profiler/gecko/ProfileGatherer.cpp index bf9e2835f5..0b46879527 100644 --- a/tools/profiler/gecko/ProfileGatherer.cpp +++ b/tools/profiler/gecko/ProfileGatherer.cpp @@ -12,7 +12,16 @@ using mozilla::dom::Promise; namespace mozilla { -NS_IMPL_ISUPPORTS0(ProfileGatherer) +/** + * When a subprocess exits before we've gathered profiles, we'll + * store profiles for those processes until gathering starts. We'll + * only store up to MAX_SUBPROCESS_EXIT_PROFILES. The buffer is + * circular, so as soon as we receive another exit profile, we'll + * bump the oldest one out of the buffer. + */ +static const uint32_t MAX_SUBPROCESS_EXIT_PROFILES = 5; + +NS_IMPL_ISUPPORTS(ProfileGatherer, nsIObserver) ProfileGatherer::ProfileGatherer(GeckoSampler* aTicker) : mTicker(aTicker) @@ -26,6 +35,13 @@ void ProfileGatherer::GatheredOOPProfile() { MOZ_ASSERT(NS_IsMainThread()); + if (!mGathering) { + // If we're not actively gathering, then we don't actually + // care that we gathered a profile here. This can happen for + // processes that exit while profiling. + return; + } + if (NS_WARN_IF(!mPromise)) { // If we're not holding on to a Promise, then someone is // calling us erroneously. @@ -68,7 +84,9 @@ ProfileGatherer::Start(double aSinceTime, nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { - nsresult rv = os->NotifyObservers(this, "profiler-subprocess-gather", nullptr); + nsresult rv = os->AddObserver(this, "profiler-subprocess", false); + NS_WARN_IF(NS_FAILED(rv)); + rv = os->NotifyObservers(this, "profiler-subprocess-gather", nullptr); NS_WARN_IF(NS_FAILED(rv)); } @@ -90,6 +108,12 @@ ProfileGatherer::Finish() UniquePtr buf = mTicker->ToJSON(mSinceTime); + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + nsresult rv = os->RemoveObserver(this, "profiler-subprocess"); + NS_WARN_IF(NS_FAILED(rv)); + } + AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mPromise->GlobalJSObject()))) { // We're really hosed if we can't get a JS context for some reason. @@ -145,4 +169,38 @@ ProfileGatherer::Cancel() mTicker = nullptr; } +void +ProfileGatherer::OOPExitProfile(const nsCString& aProfile) +{ + if (mExitProfiles.Length() >= MAX_SUBPROCESS_EXIT_PROFILES) { + mExitProfiles.RemoveElementAt(0); + } + mExitProfiles.AppendElement(aProfile); + + // If a process exited while gathering, we need to make + // sure we decrement the counter. + if (mGathering) { + GatheredOOPProfile(); + } +} + +NS_IMETHODIMP +ProfileGatherer::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t *someData) +{ + if (!strcmp(aTopic, "profiler-subprocess")) { + nsCOMPtr pse = do_QueryInterface(aSubject); + if (pse) { + for (size_t i = 0; i < mExitProfiles.Length(); ++i) { + if (!mExitProfiles[i].IsEmpty()) { + pse->AddSubProfile(mExitProfiles[i].get()); + } + } + mExitProfiles.Clear(); + } + } + return NS_OK; +} + } // namespace mozilla diff --git a/tools/profiler/gecko/ProfilerIOInterposeObserver.h b/tools/profiler/gecko/ProfilerIOInterposeObserver.h index 512447f1a5..8661b197e4 100644 --- a/tools/profiler/gecko/ProfilerIOInterposeObserver.h +++ b/tools/profiler/gecko/ProfilerIOInterposeObserver.h @@ -5,4 +5,24 @@ #ifndef PROFILERIOINTERPOSEOBSERVER_H #define PROFILERIOINTERPOSEOBSERVER_H +#ifdef MOZ_ENABLE_PROFILER_SPS + +#include "mozilla/IOInterposer.h" + +namespace mozilla { + +/** + * This class is the observer that calls into the profiler whenever + * main thread I/O occurs. + */ +class ProfilerIOInterposeObserver final : public IOInterposeObserver +{ +public: + virtual void Observe(Observation& aObservation); +}; + +} // namespace mozilla + +#endif // MOZ_ENABLE_PROFILER_SPS + #endif // PROFILERIOINTERPOSEOBSERVER_H diff --git a/tools/profiler/gecko/ThreadResponsiveness.cpp b/tools/profiler/gecko/ThreadResponsiveness.cpp index 86d9936dc3..f0c009ba47 100644 --- a/tools/profiler/gecko/ThreadResponsiveness.cpp +++ b/tools/profiler/gecko/ThreadResponsiveness.cpp @@ -9,6 +9,7 @@ #include "nsThreadUtils.h" #include "nsITimer.h" #include "mozilla/Monitor.h" +#include "ProfileEntry.h" #include "ThreadProfile.h" using mozilla::Monitor; diff --git a/tools/profiler/public/ProfileGatherer.h b/tools/profiler/public/ProfileGatherer.h index 81a95cc65f..4e39a4f5cf 100644 --- a/tools/profiler/public/ProfileGatherer.h +++ b/tools/profiler/public/ProfileGatherer.h @@ -11,22 +11,25 @@ class GeckoSampler; namespace mozilla { -class ProfileGatherer final : public nsISupports +class ProfileGatherer final : public nsIObserver { public: NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER explicit ProfileGatherer(GeckoSampler* aTicker); void WillGatherOOPProfile(); void GatheredOOPProfile(); void Start(double aSinceTime, mozilla::dom::Promise* aPromise); void Cancel(); + void OOPExitProfile(const nsCString& aProfile); private: ~ProfileGatherer() {}; void Finish(); void Reset(); + nsTArray mExitProfiles; RefPtr mPromise; GeckoSampler* mTicker; double mSinceTime; diff --git a/tools/rb/find-leakers.pl b/tools/rb/find-leakers.pl deleted file mode 100755 index 69463edd76..0000000000 --- a/tools/rb/find-leakers.pl +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/perl -w -# -# 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; - -my %allocs; -my %classes; -my %counter; - -LINE: while (<>) { - next LINE if (! /^ 0x01AFD3B8 1 AddRef 1 - - $allocs{$obj} = ++$counter{$class}; # the order of allocation - $classes{$obj} = $class; - } - elsif ($op eq 'Release' && $cnt == 0) { - # Example: 0x01AFD3B8 1 Release 0 - - delete($allocs{$obj}); - delete($classes{$obj}); - } - elsif ($op eq 'Ctor') { - # Example: 0x08880BD0 8 Ctor (20) - - $allocs{$obj} = ++$counter{$class}; - $classes{$obj} = $class; - } - elsif ($op eq 'Dtor') { - # Example: 0x08880BD0 8 Dtor (20) - - delete($allocs{$obj}); - delete($classes{$obj}); - } -} - - -sub sort_by_value { - my %x = @_; - sub _by_value($) { my %x = @_; $x{$a} cmp $x{$b}; } - sort _by_value keys(%x); -} - - -foreach my $key (&sort_by_value(%allocs)) { - # Example: 0x03F1D818 (2078) @ - print "$key (", $allocs{$key}, ") @ ", $classes{$key}, "\n"; -} diff --git a/tools/rb/find_leakers.py b/tools/rb/find_leakers.py new file mode 100644 index 0000000000..3076be8446 --- /dev/null +++ b/tools/rb/find_leakers.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# This script processes a `refcount' log, and finds out if any object leaked. +# It simply goes through the log, finds `AddRef' or `Ctor' lines, and then +# sees if they `Release' or `Dtor'. If not, it reports them as leaks. +# Please see README file in the same directory. + + +import sys + +def print_output(allocation, obj_to_class): + '''Formats and prints output.''' + items = [] + for obj, count, in allocation.iteritems(): + # Adding items to a list, so we can sort them. + items.append((obj, count)) + # Sorting by count. + items.sort(key=lambda item: item[1]) + + for obj, count, in items: + print "{obj} ({count}) @ {class_name}".format(obj=obj, + count=count, + class_name=obj_to_class[obj]) + +def process_log(log_lines): + '''Process through the log lines, and print out the result. + + @param log_lines: List of strings. + ''' + allocation = {} + class_count = {} + obj_to_class = {} + + for log_line in log_lines: + if not log_line.startswith('<'): + continue + + (class_name, + obj, + ignore, + operation, + count,) = log_line.strip('\r\n').split(' ') + + # for AddRef/Release `count' is the refcount, + # for Ctor/Dtor it's the size. + + if ((operation == 'AddRef' and count == '1') or + operation == 'Ctor'): + # Examples: + # 0x01AFD3B8 1 AddRef 1 + # 0x08880BD0 8 Ctor (20) + class_count[class_name] = class_count.setdefault(class_name, 0) + 1 + allocation[obj] = class_count[class_name] + obj_to_class[obj] = class_name + + elif ((operation == 'Release' and count == '0') or + operation == 'Dtor'): + # Examples: + # 0x01AFD3B8 1 Release 0 + # 0x08880BD0 8 Dtor (20) + if obj not in allocation: + print "An object was released that wasn't allocated!", + print obj, "@", class_name + else: + allocation.pop(obj) + obj_to_class.pop(obj) + + # Printing out the result. + print_output(allocation, obj_to_class) + + +def print_usage(): + print + print "Usage: find-leakers.py [log-file]" + print + print "If `log-file' provided, it will read that as the input log." + print "Else, it will read the stdin as the input log." + print + +def main(): + '''Main method of the script.''' + if len(sys.argv) == 1: + # Reading log from stdin. + process_log(sys.stdin.readlines()) + elif len(sys.argv) == 2: + # Reading log from file. + with open(sys.argv[1], 'r') as log_file: + log_lines = log_file.readlines() + process_log(log_lines) + else: + print 'ERROR: Invalid number of arguments' + print_usage() + +if __name__ == '__main__': + main() + diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index 3d4542299a..6c0aef6f49 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -956,7 +956,9 @@ nsExternalHelperAppService::LoadURI(nsIURI *aURI, URIParams uri; SerializeURI(aURI, uri); - mozilla::dom::ContentChild::GetSingleton()->SendLoadURIExternal(uri); + nsCOMPtr tabChild(do_GetInterface(aWindowContext)); + mozilla::dom::ContentChild::GetSingleton()-> + SendLoadURIExternal(uri, static_cast(tabChild.get())); return NS_OK; } diff --git a/uriloader/exthandler/nsWebHandlerApp.js b/uriloader/exthandler/nsWebHandlerApp.js index 30cc17f5e8..7b54a87694 100644 --- a/uriloader/exthandler/nsWebHandlerApp.js +++ b/uriloader/exthandler/nsWebHandlerApp.js @@ -80,6 +80,19 @@ nsWebHandlerApp.prototype = { // if we have a window context, use the URI loader to load there if (aWindowContext) { + try { + // getInterface throws if the object doesn't implement the given + // interface, so this try/catch statement is more of an if. + // If aWindowContext refers to a remote docshell, send the load + // request to the correct process. + aWindowContext.getInterface(Ci.nsIRemoteWindowContext) + .openURI(uriToSend, Ci.nsIURILoader.IS_CONTENT_PREFERRED); + return; + } catch (e) { + if (e.result != Cr.NS_NOINTERFACE) { + throw e; + } + } // create a channel from this URI var channel = ioService.newChannelFromURI2(uriToSend,