From 157a125630f7bfe87c7a97c44f18e1b8fddb4638 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 25 Sep 2024 09:21:53 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1144778 - Send an HTTP Date request header with telemetry pings. r=dexter (b630ff3123) - Bug 1268581 - Reduce Telemetry initialization delay in tests. r=dexter (08ea2597f4) - Bug 1205898 - Making TelemetryStopwatch.jsm handle keyed histograms. r=gfritzsche (2e1d793bc0) - Bug 1262386 - Move Scheduler shutdown() next to init(). r=dexter (9aa12c0859) - Bug 1162538 - Add test coverage for Telemetry archive probes. r=gfritzsche (f7f9095e82) - Bug 1186955 - Add test coverage. r=gfritzsche (cc76ba4fda) - Bug 1262386 - Trigger Telemetry scheduler tick after waking up from sleep. r=dexter (27b03357bf) - Bug 1178005 - Fix unintended octal literals (00 => 0). r=Mossop (dd738033b1) - Bug 1222054 - Fix broken tests. r=gfritzsche (a6c6fa3445) - Bug 1161515 - Reset UUID generator with fakeGenerateUUID(). r=gfritzsche (460ed17cb5) - Bug 1209912 - Use JSON.parse instead of nsIJSON.decode in Telemetry tests. r=Dexter (7ca55a9b5c) - Bug 1245136 - Discard old-format pending Telemetry files. r=gfritzsche (57f2a19c9d) - Bug 1034138 - d. Test BackgroundHangMonitor permahangs; r=vladan (7313b143bc) - Bug 1016629 - h. Fix wrong sysinfo usage; r=trivial (9823cd5ed0) - Bug 1197612 - In test_TelemetrySendOldPings.js, set the FHR pref to the value the tests assume it has. r=gfritzsche (06a41428b2) - Bug 1136082 - test_TelemetrySendOldPings.js is missing AddonManager initialization. r=froydnj (cfc700dd44) - Bug 1239480 - Make test_TelemetryReportingPolicy.js set the minimum policy version pref for the right channel. r=gfritzsche (e44841aa2a) - Bug 1230213 - test_TelemetryLog yields from a non generator function. r=dexter (34f28fb0b1) - Bug 1226178 - Fix deprecated octal literals warnings in Telemetry tests. r=gfritzsche (88208f0f2e) - Bug 1174111 - |test_sendTimeout| in test_TelemetryControllerShutdown.js must not wait on ping submission. r=gfritzsche (3b010e9743) - Bug 1120379 - Add tests for the deletion ping. r=gfritzsche (61e6fbd8a1) - Bug 1178262 - Add test-coverage for sending persisted deletion pings. r=gfritzsche (657fbbbcc2) - Bug 1174674 - Add test coverage. r=gfritzsche (426212e94c) - Bug 1167456 - Add test coverage. r=gfritzsche (4842d38e2e) - Bug 1198364 - Set FHR upload pref in telemetry/tests/unit/head.js to avoid test failures. r=gfritzsche (024599364e) - Bug 1149284 - Restore Telemetry tests. r=rvitillo, r=sstamm (88d4022e6b) - add esl (c459da1cd3) - Bug 1251785 - Remove remaining references to MOZILLA_XPCOMRT_API from toolkit. r=froydnj (5b5708031d) - Bug 1120379 - Add the documentation for deletion pings. r=gfritzsche (723f44f669) - Bug 1241599 - Add 'core' ping telemetry docs. r=gfritzsche (35fcc54195) - Bug 1241697 - Add docs for 'experiments' field in core ping. r=mfinkle (3c408f1a61) - Bug 1247572 - Add profileDate field to Android core ping docs. r=gfritzsche (79b1c71b53) - Bug 1249288 - Update telemetry docs to include defaultSearch. r=gfritzsche,rnewman (3807061291) - Bug 1257595 - Document that the 'core' pings profileDate field can be missing. r=mcomella (f40392cb2d) - Bug 1246816 - Add docs for profileDate. r=gfritzsche (7248d799aa) - Bug 1263761 - Update defaultSearch in core ping docs for custom search engines. r=gfritzsche (e516ecbab3) - Bug 1247982 - Lock request headers. r=mcmanus (8a5940a109) - Bug 1251332 - add PAC support for reversed ranges; r=bagder (7ac8fd97cb) - Bug 1178337 - Part 1: Supports all referrer policies for element atrributes. r=sicking, r=Gijs, r=mcmanus (85b7421b02) - Bug 1187357 - rename referrer attribute to referrerpolicy in tests. r=hsivonen (da334bf712) - Bug 1178337 - Part 2: Update/add test cases. r=sicking. (acec860fd8) - Bug 1260766 - the comparison of OriginAttributes in nsFrameLoader::Swap* methods should consider the usercontextid attribute of the owning element, r=smaug (82f8895bec) - bits of Bug 1110485 P1 (f8908eb35f) - Bug 1195968 - Check how CanvasFilterChainObserver accesses the CanvasRenderingContext2D. r=mstange (eecc12329b) - Bug 1260960 - Check skiaGL is enabled before calling skiaGLTex(), r=snorp (82c069fcc9) - align test stuff (c0ccc11ed9) - Bug 1245256 - GMP Plugins now installed in flat manner to accomodate widevine. r=spohl (ae0eb69979) - Bug 1245649: Turn on use-isnan, no-unexpected-multiline (0633b3470f) - Bug 1079665 - [e10s] Findbar focusContent doesn't work when a link was found. r=enndeakin (b349e62d5a) - Bug 1174291 - Fix ctrl-return for e10s findbar. r=evilpie (6f0bf0b149) - Bug 1174289 - Remove fake focus-ring after e10s findbar is closed. r=evilpie (302ffe7559) - Bug 1079665 - follow up, remove accidental line included from different patch. r=me (0904fe927c) - Bug 1260208 - part 1 - use C I/O facilities in TestWebGLElementArrayCache instead of C++ ones; r=BenWa The libc++ included with the Android NDK does not seem to work correctly with std::cerr; writing to it (or to std::cout, as confirmed by tests) causes the process to hang indefinitely, causing test failures. Using fprintf and stderr, however, seems to work correctly. (4279c2d09d) - Bug 1260208 - part 5 - add libc++ license to about:license when using it on Android; r=gerv,nalexander (4bcf23794a) - Bug 1260208 - part 0 - correctly compare EHTable when sorting; r=froydnj operator< for EHTable compares the LHS start PC with the RHS *end* PC. Because the ranges are non-overlapping, this works fine for two distinct EHTables. However, the comparison doesn't work if LHS and RHS refer to the same EHTable; in that case operator< returns true, even though it should return false because the two operands are identical. (dc2af36cec) - Bug 1254908 - ./mach bootstrap doesn't work on CentOS/Fedora when it attempts to upgrade Mercurial. r=gps (d288b314ec) - Bug 1239413. Clarify comment about the context flags. (c2b01ef6d1) - Bug 1146875 - Fix the leak-gauge.html broken and add a encoding declaration. r=dbaron (8df2510774) - Bug 1132499 - part 1 - convert nsTextEditorState::mValue to use Maybe instead of heap allocation; r=ehsan (2644faebbb) - Bug 1132499 - part 2 - convert nsTextEditorState::mValue to be an nsString; r=ehsan (6e0df4d1dc) - Bug 409885 - Use SetHostPort in nsHTMLDocument::SetDomain. r=bz (2d289a79c5) - Bug 1269475 - Fix uninitialized variable warning. r=jdm (881fa29803) - Bug 1263935 - Expose native version of mapURIToAddonId via amIAddonPathService. r=mossop (04da9fe703) - missing bit of Bug 1255040 Add webidl for install/uninstall (63b5489b3d) - Bug 1234974 - Handle null GetOwner() in |BluetoothAdapter::IsBluetoothCertifiedApp|, r=shuang (e4dde47d7f) - Bug 1241117 - Fix mAppUuid access before assign in BluetoothGatt::Connect. r=joliu (3920535f2a) - Bug 1238424 - Fix a missing header in Bluetooth module. r=brsun (438fd74375) - Bug 1267958: Only complain for negative size, not merely zero. r=jrmuizel (194bd2a754) - Bug 1320621 - Make sure --enable-system-hunspell picks up system headers. r=glandium (125c4ea215) - Bug 1266430: Support offsets in DrawTargetD2D1::CopySurface when using PartialUploading. r=jrmuizel (de8630b117) - Bug 1267363. Disable hinting on osx fonts only if we have expclity grayscale AA. r=mstange (c4c32fd7cc) - Bug 1134549 - Switch FlattenBezier from floats to doubles. r=bas (5753ab16a3) - Bug 1240437 part 2: Follow-up to fix implicit constrcutor static analysis failure. r=me (525d9d9a9f) - Bug 1256678 - Backout diagnostic patch rev 39c895b67af2 - r=me (f63698de77) - Bug 1256678 - Account for truncated font names in ScaledFontWin::GetFontFileData() - r=jfkthame (a6930e0a33) - Bug 1256678 - Re-add nightly-only crash if ScaledFontWin::GetFontFileData fails to find a matching TrueType collection font - r=jfkthame (8bec2d43a2) - Bug 1260350 - GFX: 2D: Use ConvolveHorizontally_LS3. r=jrmuizel (9d2f372593) - Bug 1260112 - GFX: 2D: Fix unaligned access in ConvolveVertically/Horizontally_LS3. r=huangwenjun06 (43ba8f7b05) - Bug 1260885 - Make SurfaceFormat::R5G6B5_UINT16 properly round-trip through SourceSurfaceCairo. r=lsalzman (bc8f0c0876) - Bug 1262415 - make Accessible to keep weak pointers to parent and children, r=yzen (c0b987fe10) - Bug 1267271 - skip empty surfaces in DrawTargetSkia::DrawSurfaceWithShadow. r=mchang (fd488c918a) - Bug 1249600. Lookup font and font family from font face when requesting SkTypeface. r=bas (0281313c33) - Bug 1268096. Sync the D3D11 rasterizer state if pointDrawMode or multisample changes. (25b2af8b26) - Bug 1203132 - Add an SVG for the B2G flow of touch events. r=botond (2ce3b0ddc4) - Bug 1180322. Add a document describing the history of layers (b3351dbba3) - Bug 1235223. Update history document. (212d356bd0) - Bug 1235223 - Fix typos in layers history document. (f19a5802d8) - Bug 1235223 - Fix another typo in the layers history document. (9663fe5447) - Bug 1265112 - Add support for NV_texture_barrier to GLContext. r=jrmuizel (f1f5fb1a3a) - Bug 1240730 - Initialize all GLLibraryEGL symbols in the ctor. r=mattwoodrow (dae8955458) - Bug 1240730 - Initialize all GLXLibrary members in the ctor. r=mattwoodrow (8218ed6fd9) - bug 1250485 - make the ctor for GLXLibrary constexpr r=jrmuizel (a571491f0a) - mitor spacing (0083550ab2) - Bug 1256492 - Cast to proper width to avoid C4312 on VS2015; r=jrmuizel (1bb2ed20d3) - Bug 1266878 - Fix off-by-one error in ParamTraits - r=nical (f467d496b8) - Bug 1258758 - rename GetEffectiveClipRect to GetLocalClipRect. r=kats (897798948f) - Bug 1249936 - add a LayerComposite::GetShadowTransform method & simplify Layer::GetLocalTransform. r=botond (fe492a1327) - Bug 1269032 - Add support for user multipliers to pan gesture inputs. r=mstange (ea7698dca2) - Bug 1265510 - Add some scroll-snapping logging to APZC. r=botond (6f2369163a) - Bug 1268523 - Avoid leaking tasks when they are posted to a destroyed APZC instance. r=botond (70e1e63b7b) - Bug 1265510 - Ensure that new input blocks still allow APZCs with interrupted animations to scroll-snap. r=botond (01341b554b) - Bug 1269068 - Zero out the axis velocity once the wheel scroll animation is terminating. r=botond (9a9f384485) - Bug 1246056 - Ensure that the MockContentController's timestamp is always >= GetStartupTime(). r=botond (4fe53e1f24) - Bug 1256341 - Add a gtest to catch scenarios where a tap timeout is interrupted by a non-touch block. r=botond (21a15f5a5a) - Bug 1266833 - When the scroll position is clamped during a frame reconstruction, send a scroll offset update to APZ. r=tnikkel (c0acd70a3d) - Bug 1268517 - Remove unused function. r=botond (534e4d9bae) - Bug 1213095 - Fix APZEventState build with logging enabled. r=botond (ac7c1e9401) - Bug 1250954 - Correct the SharedSurfaceTextureClient when forwarder changed. r=jgilbert (0a389bc0fa) - Bug 1265873 - Use gfxSharedReadLock in TextureClientPool r=nical (e3ed936a8a) - Bug 1265468 - Update the compositor pointer on all TextureSources in the list when recycling in ImageHost. r=nical (78f29244bb) - Bug 1252237 - Remove the TextureHost shmem mapping failed assert. r=milan (d0b7230dff) - Bug 1265112 - When rendering blend modes with CompositorOGL, use NV_texture_barrier (if available) to sample directly from the framebuffer. r=jrmuizel (6f3ae237a5) - Bug 1268878 - Include various of gfxFontEntry's sub-objects in memory reporting. r=njn (b960415fa8) - Bug 1268951 - Reallocate sanitized user font data into an appropriately-sized block, to reduce ongoing RAM footprint. r=jrmuizel (a26e9dd632) - Bug 1123416 - Part 1: Make topsrcdir a valid Gradle project root. r=sebastian (77b0b35fc1) - Bug 1260672: Remove 'nsAutoPtr.h' and clean up includes of Gonk diskspace watcher, r=gsvelto (99c4c35e3d) - Bug 1167535 - Fix fanotify 4g size limit. r=fabrice (c62015e454) - Bug 1167817 - Switch back to printf_stderr in GonkDiskSpaceWatcher.cpp r=dhylands (110445f628) - Bug 1260672: Use |UniquePtr| for Gonk alarms, r=gsvelto (03678b9015) - Bug 1260672: Use |UniquePtr| for Gonk sensors, r=gsvelto (545ec3fc70) - Bug 1263845. When a parent changes from auto height to non-auto height or vice versa, a percentage height non-block child needs to realize it's doing a vertical resize. r=dbaron (05db492bc8) - Bug 1268009 - If APZ is force-disabled, disable paint skipping even for apz-originated scrolls. r=kats (c02f8755cd) - Bug 1267555 part 1 - [css-grid] Make grid-aligned abs.pos. descendants that span from 'auto' to the first/last line stretch to the outer edge of the first/last track respectively. r=dholbert (88f6261043) - Bug 1267555 part 2 - [css-grid] Adjust reftests for abs.pos. "auto/first-line" and "last-line/auto" change. (43d03ab15b) --- accessible/generic/Accessible.cpp | 5 +- accessible/generic/Accessible.h | 4 +- accessible/html/HTMLSelectAccessible.h | 2 +- browser/base/content/browser.js | 2 +- browser/base/content/content.js | 4 +- .../healthreport_testRemoteCommands.html | 539 +++++++++--------- dom/base/nsFrameLoader.cpp | 73 ++- dom/base/nsFrameLoader.h | 6 + dom/base/test/img_referrer_testserver.sjs | 8 +- dom/base/test/referrer_helper.js | 10 +- dom/base/test/referrer_testserver.sjs | 56 +- dom/base/test/test_anchor_area_referrer.html | 26 +- .../test_anchor_area_referrer_changing.html | 2 +- .../test_anchor_area_referrer_invalid.html | 21 +- .../test/test_anchor_area_referrer_rel.html | 2 +- dom/base/test/test_iframe_referrer.html | 2 +- .../test/test_iframe_referrer_changing.html | 2 +- .../test/test_iframe_referrer_invalid.html | 21 +- dom/base/test/test_referrer_redirect.html | 2 +- .../common/webapi/BluetoothAdapter.cpp | 2 + dom/bluetooth/common/webapi/BluetoothGatt.cpp | 10 +- .../webapi/BluetoothPbapRequestHandle.h | 1 + dom/cache/CacheInitData.ipdlh | 0 ..._camera_hardware_auto_focus_moving_cb.html | 73 --- dom/canvas/CanvasRenderingContext2D.cpp | 107 ++-- .../TestWebGLElementArrayCache.cpp | 6 +- dom/cellbroadcast/tests/marionette/head.js | 39 +- .../tests/marionette/manifest.ini | 6 +- .../marionette/test_cellbroadcast_etws.js | 4 +- .../marionette/test_cellbroadcast_gsm.js | 59 +- ...est_cellbroadcast_gsm_language_and_body.js | 64 +++ .../test_cellbroadcast_multi_sim.js | 2 +- .../marionette/test_cellbroadcast_umts.js | 73 +-- ...st_cellbroadcast_umts_language_and_body.js | 71 +++ dom/contacts/tests/test_migration.html | 213 +------ dom/html/nsGenericHTMLElement.cpp | 9 +- dom/html/nsHTMLDNSPrefetch.cpp | 2 +- dom/html/nsHTMLDocument.cpp | 32 +- dom/html/nsTextEditorState.cpp | 6 +- dom/html/nsTextEditorState.h | 3 +- .../tests/marionette/head_chrome.js | 144 +++++ .../tests/marionette/manifest.ini | 5 +- .../test_call_barring_basic_operations.js | 130 +++++ .../test_call_barring_change_password.js | 100 ++-- .../marionette/test_call_barring_get_error.js | 88 +-- .../marionette/test_call_barring_set_error.js | 183 +++--- .../test_dsds_mobile_data_connection.js | 6 +- .../marionette/test_mobile_cell_Info_list.js | 25 + .../tests/marionette/test_mobile_data_ipv6.js | 20 +- .../test_mobile_neighboring_cell_ids.js | 46 +- .../marionette/test_mobile_voice_location.js | 7 + .../marionette/test_mobile_voice_state.js | 21 - extensions/spellcheck/hunspell/glue/moz.build | 8 +- extensions/spellcheck/src/moz.build | 6 +- gfx/2d/DrawTargetCairo.cpp | 2 +- gfx/2d/DrawTargetD2D1.cpp | 14 +- gfx/2d/DrawTargetD2D1.h | 3 + gfx/2d/DrawTargetRecording.cpp | 4 - gfx/2d/DrawTargetSkia.cpp | 18 +- gfx/2d/Path.cpp | 123 ++-- gfx/2d/PathD2D.cpp | 37 +- gfx/2d/RecordedEvent.h | 2 +- gfx/2d/SFNTData.cpp | 23 +- gfx/2d/SFNTData.h | 4 +- gfx/2d/ScaledFontDWrite.cpp | 32 +- gfx/2d/ScaledFontDWrite.h | 2 + gfx/2d/ScaledFontWin.cpp | 10 +- gfx/2d/SourceSurfaceCairo.cpp | 2 + gfx/2d/convolver.cpp | 4 +- gfx/2d/convolverLS3.cpp | 22 +- .../renderer/d3d/d3d11/StateManager11.cpp | 4 +- .../src/tests/gl_tests/PointSpritesTest.cpp | 78 +++ gfx/doc/B2GInputFlow.svg | 349 ++++++++++++ gfx/doc/LayersHistory.md | 60 ++ gfx/gl/GLContext.cpp | 9 + gfx/gl/GLContext.h | 12 + gfx/gl/GLContextSymbols.h | 4 + gfx/gl/GLLibraryEGL.h | 43 ++ gfx/gl/GLScreenBuffer.cpp | 4 +- gfx/gl/GLXLibrary.h | 37 +- gfx/gl/SharedSurfaceEGL.cpp | 2 +- gfx/ipc/GfxMessageUtils.h | 2 +- gfx/layers/ImageTypes.h | 3 +- gfx/layers/LayerTreeInvalidation.cpp | 14 +- gfx/layers/Layers.cpp | 34 +- gfx/layers/Layers.h | 2 +- gfx/layers/ReadbackProcessor.cpp | 2 +- gfx/layers/apz/src/APZCTreeManager.cpp | 10 + gfx/layers/apz/src/AsyncPanZoomController.cpp | 29 +- gfx/layers/apz/src/InputQueue.cpp | 2 +- gfx/layers/apz/src/WheelScrollAnimation.cpp | 5 +- gfx/layers/apz/test/gtest/APZTestCommon.h | 14 +- .../apz/test/gtest/TestGestureDetector.cpp | 23 +- ...me-reconstruction-scroll-clamping-ref.html | 27 + .../frame-reconstruction-scroll-clamping.html | 53 ++ gfx/layers/apz/test/reftest/reftest.list | 2 + gfx/layers/apz/util/APZEventState.cpp | 1 + gfx/layers/apz/util/APZThreadUtils.cpp | 17 - gfx/layers/apz/util/APZThreadUtils.h | 7 - gfx/layers/basic/BasicLayerManager.cpp | 10 +- gfx/layers/client/CanvasClient.cpp | 3 + gfx/layers/client/ClientLayerManager.cpp | 8 +- gfx/layers/client/SingleTiledContentClient.h | 2 +- gfx/layers/client/TextureClientPool.cpp | 51 +- gfx/layers/client/TextureClientPool.h | 20 +- gfx/layers/client/TiledContentClient.cpp | 24 +- .../composite/ContainerLayerComposite.cpp | 4 +- gfx/layers/composite/ImageHost.cpp | 4 - .../composite/LayerManagerComposite.cpp | 21 +- gfx/layers/composite/LayerManagerComposite.h | 1 + gfx/layers/composite/TextureHost.cpp | 15 +- gfx/layers/opengl/CompositorOGL.cpp | 17 +- gfx/tests/crashtests/1134549-1.svg | 14 + gfx/tests/crashtests/crashtests.list | 1 + gfx/thebes/gfxFontEntry.cpp | 38 ++ gfx/thebes/gfxMathTable.cpp | 10 + gfx/thebes/gfxMathTable.h | 2 + gfx/thebes/gfxSVGGlyphs.cpp | 25 + gfx/thebes/gfxSVGGlyphs.h | 4 + gfx/thebes/gfxUserFontSet.cpp | 28 +- gfx/thebes/gfxUserFontSet.h | 2 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53638 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 160 ++++++ hal/gonk/GonkDiskSpaceWatcher.cpp | 19 +- hal/gonk/GonkHal.cpp | 10 +- hal/gonk/GonkSensorsInterface.cpp | 16 +- hal/gonk/GonkSensorsInterface.h | 7 +- hal/gonk/GonkSensorsPollInterface.cpp | 25 +- hal/gonk/GonkSensorsPollInterface.h | 7 +- hal/gonk/GonkSensorsRegistryInterface.cpp | 17 +- hal/gonk/GonkSensorsRegistryInterface.h | 7 +- layout/generic/nsGfxScrollFrame.cpp | 45 +- layout/generic/nsGridContainerFrame.cpp | 9 +- layout/generic/nsHTMLReflowState.cpp | 23 + layout/reftests/bugs/1263845-ref.html | 10 + layout/reftests/bugs/1263845.html | 15 + layout/reftests/bugs/reftest.list | 1 + layout/reftests/canvas/reftest.list | 2 +- .../css-grid/grid-abspos-items-013-ref.html | 8 +- ...rid-placement-abspos-implicit-001-ref.html | 2 +- .../grid-repeat-auto-fill-fit-007-ref.html | 2 +- netwerk/base/Predictor.cpp | 3 +- netwerk/base/ProxyAutoConfig.cpp | 10 +- netwerk/base/ReferrerPolicy.h | 6 +- .../protocol/http/ConnectionDiagnostics.cpp | 5 +- netwerk/protocol/http/Http2Session.cpp | 2 +- netwerk/protocol/http/Http2Stream.cpp | 24 +- netwerk/protocol/http/HttpBaseChannel.cpp | 95 +-- netwerk/protocol/http/HttpChannelChild.cpp | 10 +- netwerk/protocol/http/HttpChannelParent.cpp | 8 +- netwerk/protocol/http/SpdyStream31.cpp | 23 +- netwerk/protocol/http/nsHttpChannel.cpp | 102 ++-- netwerk/protocol/http/nsHttpChannel.h | 2 +- netwerk/protocol/http/nsHttpConnection.cpp | 22 +- netwerk/protocol/http/nsHttpHandler.cpp | 4 +- netwerk/protocol/http/nsHttpHandler.h | 4 +- netwerk/protocol/http/nsHttpHeaderArray.cpp | 8 + netwerk/protocol/http/nsHttpHeaderArray.h | 2 + netwerk/protocol/http/nsHttpRequestHead.cpp | 210 ++++++- netwerk/protocol/http/nsHttpRequestHead.h | 107 ++-- netwerk/protocol/http/nsHttpTransaction.cpp | 23 +- netwerk/protocol/http/nsIHttpChannel.idl | 5 + .../test/unit/test_protocolproxyservice.js | 30 + python/mozboot/mozboot/centosfedora.py | 3 +- settings.gradle | 43 ++ toolkit/components/telemetry/Telemetry.h | 2 +- .../telemetry/TelemetryController.jsm | 2 +- .../components/telemetry/TelemetrySend.jsm | 1 + .../components/telemetry/TelemetrySession.jsm | 61 +- .../telemetry/TelemetryStopwatch.jsm | 374 ++++++++---- .../components/telemetry/TelemetryStorage.jsm | 4 - .../components/telemetry/docs/core-ping.rst | 105 ++++ .../telemetry/docs/deletion-ping.rst | 17 + toolkit/components/telemetry/docs/index.rst | 3 + toolkit/components/telemetry/docs/pings.rst | 3 +- .../components/telemetry/docs/uitour-ping.rst | 24 + .../components/telemetry/tests/unit/.eslintrc | 5 + .../components/telemetry/tests/unit/head.js | 69 +-- .../telemetry/tests/unit/test_PingAPI.js | 107 +++- .../tests/unit/test_TelemetryController.js | 130 ++++- .../unit/test_TelemetryControllerShutdown.js | 4 +- .../tests/unit/test_TelemetryLateWrites.js | 2 +- .../tests/unit/test_TelemetryLockCount.js | 2 +- .../telemetry/tests/unit/test_TelemetryLog.js | 5 +- .../unit/test_TelemetryReportingPolicy.js | 18 +- .../tests/unit/test_TelemetrySend.js | 78 +++ .../tests/unit/test_TelemetrySendOldPings.js | 65 ++- .../tests/unit/test_TelemetrySession.js | 258 ++++++--- .../tests/unit/test_TelemetryStopwatch.js | 67 ++- .../tests/unit/test_ThirdPartyCookieProbe.js | 166 ------ .../tests/unit/test_ThreadHangStats.js | 27 +- .../telemetry/tests/unit/xpcshell.ini | 9 - toolkit/content/license.html | 35 ++ toolkit/content/moz.build | 2 + toolkit/content/widgets/findbar.xml | 1 - toolkit/modules/GMPInstallManager.jsm | 26 +- toolkit/modules/InlineSpellChecker.jsm | 3 +- toolkit/modules/RemoteFinder.jsm | 22 +- .../mozapps/extensions/AddonPathService.cpp | 10 + .../extensions/amIAddonPathService.idl | 8 + toolkit/mozapps/extensions/amWebAPI.js | 4 +- toolkit/profile/test/.eslintrc | 5 + tools/leak-gauge/leak-gauge.html | 3 +- tools/profiler/core/EHABIStackWalk.cpp | 2 +- tools/profiler/core/platform-win32.cc | 4 +- widget/InputData.cpp | 14 + widget/InputData.h | 9 + widget/gonk/HwcComposer2D.cpp | 4 +- 209 files changed, 4595 insertions(+), 2121 deletions(-) delete mode 100644 dom/cache/CacheInitData.ipdlh delete mode 100644 dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html create mode 100644 dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm_language_and_body.js create mode 100644 dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts_language_and_body.js create mode 100644 dom/mobileconnection/tests/marionette/head_chrome.js create mode 100644 dom/mobileconnection/tests/marionette/test_call_barring_basic_operations.js create mode 100644 dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js create mode 100644 gfx/doc/B2GInputFlow.svg create mode 100644 gfx/doc/LayersHistory.md create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html create mode 100644 gfx/tests/crashtests/1134549-1.svg create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 layout/reftests/bugs/1263845-ref.html create mode 100644 layout/reftests/bugs/1263845.html create mode 100644 settings.gradle create mode 100644 toolkit/components/telemetry/docs/core-ping.rst create mode 100644 toolkit/components/telemetry/docs/deletion-ping.rst create mode 100644 toolkit/components/telemetry/docs/uitour-ping.rst create mode 100644 toolkit/components/telemetry/tests/unit/.eslintrc delete mode 100644 toolkit/components/telemetry/tests/unit/test_ThirdPartyCookieProbe.js create mode 100644 toolkit/profile/test/.eslintrc diff --git a/accessible/generic/Accessible.cpp b/accessible/generic/Accessible.cpp index 4a8c693685..420489ad98 100644 --- a/accessible/generic/Accessible.cpp +++ b/accessible/generic/Accessible.cpp @@ -94,8 +94,7 @@ using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // Accessible: nsISupports and cycle collection -NS_IMPL_CYCLE_COLLECTION(Accessible, - mContent, mParent, mChildren) +NS_IMPL_CYCLE_COLLECTION(Accessible, mContent) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Accessible) if (aIID.Equals(NS_GET_IID(Accessible))) @@ -1979,7 +1978,7 @@ Accessible::BindToParent(Accessible* aParent, uint32_t aIndexInParent) #ifdef A11Y_LOG if (mParent) { logging::TreeInfo("BindToParent: stealing accessible", 0, - "old parent", mParent.get(), + "old parent", mParent, "new parent", aParent, "child", this, nullptr); } diff --git a/accessible/generic/Accessible.h b/accessible/generic/Accessible.h index 5c30db765e..db7c52b246 100644 --- a/accessible/generic/Accessible.h +++ b/accessible/generic/Accessible.h @@ -1094,8 +1094,8 @@ protected: nsCOMPtr mContent; DocAccessible* mDoc; - RefPtr mParent; - nsTArray > mChildren; + Accessible* mParent; + nsTArray mChildren; int32_t mIndexInParent; static const uint8_t kStateFlagsBits = 13; diff --git a/accessible/html/HTMLSelectAccessible.h b/accessible/html/HTMLSelectAccessible.h index 2cef32b486..0c781034fb 100644 --- a/accessible/html/HTMLSelectAccessible.h +++ b/accessible/html/HTMLSelectAccessible.h @@ -99,7 +99,7 @@ private: if (parent && parent->IsListControl()) { Accessible* combobox = parent->Parent(); - return combobox && combobox->IsCombobox() ? combobox : mParent.get(); + return combobox && combobox->IsCombobox() ? combobox : mParent; } return nullptr; diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 4860f54b33..d2ae8c9c3a 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -5728,7 +5728,7 @@ function handleLinkClick(event, href, linkNode) { linkNode) { let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode. getAttribute("referrerpolicy")); - if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) { + if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) { referrerPolicy = referrerAttrValue; } } diff --git a/browser/base/content/content.js b/browser/base/content/content.js index 57cc579387..e6ed5b5e85 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -153,7 +153,7 @@ var handleContentContextMenu = function (event) { if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer")) { let referrerAttrValue = Services.netUtils.parseAttributePolicyString(event.target. getAttribute("referrerpolicy")); - if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) { + if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) { referrerPolicy = referrerAttrValue; } } @@ -452,7 +452,7 @@ var ClickEventHandler = { node) { let referrerAttrValue = Services.netUtils.parseAttributePolicyString(node. getAttribute("referrerpolicy")); - if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) { + if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) { referrerPolicy = referrerAttrValue; } } diff --git a/browser/base/content/test/general/healthreport_testRemoteCommands.html b/browser/base/content/test/general/healthreport_testRemoteCommands.html index 550c198df9..cc9e707670 100644 --- a/browser/base/content/test/general/healthreport_testRemoteCommands.html +++ b/browser/base/content/test/general/healthreport_testRemoteCommands.html @@ -1,267 +1,272 @@ - - - - - - - - - + + + + + + + + + diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index 25780d8f4c..aa321f35c4 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -905,8 +905,25 @@ nsFrameLoader::SwapWithOtherRemoteLoader(nsFrameLoader* aOther, return NS_ERROR_NOT_IMPLEMENTED; } - if (mRemoteBrowser->OriginAttributesRef() != - aOther->mRemoteBrowser->OriginAttributesRef()) { + // When we swap docShells, maybe we have to deal with a new page created just + // for this operation. In this case, the browser code should already have set + // the correct userContextId attribute value in the owning XULElement, but our + // docShell, that has been created way before) doesn't know that that + // happened. + // This is the reason why now we must retrieve the correct value from the + // usercontextid attribute before comparing our originAttributes with the + // other one. + DocShellOriginAttributes ourOriginAttributes = + mRemoteBrowser->OriginAttributesRef(); + rv = PopulateUserContextIdFromAttribute(ourOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + DocShellOriginAttributes otherOriginAttributes = + aOther->mRemoteBrowser->OriginAttributesRef(); + rv = aOther->PopulateUserContextIdFromAttribute(otherOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + if (ourOriginAttributes != otherOriginAttributes) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -1286,8 +1303,25 @@ nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther, return NS_ERROR_NOT_IMPLEMENTED; } - if (ourDocshell->GetOriginAttributes() != - otherDocshell->GetOriginAttributes()) { + // When we swap docShells, maybe we have to deal with a new page created just + // for this operation. In this case, the browser code should already have set + // the correct userContextId attribute value in the owning XULElement, but our + // docShell, that has been created way before) doesn't know that that + // happened. + // This is the reason why now we must retrieve the correct value from the + // usercontextid attribute before comparing our originAttributes with the + // other one. + DocShellOriginAttributes ourOriginAttributes = + ourDocshell->GetOriginAttributes(); + rv = PopulateUserContextIdFromAttribute(ourOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + DocShellOriginAttributes otherOriginAttributes = + otherDocshell->GetOriginAttributes(); + rv = aOther->PopulateUserContextIdFromAttribute(otherOriginAttributes); + NS_ENSURE_SUCCESS(rv,rv); + + if (ourOriginAttributes != otherOriginAttributes) { return NS_ERROR_NOT_IMPLEMENTED; } @@ -2047,13 +2081,9 @@ nsFrameLoader::MaybeCreateDocShell() } // Grab the userContextId from owner if XUL - nsAutoString userContextIdStr; - if ((namespaceID == kNameSpaceID_XUL) && - mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, userContextIdStr) && - !userContextIdStr.IsEmpty()) { - nsresult rv; - attrs.mUserContextId = userContextIdStr.ToInteger(&rv); - NS_ENSURE_SUCCESS(rv, rv); + nsresult rv = PopulateUserContextIdFromAttribute(attrs); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; } nsDocShell::Cast(mDocShell)->SetOriginAttributes(attrs); @@ -3270,3 +3300,24 @@ nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext, return NS_OK; } + +nsresult +nsFrameLoader::PopulateUserContextIdFromAttribute(DocShellOriginAttributes& aAttr) +{ + if (aAttr.mUserContextId == + nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) { + // Grab the userContextId from owner if XUL + nsAutoString userContextIdStr; + int32_t namespaceID = mOwnerContent->GetNameSpaceID(); + if ((namespaceID == kNameSpaceID_XUL) && + mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, + userContextIdStr) && + !userContextIdStr.IsEmpty()) { + nsresult rv; + aAttr.mUserContextId = userContextIdStr.ToInteger(&rv); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} diff --git a/dom/base/nsFrameLoader.h b/dom/base/nsFrameLoader.h index 7fe427efd0..fcf99edd4d 100644 --- a/dom/base/nsFrameLoader.h +++ b/dom/base/nsFrameLoader.h @@ -39,6 +39,9 @@ class nsIDocShellTreeOwner; class mozIApplication; namespace mozilla { + +class DocShellOriginAttributes; + namespace dom { class ContentParent; class PBrowserParent; @@ -340,6 +343,9 @@ private: }; void MaybeUpdatePrimaryTabParent(TabParentChange aChange); + nsresult + PopulateUserContextIdFromAttribute(mozilla::DocShellOriginAttributes& aAttr); + nsCOMPtr mDocShell; nsCOMPtr mURIToLoad; mozilla::dom::Element* mOwnerContent; // WEAK diff --git a/dom/base/test/img_referrer_testserver.sjs b/dom/base/test/img_referrer_testserver.sjs index 25b6636bd5..db60f8a463 100644 --- a/dom/base/test/img_referrer_testserver.sjs +++ b/dom/base/test/img_referrer_testserver.sjs @@ -14,7 +14,7 @@ function createTestPage(aHead, aImgPolicy, aName) { '+ aHead + '\n\ - \n\ + \n\ - - - - - - Mozilla Bug 965421 - - This image is going to load - - - - - diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 65429b2865..0ae300d7b5 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -872,6 +872,35 @@ private: CanvasRenderingContext2D *mContext; }; +class CanvasFilterChainObserver : public nsSVGFilterChainObserver +{ +public: + CanvasFilterChainObserver(nsTArray& aFilters, + Element* aCanvasElement, + CanvasRenderingContext2D* aContext) + : nsSVGFilterChainObserver(aFilters, aCanvasElement) + , mContext(aContext) + { + } + + virtual void DoUpdate() override + { + if (!mContext) { + MOZ_CRASH("This should never be called without a context"); + } + // Refresh the cached FilterDescription in mContext->CurrentState().filter. + // If this filter is not at the top of the state stack, we'll refresh the + // wrong filter, but that's ok, because we'll refresh the right filter + // when we pop the state stack in CanvasRenderingContext2D::Restore(). + mContext->UpdateFilter(); + } + + void DetachFromContext() { mContext = nullptr; } + +private: + CanvasRenderingContext2D *mContext; +}; + NS_IMPL_CYCLE_COLLECTING_ADDREF(CanvasRenderingContext2D) NS_IMPL_CYCLE_COLLECTING_RELEASE(CanvasRenderingContext2D) @@ -887,6 +916,12 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CanvasRenderingContext2D) ImplCycleCollectionUnlink(tmp->mStyleStack[i].patternStyles[Style::FILL]); ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::STROKE]); ImplCycleCollectionUnlink(tmp->mStyleStack[i].gradientStyles[Style::FILL]); + CanvasFilterChainObserver *filterChainObserver = + static_cast(tmp->mStyleStack[i].filterChainObserver.get()); + if (filterChainObserver) { + filterChainObserver->DetachFromContext(); + } + ImplCycleCollectionUnlink(tmp->mStyleStack[i].filterChainObserver); } for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) { RegionInfo& info = tmp->mHitRegionsOptions[x]; @@ -904,6 +939,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CanvasRenderingContext2D) ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].patternStyles[Style::FILL], "Fill CanvasPattern"); ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::STROKE], "Stroke CanvasGradient"); ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].gradientStyles[Style::FILL], "Fill CanvasGradient"); + ImplCycleCollectionTraverse(cb, tmp->mStyleStack[i].filterChainObserver, "Filter Chain Observer"); } for (size_t x = 0 ; x < tmp->mHitRegionsOptions.Length(); x++) { RegionInfo& info = tmp->mHitRegionsOptions[x]; @@ -1182,7 +1218,7 @@ CanvasRenderingContext2D::Redraw(const gfx::Rect& aR) void CanvasRenderingContext2D::DidRefresh() { - if (IsTargetValid() && SkiaGLTex()) { + if (IsTargetValid() && mIsSkiaGL) { SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); MOZ_ASSERT(glue); @@ -2425,30 +2461,6 @@ CanvasRenderingContext2D::ParseFilter(const nsAString& aString, return true; } -class CanvasFilterChainObserver : public nsSVGFilterChainObserver -{ -public: - CanvasFilterChainObserver(nsTArray& aFilters, - Element* aCanvasElement, - CanvasRenderingContext2D* aContext) - : nsSVGFilterChainObserver(aFilters, aCanvasElement) - , mContext(aContext) - { - } - - virtual void DoUpdate() override - { - // Refresh the cached FilterDescription in mContext->CurrentState().filter. - // If this filter is not at the top of the state stack, we'll refresh the - // wrong filter, but that's ok, because we'll refresh the right filter - // when we pop the state stack in CanvasRenderingContext2D::Restore(). - mContext->UpdateFilter(); - } - -private: - CanvasRenderingContext2D *mContext; -}; - void CanvasRenderingContext2D::SetFilter(const nsAString& aFilter, ErrorResult& aError) { @@ -5641,20 +5653,19 @@ CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, CanvasLayer::Data data; - GLuint skiaGLTex = SkiaGLTex(); - if (mIsSkiaGL && skiaGLTex) { - SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); - MOZ_ASSERT(glue); - - data.mGLContext = glue->GetGLContext(); - data.mFrontbufferGLTex = skiaGLTex; - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; - } else { - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; + if (mIsSkiaGL) { + GLuint skiaGLTex = SkiaGLTex(); + if (skiaGLTex) { + SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); + MOZ_ASSERT(glue); + data.mGLContext = glue->GetGLContext(); + data.mFrontbufferGLTex = skiaGLTex; + } } + PersistentBufferProvider *provider = GetBufferProvider(aManager); + data.mBufferProvider = provider; + if (userData && userData->IsForContext(this) && static_cast(aOldLayer)->IsDataValid(data)) { @@ -5695,20 +5706,20 @@ CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, canvasLayer->SetPreTransactionCallback( CanvasRenderingContext2DUserData::PreTransactionCallback, userData); - GLuint skiaGLTex = SkiaGLTex(); - if (mIsSkiaGL && skiaGLTex) { - SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); - MOZ_ASSERT(glue); - data.mGLContext = glue->GetGLContext(); - data.mFrontbufferGLTex = skiaGLTex; - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; - } else { - PersistentBufferProvider *provider = GetBufferProvider(aManager); - data.mBufferProvider = provider; + if (mIsSkiaGL) { + GLuint skiaGLTex = SkiaGLTex(); + if (skiaGLTex) { + SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); + MOZ_ASSERT(glue); + data.mGLContext = glue->GetGLContext(); + data.mFrontbufferGLTex = skiaGLTex; + } } + PersistentBufferProvider *provider = GetBufferProvider(aManager); + data.mBufferProvider = provider; + canvasLayer->Initialize(data); uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0; canvasLayer->SetContentFlags(flags); diff --git a/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp b/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp index 2d0c98b29c..21bc0838f4 100644 --- a/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp +++ b/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp @@ -8,8 +8,8 @@ #include "WebGLElementArrayCache.cpp" +#include #include -#include #include "nscore.h" #include "nsTArray.h" @@ -21,7 +21,7 @@ VerifyImplFunction(bool condition, const char* file, int line) if (condition) { gTestsPassed++; } else { - std::cerr << "Test failed at " << file << ":" << line << std::endl; + std::fprintf(stderr, "Test failed at %s:%d\n", file, line); abort(); } } @@ -226,7 +226,7 @@ main(int argc, char* argv[]) } // i } // maxBufferSize - std::cerr << argv[0] << ": all " << gTestsPassed << " tests passed" << std::endl; + std::fprintf(stderr, "%s: all %d tests passed\n", argv[0], gTestsPassed); return 0; } diff --git a/dom/cellbroadcast/tests/marionette/head.js b/dom/cellbroadcast/tests/marionette/head.js index ed8de04460..0917791b4a 100644 --- a/dom/cellbroadcast/tests/marionette/head.js +++ b/dom/cellbroadcast/tests/marionette/head.js @@ -39,6 +39,9 @@ GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_2] = "user-2"; const CB_MESSAGE_SIZE_GSM = 88; const CB_MESSAGE_SIZE_ETWS = 56; +const CB_UMTS_MESSAGE_TYPE_CBS = 1; +const CB_UMTS_MESSAGE_PAGE_SIZE = 82; + const CB_GSM_MESSAGEID_ETWS_BEGIN = 0x1100; const CB_GSM_MESSAGEID_ETWS_END = 0x1107; @@ -189,7 +192,7 @@ function decodeGsmDataCodingScheme(aDcs) { * * @return A deferred promise. */ -let cbManager; +var cbManager; function ensureCellBroadcast() { let deferred = Promise.defer(); @@ -201,18 +204,28 @@ function ensureCellBroadcast() { SpecialPowers.pushPermissions(permissions, function() { ok(true, "permissions pushed: " + JSON.stringify(permissions)); - cbManager = window.navigator.mozCellBroadcast; - if (cbManager) { - log("navigator.mozCellBroadcast is instance of " + cbManager.constructor); - } else { - log("navigator.mozCellBroadcast is undefined."); - } + // Permission changes can't change existing Navigator.prototype + // objects, so grab our objects from a new Navigator. + let workingFrame = document.createElement("iframe"); + workingFrame.addEventListener("load", function load() { + workingFrame.removeEventListener("load", load); - if (cbManager instanceof window.MozCellBroadcast) { - deferred.resolve(cbManager); - } else { - deferred.reject(); - } + cbManager = workingFrame.contentWindow.navigator.mozCellBroadcast; + + if (cbManager) { + log("navigator.mozCellBroadcast is instance of " + cbManager.constructor); + } else { + log("navigator.mozCellBroadcast is undefined."); + } + + if (cbManager instanceof window.MozCellBroadcast) { + deferred.resolve(cbManager); + } else { + deferred.reject(); + } + }); + + document.body.appendChild(workingFrame); }); return deferred.promise; @@ -233,7 +246,7 @@ function ensureCellBroadcast() { * * @return A deferred promise. */ -let pendingEmulatorCmdCount = 0; +var pendingEmulatorCmdCount = 0; function runEmulatorCmdSafe(aCommand) { let deferred = Promise.defer(); diff --git a/dom/cellbroadcast/tests/marionette/manifest.ini b/dom/cellbroadcast/tests/marionette/manifest.ini index dc649fa172..8b440e887d 100644 --- a/dom/cellbroadcast/tests/marionette/manifest.ini +++ b/dom/cellbroadcast/tests/marionette/manifest.ini @@ -5,5 +5,9 @@ qemu = true [test_cellbroadcast_etws.js] [test_cellbroadcast_gsm.js] +[test_cellbroadcast_gsm_language_and_body.js] +disabled = Bug 1231462 [test_cellbroadcast_multi_sim.js] -[test_cellbroadcast_umts.js] \ No newline at end of file +[test_cellbroadcast_umts.js] +[test_cellbroadcast_umts_language_and_body.js] +disabled = Bug 1224992 diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js index 557bcbc08b..fe4b75ac86 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_etws.js @@ -1,7 +1,7 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -MARIONETTE_TIMEOUT = 20000; +MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = 'head.js'; function testReceiving_ETWS_MessageAttributes() { @@ -91,7 +91,7 @@ function testReceiving_ETWS_MessageId() { // Message Identifier has 16 bits, but no bitwise operation is needed. // Test some selected values only. let messageIds = [ - 0x0000, 0x0001, 0x0010, 0x0100, 0x1000, 0x1111, 0x8888, 0x8811, + 0x0000, 0x0001, 0x0010, 0x0100, 0x1111, 0x8888, 0x8811, ]; let verifyCBMessage = (aMessage, aMessageId) => { diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js index 97d826f2ae..8b749737fb 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm.js @@ -90,7 +90,7 @@ function testReceiving_GSM_MessageId() { // Message Identifier has 16 bits, but no bitwise operation is needed. // Test some selected values only. let messageIds = [ - 0x0000, 0x0001, 0x0010, 0x0100, 0x1000, 0x1111, 0x8888, 0x8811, + 0x0000, 0x0001, 0x0010, 0x0100, 0x1111, 0x8888, 0x8811, ]; let verifyCBMessage = (aMessage, aMessageId) => { @@ -110,62 +110,6 @@ function testReceiving_GSM_MessageId() { return promise; } -function testReceiving_GSM_Language_and_Body() { - log("Test receiving GSM Cell Broadcast - Language & Body"); - - let promise = Promise.resolve(); - - let testDcs = []; - let dcs = 0; - while (dcs <= 0xFF) { - try { - let dcsInfo = { dcs: dcs }; - [ dcsInfo.encoding, dcsInfo.language, - dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); - testDcs.push(dcsInfo); - } catch (e) { - // Unsupported coding group, skip. - dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; - } - dcs++; - } - - let verifyCBMessage = (aMessage, aDcsInfo) => { - if (aDcsInfo.language) { - is(aMessage.language, aDcsInfo.language, "aMessage.language"); - } else if (aDcsInfo.indicator) { - is(aMessage.language, "@@", "aMessage.language"); - } else { - ok(aMessage.language == null, "aMessage.language"); - } - - switch (aDcsInfo.encoding) { - case PDU_DCS_MSG_CODING_7BITS_ALPHABET: - is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_8BITS_ALPHABET: - ok(aMessage.body == null, "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_16BITS_ALPHABET: - is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, "aMessage.body"); - break; - } - - is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); - }; - - testDcs.forEach(function(aDcsInfo) { - let pdu = buildHexStr(0, 8) - + buildHexStr(aDcsInfo.dcs, 2) - + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 5) * 2); - promise = promise - .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) - .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); - }); - - return promise; -} - function testReceiving_GSM_Timestamp() { log("Test receiving GSM Cell Broadcast - Timestamp"); @@ -351,7 +295,6 @@ startTestCommon(function testCaseMain() { .then(() => testReceiving_GSM_GeographicalScope()) .then(() => testReceiving_GSM_MessageCode()) .then(() => testReceiving_GSM_MessageId()) - .then(() => testReceiving_GSM_Language_and_Body()) .then(() => testReceiving_GSM_Timestamp()) .then(() => testReceiving_GSM_WarningType()) .then(() => testReceiving_GSM_EmergencyUserAlert()) diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm_language_and_body.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm_language_and_body.js new file mode 100644 index 0000000000..7953532340 --- /dev/null +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_gsm_language_and_body.js @@ -0,0 +1,64 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 90000; +MARIONETTE_HEAD_JS = 'head.js'; + +function testReceiving_GSM_Language_and_Body() { + log("Test receiving GSM Cell Broadcast - Language & Body"); + + let promise = Promise.resolve(); + + let testDcs = []; + let dcs = 0; + while (dcs <= 0xFF) { + try { + let dcsInfo = { dcs: dcs }; + [ dcsInfo.encoding, dcsInfo.language, + dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); + testDcs.push(dcsInfo); + } catch (e) { + // Unsupported coding group, skip. + dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; + } + dcs++; + } + + let verifyCBMessage = (aMessage, aDcsInfo) => { + if (aDcsInfo.language) { + is(aMessage.language, aDcsInfo.language, "aMessage.language"); + } else if (aDcsInfo.indicator) { + is(aMessage.language, "@@", "aMessage.language"); + } else { + ok(aMessage.language == null, "aMessage.language"); + } + + switch (aDcsInfo.encoding) { + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: + is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: + ok(aMessage.body == null, "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: + is(aMessage.body, aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, "aMessage.body"); + break; + } + + is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); + }; + + ok(testDcs.length, "testDcs.length"); + testDcs.forEach(function(aDcsInfo) { + let pdu = buildHexStr(0, 8) + + buildHexStr(aDcsInfo.dcs, 2) + + buildHexStr(0, (CB_MESSAGE_SIZE_GSM - 5) * 2); + promise = promise + .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) + .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); + }); + + return promise; +} + +startTestCommon(() => testReceiving_GSM_Language_and_Body()); diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js index eb322d0395..e759cd47a4 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_multi_sim.js @@ -1,7 +1,7 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -MARIONETTE_TIMEOUT = 10000; +MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = 'head.js'; function testReceiving_MultiSIM() { diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js index d08a565ba3..f9e85c1c38 100644 --- a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts.js @@ -1,9 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + MARIONETTE_TIMEOUT = 90000; MARIONETTE_HEAD_JS = 'head.js'; -const CB_UMTS_MESSAGE_TYPE_CBS = 1; -const CB_UMTS_MESSAGE_PAGE_SIZE = 82; - function testReceiving_UMTS_MessageAttributes() { log("Test receiving UMTS Cell Broadcast - Message Attributes"); @@ -102,7 +102,7 @@ function testReceiving_UMTS_MessageId() { // Message Identifier has 16 bits, but no bitwise operation is needed. // Test some selected values only. let messageIds = [ - 0x0000, 0x0001, 0x0010, 0x0100, 0x1000, 0x1111, 0x8888, 0x8811, + 0x0000, 0x0001, 0x0010, 0x0100, 0x1111, 0x8888, 0x8811, ]; let verifyCBMessage = (aMessage, aMessageId) => { @@ -126,70 +126,6 @@ function testReceiving_UMTS_MessageId() { return promise; } -function testReceiving_UMTS_Language_and_Body() { - log("Test receiving UMTS Cell Broadcast - Language & Body"); - - let promise = Promise.resolve(); - - let testDcs = []; - let dcs = 0; - while (dcs <= 0xFF) { - try { - let dcsInfo = { dcs: dcs }; - [ dcsInfo.encoding, dcsInfo.language, - dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); - testDcs.push(dcsInfo); - } catch (e) { - // Unsupported coding group, skip. - dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; - } - dcs++; - } - - let verifyCBMessage = (aMessage, aDcsInfo) => { - if (aDcsInfo.language) { - is(aMessage.language, aDcsInfo.language, "aMessage.language"); - } else if (aDcsInfo.indicator) { - is(aMessage.language, "@@", "aMessage.language"); - } else { - ok(aMessage.language == null, "aMessage.language"); - } - - switch (aDcsInfo.encoding) { - case PDU_DCS_MSG_CODING_7BITS_ALPHABET: - is(aMessage.body, - aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, - "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_8BITS_ALPHABET: - ok(aMessage.body == null, "aMessage.body"); - break; - case PDU_DCS_MSG_CODING_16BITS_ALPHABET: - is(aMessage.body, - aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, - "aMessage.body"); - break; - } - - is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); - }; - - testDcs.forEach(function(aDcsInfo) { - let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type - + buildHexStr(0, 4) // skip msg_id - + buildHexStr(0, 4) // skip SN - + buildHexStr(aDcsInfo.dcs, 2) // set dcs - + buildHexStr(1, 2) // set num_of_pages to 1 - + buildHexStr(0, CB_UMTS_MESSAGE_PAGE_SIZE * 2) - + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length - promise = promise - .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) - .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); - }); - - return promise; -} - function testReceiving_UMTS_Timestamp() { log("Test receiving UMTS Cell Broadcast - Timestamp"); @@ -441,7 +377,6 @@ startTestCommon(function testCaseMain() { .then(() => testReceiving_UMTS_GeographicalScope()) .then(() => testReceiving_UMTS_MessageCode()) .then(() => testReceiving_UMTS_MessageId()) - .then(() => testReceiving_UMTS_Language_and_Body()) .then(() => testReceiving_UMTS_Timestamp()) .then(() => testReceiving_UMTS_WarningType()) .then(() => testReceiving_UMTS_EmergencyUserAlert()) diff --git a/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts_language_and_body.js b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts_language_and_body.js new file mode 100644 index 0000000000..f7846452a7 --- /dev/null +++ b/dom/cellbroadcast/tests/marionette/test_cellbroadcast_umts_language_and_body.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 90000; +MARIONETTE_HEAD_JS = 'head.js'; + +function testReceiving_UMTS_Language_and_Body() { + log("Test receiving UMTS Cell Broadcast - Language & Body"); + + let promise = Promise.resolve(); + + let testDcs = []; + let dcs = 0; + while (dcs <= 0xFF) { + try { + let dcsInfo = { dcs: dcs }; + [ dcsInfo.encoding, dcsInfo.language, + dcsInfo.indicator, dcsInfo.messageClass ] = decodeGsmDataCodingScheme(dcs); + testDcs.push(dcsInfo); + } catch (e) { + // Unsupported coding group, skip. + dcs = (dcs & PDU_DCS_CODING_GROUP_BITS) + 0x10; + } + dcs++; + } + + let verifyCBMessage = (aMessage, aDcsInfo) => { + if (aDcsInfo.language) { + is(aMessage.language, aDcsInfo.language, "aMessage.language"); + } else if (aDcsInfo.indicator) { + is(aMessage.language, "@@", "aMessage.language"); + } else { + ok(aMessage.language == null, "aMessage.language"); + } + + switch (aDcsInfo.encoding) { + case PDU_DCS_MSG_CODING_7BITS_ALPHABET: + is(aMessage.body, + aDcsInfo.indicator ? DUMMY_BODY_7BITS_IND : DUMMY_BODY_7BITS, + "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_8BITS_ALPHABET: + ok(aMessage.body == null, "aMessage.body"); + break; + case PDU_DCS_MSG_CODING_16BITS_ALPHABET: + is(aMessage.body, + aDcsInfo.indicator ? DUMMY_BODY_UCS2_IND : DUMMY_BODY_UCS2, + "aMessage.body"); + break; + } + + is(aMessage.messageClass, aDcsInfo.messageClass, "aMessage.messageClass"); + }; + + testDcs.forEach(function(aDcsInfo) { + let pdu = buildHexStr(CB_UMTS_MESSAGE_TYPE_CBS, 2) // msg_type + + buildHexStr(0, 4) // skip msg_id + + buildHexStr(0, 4) // skip SN + + buildHexStr(aDcsInfo.dcs, 2) // set dcs + + buildHexStr(1, 2) // set num_of_pages to 1 + + buildHexStr(0, CB_UMTS_MESSAGE_PAGE_SIZE * 2) + + buildHexStr(CB_UMTS_MESSAGE_PAGE_SIZE, 2); // msg_info_length + promise = promise + .then(() => sendMultipleRawCbsToEmulatorAndWait([pdu])) + .then((aMessage) => verifyCBMessage(aMessage, aDcsInfo)); + }); + + return promise; +} + +startTestCommon(() => testReceiving_UMTS_Language_and_Body()); diff --git a/dom/contacts/tests/test_migration.html b/dom/contacts/tests/test_migration.html index 18141899c1..ff0c39cdd1 100644 --- a/dom/contacts/tests/test_migration.html +++ b/dom/contacts/tests/test_migration.html @@ -1,209 +1,28 @@ - + - Migration tests - - - + + -

migration tests

-

- +
-
-
 
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index 7ddfadd3d5..9985b82d1d 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -109,6 +109,7 @@ #include "nsComputedDOMStyle.h" #include "mozilla/StyleSetHandle.h" #include "mozilla/StyleSetHandleInlines.h" +#include "ReferrerPolicy.h" using namespace mozilla; using namespace mozilla::dom; @@ -1275,9 +1276,11 @@ nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString, nsAttrValue& aResult) { static const nsAttrValue::EnumTable kReferrerTable[] = { - { "no-referrer", net::RP_No_Referrer }, - { "origin", net::RP_Origin }, - { "unsafe-url", net::RP_Unsafe_URL }, + { net::kRPS_No_Referrer, net::RP_No_Referrer }, + { net::kRPS_Origin, net::RP_Origin }, + { net::kRPS_Origin_When_Cross_Origin, net::RP_Origin_When_Crossorigin }, + { net::kRPS_No_Referrer_When_Downgrade, net::RP_No_Referrer_When_Downgrade }, + { net::kRPS_Unsafe_URL, net::RP_Unsafe_URL }, { 0 } }; return aResult.ParseEnumValue(aString, kReferrerTable, false); diff --git a/dom/html/nsHTMLDNSPrefetch.cpp b/dom/html/nsHTMLDNSPrefetch.cpp index 82cb1e097e..331a005c6a 100644 --- a/dom/html/nsHTMLDNSPrefetch.cpp +++ b/dom/html/nsHTMLDNSPrefetch.cpp @@ -320,7 +320,7 @@ nsHTMLDNSPrefetch::nsDeferrals::SubmitQueue() if (link && link->HasDeferredDNSPrefetchRequest()) { nsCOMPtr hrefURI(link ? link->GetURI() : nullptr); bool isLocalResource = false; - nsresult rv; + nsresult rv = NS_OK; hostName.Truncate(); if (hrefURI) { diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp index d0ee7bc626..8763af5280 100644 --- a/dom/html/nsHTMLDocument.cpp +++ b/dom/html/nsHTMLDocument.cpp @@ -934,23 +934,22 @@ nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv) return; } - nsAutoCString newURIString; - if (NS_FAILED(uri->GetScheme(newURIString))) { - rv.Throw(NS_ERROR_FAILURE); - return; - } - nsAutoCString path; - if (NS_FAILED(uri->GetPath(path))) { - rv.Throw(NS_ERROR_FAILURE); - return; - } - newURIString.AppendLiteral("://"); - AppendUTF16toUTF8(aDomain, newURIString); - newURIString.Append(path); - nsCOMPtr newURI; - if (NS_FAILED(NS_NewURI(getter_AddRefs(newURI), newURIString))) { - rv.Throw(NS_ERROR_FAILURE); + nsresult rv2 = uri->Clone(getter_AddRefs(newURI)); + if (NS_FAILED(rv2)) { + rv.Throw(rv2); + return; + } + + rv2 = newURI->SetUserPass(EmptyCString()); + if (NS_FAILED(rv2)) { + rv.Throw(rv2); + return; + } + + rv2 = newURI->SetHostPort(NS_ConvertUTF16toUTF8(aDomain)); + if (NS_FAILED(rv2)) { + rv.Throw(rv2); return; } @@ -989,6 +988,7 @@ nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv) return; } + NS_TryToSetImmutable(newURI); rv = NodePrincipal()->SetDomain(newURI); } diff --git a/dom/html/nsTextEditorState.cpp b/dom/html/nsTextEditorState.cpp index ca74571a1e..7683fe0274 100644 --- a/dom/html/nsTextEditorState.cpp +++ b/dom/html/nsTextEditorState.cpp @@ -1918,7 +1918,7 @@ nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const if (!mTextCtrlElement->ValueChanged() || !mValue) { mTextCtrlElement->GetDefaultValueFromContent(aValue); } else { - aValue = NS_ConvertUTF8toUTF16(*mValue); + aValue = *mValue; } } } @@ -2139,7 +2139,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags) } } else { if (!mValue) { - mValue = new nsCString; + mValue.emplace(); } nsString value; if (!value.Assign(newValue, fallible)) { @@ -2148,7 +2148,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, uint32_t aFlags) if (!nsContentUtils::PlatformToDOMLineBreaks(value, fallible)) { return false; } - if (!CopyUTF16toUTF8(value, *mValue, fallible)) { + if (!mValue->Assign(value, fallible)) { return false; } diff --git a/dom/html/nsTextEditorState.h b/dom/html/nsTextEditorState.h index 06aefcabad..7a15c04528 100644 --- a/dom/html/nsTextEditorState.h +++ b/dom/html/nsTextEditorState.h @@ -14,6 +14,7 @@ #include "nsCycleCollectionParticipant.h" #include "mozilla/dom/Element.h" #include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" #include "mozilla/WeakPtr.h" class nsTextInputListener; @@ -292,7 +293,7 @@ private: nsCOMPtr mPlaceholderDiv; nsTextControlFrame* mBoundFrame; RefPtr mTextListener; - nsAutoPtr mValue; + mozilla::Maybe mValue; RefPtr mMutationObserver; mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control. // mValueBeingSet is available only while SetValue() is requesting to commit diff --git a/dom/mobileconnection/tests/marionette/head_chrome.js b/dom/mobileconnection/tests/marionette/head_chrome.js new file mode 100644 index 0000000000..6b29d6864e --- /dev/null +++ b/dom/mobileconnection/tests/marionette/head_chrome.js @@ -0,0 +1,144 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_CONTEXT = "chrome"; + +var XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils; +var Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; + +var mobileConnectionService = + Cc["@mozilla.org/mobileconnection/gonkmobileconnectionservice;1"] + .getService(Ci.nsIMobileConnectionService); +ok(mobileConnectionService, + "mobileConnectionService.constructor is " + mobileConnectionService.constructor); + +var _pendingEmulatorShellCmdCount = 0; + +/** + * Send emulator shell command with safe guard. + * + * We should only call |finish()| after all emulator shell command transactions + * end, so here comes with the pending counter. Resolve when the emulator + * shell gives response. Never reject. + * + * Fulfill params: + * result -- an array of emulator shell response lines. + * + * @param aCommands + * A string array commands to be passed to emulator through adb shell. + * + * @return A deferred promise. + */ +function runEmulatorShellCmdSafe(aCommands) { + return new Promise(function(aResolve, aReject) { + ++_pendingEmulatorShellCmdCount; + runEmulatorShell(aCommands, function(aResult) { + --_pendingEmulatorShellCmdCount; + + ok(true, "Emulator shell response: " + JSON.stringify(aResult)); + aResolve(aResult); + }); + }); +} + +/** + * Get nsIMobileConnection by clientId + * + * @param aClient [optional] + * A numeric DSDS client id. Default: 0. + * + * @return A nsIMobileConnection. + */ +function getMobileConnection(aClientId = 0) { + let mobileConnection = mobileConnectionService.getItemByServiceId(0); + ok(mobileConnection, + "mobileConnection.constructor is " + mobileConnection.constructor); + return mobileConnection; +} + +/** + * Get Neighboring Cell Ids. + * + * Fulfill params: + * An array of nsINeighboringCellInfo. + * Reject params: + * 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure' + * + * @param aClient [optional] + * A numeric DSDS client id. Default: 0. + * + * @return A deferred promise. + */ +function getNeighboringCellIds(aClientId = 0) { + let mobileConnection = getMobileConnection(aClientId); + return new Promise(function(aResolve, aReject) { + ok(true, "getNeighboringCellIds"); + mobileConnection.getNeighboringCellIds({ + QueryInterface: XPCOMUtils.generateQI([Ci.nsINeighboringCellIdsCallback]), + notifyGetNeighboringCellIds: function(aCount, aResults) { + aResolve(aResults); + }, + notifyGetNeighboringCellIdsFailed: function(aErrorMsg) { + aReject(aErrorMsg); + }, + }); + }); +} + +/** + * Get Cell Info List. + * + * Fulfill params: + * An array of nsICellInfo. + * Reject params: + * 'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure' + * + * @param aClient [optional] + * A numeric DSDS client id. Default: 0. + * + * @return A deferred promise. + */ +function getCellInfoList(aClientId = 0) { + let mobileConnection = getMobileConnection(aClientId); + return new Promise(function(aResolve, aReject) { + ok(true, "getCellInfoList"); + mobileConnection.getCellInfoList({ + QueryInterface: XPCOMUtils.generateQI([Ci.nsICellInfoListCallback]), + notifyGetCellInfoList: function(aCount, aResults) { + aResolve(aResults); + }, + notifyGetCellInfoListFailed: function(aErrorMsg) { + aReject(aErrorMsg); + }, + }); + }); +} + +/** + * Wait for pending emulator transactions and call |finish()|. + */ +function cleanUp() { + // Use ok here so that we have at least one test run. + ok(true, ":: CLEANING UP ::"); + + waitFor(finish, function() { + return _pendingEmulatorShellCmdCount === 0; + }); +} + +/** + * Basic test routine helper. + * + * This helper does nothing but clean-ups. + * + * @param aTestCaseMain + * A function that takes no parameter. + */ +function startTestBase(aTestCaseMain) { + return Promise.resolve() + .then(aTestCaseMain) + .catch((aException) => { + ok(false, "promise rejects during test: " + aException); + }) + .then(cleanUp); +} diff --git a/dom/mobileconnection/tests/marionette/manifest.ini b/dom/mobileconnection/tests/marionette/manifest.ini index 5879c3ee05..7780d633f6 100644 --- a/dom/mobileconnection/tests/marionette/manifest.ini +++ b/dom/mobileconnection/tests/marionette/manifest.ini @@ -8,6 +8,7 @@ qemu = true [test_mobile_voice_location.js] [test_mobile_operator_names.js] [test_mobile_operator_names_plmnlist.js] +disabled = Bug 1234746 [test_mobile_operator_names_roaming.js] [test_mobile_preferred_network_type.js] [test_mobile_preferred_network_type_radio_off.js] @@ -15,6 +16,7 @@ qemu = true [test_mobile_data_location.js] [test_mobile_data_state.js] [test_mobile_roaming_preference.js] +[test_call_barring_basic_operations.js] [test_call_barring_get_error.js] [test_call_barring_set_error.js] [test_call_barring_change_password.js] @@ -25,7 +27,6 @@ qemu = true [test_mobile_connections_array_uninitialized.js] [test_mobile_signal_strength.js] [test_mobile_data_ipv6.js] -disabled = Bug 979137 [test_mobile_supported_network_types.js] [test_mobile_call_forwarding.js] [test_mobile_call_forwarding_set_error.js] @@ -35,3 +36,5 @@ disabled = Bug 979137 [test_mobile_clir.js] [test_mobile_clir_radio_off.js] [test_mobile_neighboring_cell_ids.js] +[test_mobile_cell_Info_list.js] +skip-if = android_version < '19' diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_basic_operations.js b/dom/mobileconnection/tests/marionette/test_call_barring_basic_operations.js new file mode 100644 index 0000000000..07781fbb84 --- /dev/null +++ b/dom/mobileconnection/tests/marionette/test_call_barring_basic_operations.js @@ -0,0 +1,130 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +const AO = MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING; +const OI = MozMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL; +const OX = MozMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_INTERNATIONAL_EXCEPT_HOME; +var outgoingPrograms = [null, AO, OI, OX]; + +const AI = MozMobileConnection.CALL_BARRING_PROGRAM_ALL_INCOMING; +const IR = MozMobileConnection.CALL_BARRING_PROGRAM_INCOMING_ROAMING; +var incomingPrograms = [null, AI, IR]; + +const SERVICE_CLASS_VOICE = MozMobileConnection.ICC_SERVICE_CLASS_VOICE; + +function getProgram(aProgram, aEnabled) { + if (aProgram === null) { + return Promise.resolve(); + } + + return Promise.resolve() + .then(() => getCallBarringOption({ + program: aProgram, + serviceClass: SERVICE_CLASS_VOICE + })) + + .then(result => { + is(result.program, aProgram, "Program"); + is(result.enabled, aEnabled, "Enabled"); + is(result.serviceClass, SERVICE_CLASS_VOICE, "ServiceClass"); + }); +} + +function setProgram(aProgram, aEnabled) { + if (aProgram === null) { + return Promise.resolve(); + } + + return Promise.resolve() + .then(() => setCallBarringOption({ + program: aProgram, + serviceClass: SERVICE_CLASS_VOICE, + enabled: aEnabled, + password: "0000" // The dafault call barring password of the emulator + })); +} + +function setAndCheckProgram(aActiveProgram, aAllPrograms) { + let promise = Promise.resolve(); + + if (aActiveProgram !== null) { + promise = promise.then(() => setProgram(aActiveProgram, true)); + } else { + // Deactive all barring programs in |aAllPrograms|. + promise = aAllPrograms.reduce((previousPromise, program) => { + return previousPromise.then(() => setProgram(program, false)); + }, promise); + } + + // Make sure |aActiveProgram| is the only active program in |aAllPrograms|. + promise = aAllPrograms.reduce((previousPromise, program) => { + return previousPromise.then(() => getProgram(program, program === aActiveProgram)); + }, promise); + + return promise; +} + +function testSingleProgram(aOriginProgram, aTargetPrograms, aOriginPrograms) { + let promise = setAndCheckProgram(aOriginProgram, aOriginPrograms); + + return aTargetPrograms.reduce((previousPromise, targetProgram) => { + return previousPromise + .then(() => log(aOriginProgram + " <-> " + targetProgram)) + .then(() => setAndCheckProgram(targetProgram, aOriginPrograms)) + .then(() => setAndCheckProgram(aOriginProgram, aOriginPrograms)); + }, promise); +} + +function testAllPrograms(aPrograms) { + let targetPrograms = aPrograms.slice(); + + return aPrograms.reduce((previousPromise, program) => { + return previousPromise + .then(() => { + targetPrograms.shift(); + return testSingleProgram(program, targetPrograms, aPrograms); + }); + }, Promise.resolve()); +} + +function testUnsupportedPrograms() { + // Emulator now doesn't support these three programs. + let unsupportedPrograms = + [MozMobileConnection.CALL_BARRING_PROGRAM_ALL_SERVICE, + MozMobileConnection.CALL_BARRING_PROGRAM_OUTGOING_SERVICE, + MozMobileConnection.CALL_BARRING_PROGRAM_INCOMING_SERVICE]; + + return unsupportedPrograms.reduce((previousPromise, program) => { + return previousPromise + .then(() => log("Test " + program)) + .then(() => setProgram(program, false)) + .then(() => { + ok(false, "Should be rejected"); + }, error => { + is(error.name, "RequestNotSupported", + "Failed to setProgram: "+ error.name); + }); + }, Promise.resolve()); +} + +// Start tests +startTestCommon(function() { + return Promise.resolve() + .then(() => setAndCheckProgram(null, outgoingPrograms)) + .then(() => setAndCheckProgram(null, incomingPrograms)) + + .then(() => log("=== Test outgoing call barring programs ===")) + .then(() => testAllPrograms(outgoingPrograms)) + .then(() => log("=== Test incoming call barring programs ===")) + .then(() => testAllPrograms(incomingPrograms)) + + .then(() => log("=== Test unsupported call barring programs ===")) + .then(() => testUnsupportedPrograms()) + + .catch(aError => ok(false, "promise rejects during test: " + aError)) + .then(() => setAndCheckProgram(null, outgoingPrograms)) + .then(() => setAndCheckProgram(null, incomingPrograms)); +}); diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js b/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js index 707be64a7d..9ca0a3d622 100644 --- a/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js +++ b/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js @@ -4,33 +4,11 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const TEST_DATA = [ - // [, , ] +function testChangeCallBarringPassword(aExpectedError, aOptions) { + log("Test changing call barring password from " + + aOptions.pin + " to " + aOptions.newPin); - // Test passing an invalid pin or newPin. - [null, "0000", "InvalidPassword"], - ["0000", null, "InvalidPassword"], - [null, null, "InvalidPassword"], - - // Test passing mismatched newPin. - ["000", "0000", "InvalidPassword"], - ["00000", "1111", "InvalidPassword"], - ["abcd", "efgh", "InvalidPassword"], - - // TODO: Bug 906603 - B2G RIL: Support Change Call Barring Password on Emulator. - // Currently emulator doesn't support REQUEST_CHANGE_BARRING_PASSWORD, so we - // expect to get a 'RequestNotSupported' error here. - ["1234", "1234", "RequestNotSupported"] -]; - -function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) { - log("Test changing call barring password to " + aPin + "/" + aNewPin); - - let options = { - pin: aPin, - newPin: aNewPin - }; - return changeCallBarringPassword(options) + return changeCallBarringPassword(aOptions) .then(function resolve() { ok(!aExpectedError, "changeCallBarringPassword success"); }, function reject(aError) { @@ -40,11 +18,67 @@ function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) { // Start tests startTestCommon(function() { - let promise = Promise.resolve(); - for (let i = 0; i < TEST_DATA.length; i++) { - let data = TEST_DATA[i]; - promise = - promise.then(() => testChangeCallBarringPassword(data[0], data[1], data[2])); - } - return promise; + return Promise.resolve() + + // According to TS.22.004 clause 5.2, the password should consist of four + // digits in the range 0000 to 9999. + + // Test passing an invalid pin. + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: null, + newPin: "0000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "000", + newPin: "0000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "00000", + newPin: "0000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "abcd", + newPin: "0000" + })) + + // Test passing an invalid newPin + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: null + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: "000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: "00000" + })) + + .then(() => testChangeCallBarringPassword("InvalidPassword", { + pin: "0000", + newPin: "abcd" + })) + + // Test passing an incorrect password, where the default password is "0000". + .then(() => testChangeCallBarringPassword("IncorrectPassword", { + pin: "1111", + newPin: "2222" + })) + + // Sucessful + .then(() => testChangeCallBarringPassword(null, { + pin: "0000", + newPin: "2222" + })) + + .then(() => testChangeCallBarringPassword(null, { + pin: "2222", + newPin: "0000" + })); }); diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js b/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js index 5b6eb2c46b..a561484503 100644 --- a/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js +++ b/dom/mobileconnection/tests/marionette/test_call_barring_get_error.js @@ -4,59 +4,12 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const TEST_DATA = [ - // Test passing invalid program. - { - options: { - program: 5, /* Invalid program */ - serviceClass: 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - program: null, - serviceClass: 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - /* Undefined program */ - serviceClass: 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid serviceClass. - { - options: { - program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - serviceClass: null - }, - expectedError: "InvalidParameter" - }, { - options: { - program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - /* Undefined serviceClass */ - }, - expectedError: "InvalidParameter" - }, - // TODO: Bug 1027546 - [B2G][Emulator] Support call barring - // Currently emulator doesn't support call barring, so we expect to get a - // 'RequestNotSupported' error here. - { - options: { - program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - serviceClass: 0 - }, - expectedError: "RequestNotSupported" - } -]; - -function testGetCallBarringOption(aOptions, aExpectedError) { - log("Test getting call barring to " + JSON.stringify(aOptions)); +function testGetCallBarringOption(aExpectedError, aOptions) { + log("Test getCallBarringOption with " + JSON.stringify(aOptions)); return getCallBarringOption(aOptions) .then(function resolve(aResult) { - ok(false, "should not success"); + ok(false, "should be rejected"); }, function reject(aError) { is(aError.name, aExpectedError, "failed to getCallBarringOption"); }); @@ -64,11 +17,32 @@ function testGetCallBarringOption(aOptions, aExpectedError) { // Start tests startTestCommon(function() { - let promise = Promise.resolve(); - for (let i = 0; i < TEST_DATA.length; i++) { - let data = TEST_DATA[i]; - promise = promise.then(() => testGetCallBarringOption(data.options, - data.expectedError)); - } - return promise; + return Promise.resolve() + + // Test program + .then(() => testGetCallBarringOption("InvalidParameter", { + program: 8, /* Invalid program */ + serviceClass: 0 + })) + + .then(() => testGetCallBarringOption("InvalidParameter", { + program: null, /* Invalid program */ + serviceClass: 0 + })) + + .then(() => testGetCallBarringOption("InvalidParameter", { + /* Undefined program */ + serviceClass: 0 + })) + + // Test serviceClass + .then(() => testGetCallBarringOption("InvalidParameter", { + program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + serviceClass: null /* Invalid serviceClass */ + })) + + .then(() => testGetCallBarringOption("InvalidParameter", { + program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + /* Undefined serviceClass */ + })); }); diff --git a/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js b/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js index 33d7cd22c7..0dbd4e521d 100644 --- a/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js +++ b/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js @@ -4,107 +4,12 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const TEST_DATA = [ - // Test passing invalid program. - { - options: { - "program": 5, /* Invalid program */ - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": null, - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - /* Undefined program */ - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid enabled. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": null, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - /* Undefined enabled */ - "password": "0000", - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid password. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": null, - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - /* Undefined password */ - "serviceClass": 0 - }, - expectedError: "InvalidParameter" - }, - // Test passing invalid serviceClass. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": "0000", - "serviceClass": null - }, - expectedError: "InvalidParameter" - }, { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": "0000", - /* Undefined serviceClass */ - }, - expectedError: "InvalidParameter" - }, - // TODO: Bug 1027546 - [B2G][Emulator] Support call barring - // Currently emulator doesn't support call barring, so we expect to get a - // 'RequestNotSupported' error here. - { - options: { - "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, - "enabled": true, - "password": "0000", - "serviceClass": 0 - }, - expectedError: "RequestNotSupported" - } -]; - -function testSetCallBarringOption(aOptions, aExpectedError) { - log("Test setting call barring to " + JSON.stringify(aOptions)); +function testSetCallBarringOption(aExpectedError, aOptions) { + log("Test setCallBarringOption with " + JSON.stringify(aOptions)); return setCallBarringOption(aOptions) .then(function resolve() { - ok(false, "changeCallBarringPassword success"); + ok(false, "should be rejected"); }, function reject(aError) { is(aError.name, aExpectedError, "failed to changeCallBarringPassword"); }); @@ -112,11 +17,79 @@ function testSetCallBarringOption(aOptions, aExpectedError) { // Start tests startTestCommon(function() { - let promise = Promise.resolve(); - for (let i = 0; i < TEST_DATA.length; i++) { - let data = TEST_DATA[i]; - promise = promise.then(() => testSetCallBarringOption(data.options, - data.expectedError)); - } - return promise; + return Promise.resolve() + + // Test program + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": 8, /* Invalid program */ + "enabled": true, + "password": "0000", + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": null, + "enabled": true, + "password": "0000", + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + /* Undefined program */ + "enabled": true, + "password": "0000", + "serviceClass": 0 + })) + + // Test enabled + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": null, /* Invalid enabled */ + "password": "0000", + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + /* Undefined enabled */ + "password": "0000", + "serviceClass": 0 + })) + + // Test password + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": null, /* Invalid password */ + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + /* Undefined password */ + "serviceClass": 0 + })) + + .then(() => testSetCallBarringOption("IncorrectPassword", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": "1111", /* Incorrect password */ + "serviceClass": 0 + })) + + // Test serviceClass + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": "0000", + "serviceClass": null /* Invalid serviceClass */ + })) + + .then(() => testSetCallBarringOption("InvalidParameter", { + "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING, + "enabled": true, + "password": "0000", + /* Undefined serviceClass */ + })) }); diff --git a/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js b/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js index 4a9bafccc0..7fa08266d2 100644 --- a/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js +++ b/dom/mobileconnection/tests/marionette/test_dsds_mobile_data_connection.js @@ -6,9 +6,9 @@ MARIONETTE_HEAD_JS = "head.js"; const SETTINGS_KEY_DATA_DEFAULT_ID = "ril.data.defaultServiceId"; -let connections; -let numOfRadioInterfaces; -let currentDataDefaultId = 0; +var connections; +var numOfRadioInterfaces; +var currentDataDefaultId = 0; function muxModem(id) { return runEmulatorCmdSafe("mux modem " + id); diff --git a/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js b/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js new file mode 100644 index 0000000000..6c4dc9246a --- /dev/null +++ b/dom/mobileconnection/tests/marionette/test_mobile_cell_Info_list.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head_chrome.js"; + +// Start test. +startTestBase(function() { + return getCellInfoList() + .then((aResults) => { + // Cell Info are hard-coded in hardware/ril/reference-ril/reference-ril.c. + is(aResults.length, 1, "Check number of cell Info"); + + let cell = aResults[0]; + is(cell.type, Ci.nsICellInfo.CELL_INFO_TYPE_GSM, "Check cell.type"); + is(cell.registered, true, "Check cell.registered"); + + ok(cell instanceof Ci.nsIGsmCellInfo, + "cell.constructor is " + cell.constructor); + + // The data hard-coded in hardware/ril/reference-ril/reference-ril.c + // isn't correct (missing timeStampType), so we skip to check other + // attributes first until we fix it. + }); +}); \ No newline at end of file diff --git a/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js index af5238bf59..7d49979f24 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js @@ -11,7 +11,7 @@ MARIONETTE_HEAD_JS = "head.js"; * * 1) set "ril.data.apnSettings" to a given settings object, * 2) enable data connection and wait for a "datachange" event, - * 3) check the IP address type of the active network interface, + * 3) check the IP address type of the active network info, * 4) disable data connection. * * Fulfill params: (none) @@ -30,12 +30,12 @@ function doTest(aApnSettings, aHaveV4Address, aHaveV6Address) { .then(() => setDataEnabledAndWait(true)) .then(function() { let nm = getNetworkManager(); - let active = nm.active; - ok(active, "Active network interface"); - log(" Interface: " + active.name); + let networkInfo = nm.activeNetworkInfo; + ok(networkInfo, "Active network info"); + log(" Interface: " + networkInfo.name); let ips = {}, prefixLengths = {}; - let num = active.getAddresses(ips, prefixLengths); + let num = networkInfo.getAddresses(ips, prefixLengths); log(" Num addresses: " + num); log(" Addresses: " + JSON.stringify(ips.value)); log(" PrefixLengths: " + JSON.stringify(prefixLengths.value)); @@ -43,12 +43,12 @@ function doTest(aApnSettings, aHaveV4Address, aHaveV6Address) { if (aHaveV4Address) { ok(ips.value.reduce(function(aFound, aAddress) { return aFound || aAddress.indexOf(":") < 0; - }), "IPv4 address"); + }, false), "IPv4 address"); } if (aHaveV6Address) { ok(ips.value.reduce(function(aFound, aAddress) { return aFound || aAddress.indexOf(":") > 0; - }), "IPv6 address"); + }, false), "IPv6 address"); } }) .then(() => setDataEnabledAndWait(false)); @@ -106,14 +106,16 @@ startTestCommon(function() { .then(() => doTestHome(aApnSettings, "NoSuchProtocol")) .then(() => doTestHome(aApnSettings, "IP")) - .then(() => doTestHome(aApnSettings, "IPV4V6")) + // TODO: Bug 979137 - B2G Emulator: Support the IPV4V6 + //.then(() => doTestHome(aApnSettings, "IPV4V6")) .then(() => doTestHome(aApnSettings, "IPV6")) .then(() => setEmulatorRoamingAndWait(true)) .then(() => doTestRoaming(aApnSettings, "NoSuchProtocol")) .then(() => doTestRoaming(aApnSettings, "IP")) - .then(() => doTestRoaming(aApnSettings, "IPV4V6")) + // TODO: Bug 979137 - B2G Emulator: Support the IPV4V6 + //.then(() => doTestRoaming(aApnSettings, "IPV4V6")) .then(() => doTestRoaming(aApnSettings, "IPV6")) .then(() => setEmulatorRoamingAndWait(false)); diff --git a/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js b/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js index 7904ec68d7..ec4fbb3e44 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_neighboring_cell_ids.js @@ -2,37 +2,17 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ MARIONETTE_TIMEOUT = 30000; -// This test must run in chrome context. -MARIONETTE_CONTEXT = "chrome"; +MARIONETTE_HEAD_JS = "head_chrome.js"; -let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; - -let service = Cc["@mozilla.org/mobileconnection/mobileconnectionservice;1"] - .getService(Ci.nsIMobileConnectionService); -ok(service, "service.constructor is " + service.constructor); - -let mobileConnection = service.getItemByServiceId(0); -ok(mobileConnection, "mobileConnection.constructor is " + mobileConnection.constrctor); - -function testGetNeighboringCellIds() { - log("Test getting mobile neighboring cell ids"); - let deferred = Promise.defer(); - - mobileConnection.getNeighboringCellIds({ - notifyGetNeighboringCellIds: function(aResult) { - deferred.resolve(aResult); - }, - notifyGetNeighboringCellIdsFailed: function(aError) { - deferred.reject(aError); - } - }); - return deferred.promise; -} - -// Start tests -testGetNeighboringCellIds() - .then(function resolve(aResult) { - ok(false, "getNeighboringCellIds should not success"); - }, function reject(aError) { - is(aError, "RequestNotSupported", "failed to getNeighboringCellIds"); - }).then(finish); \ No newline at end of file +// Start test. +startTestBase(function() { + // Emulator doesn't support REQUEST_GET_NEIGHBORING_CELL_IDS, so we expect to + // get an 'RequestNotSupported' error here. + return getNeighboringCellIds() + .then(() => { + ok(false, "should not success"); + }, (aErrorMsg) => { + is(aErrorMsg, "RequestNotSupported", + "Failed to getNeighboringCellIds: " + aErrorMsg); + }); +}); diff --git a/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js b/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js index 9501bf1378..e82010e237 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_voice_location.js @@ -10,6 +10,12 @@ function verifyVoiceCellLocationInfo(aLac, aCid) { is(cell.gsmLocationAreaCode, aLac, "check voice.cell.gsmLocationAreaCode"); is(cell.gsmCellId, aCid, "check voice.cell.gsmCellId"); + + // TODO: Since gecko doesn't reset these values below to their invalid values, + // the tests below will fail after we once change to CDMA mode. Please refer + // to Bug 1190274 for more information. + + /* is(cell.cdmaBaseStationId, -1, "check voice.cell.cdmaBaseStationId"); is(cell.cdmaBaseStationLatitude, -2147483648, "check voice.cell.cdmaBaseStationLatitude"); @@ -17,6 +23,7 @@ function verifyVoiceCellLocationInfo(aLac, aCid) { "check voice.cell.cdmaBaseStationLongitude"); is(cell.cdmaSystemId, -1, "check voice.cell.cdmaSystemId"); is(cell.cdmaNetworkId, -1, "check voice.cell.cdmaNetworkId"); + */ } /* Test Voice Cell Location Info Change */ diff --git a/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js b/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js index b3aa3103d6..86438e8153 100644 --- a/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js +++ b/dom/mobileconnection/tests/marionette/test_mobile_voice_state.js @@ -4,25 +4,6 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -const INITIAL_STATES = { - state: "registered", - connected: true, - emergencyCallsOnly: false, - roaming: false, - signalStrength: -99, - relSignalStrength: 44, - - cell: { - gsmLocationAreaCode: 0, - gsmCellId: 0, - cdmaBaseStationId: -1, - cdmaBaseStationLatitude: -2147483648, - cdmaBaseStationLongitude: -2147483648, - cdmaSystemId: -1, - cdmaNetworkId: -1, - } -}; - const TEST_DATA = [{ // Test state becomes to "unregistered" state: "unregistered", @@ -126,8 +107,6 @@ function testVoiceStateUpdate(aNewState, aExpected) { startTestCommon(function() { log("Test initial voice connection info"); - verifyVoiceInfo(INITIAL_STATES); - let promise = Promise.resolve(); for (let i = 0; i < TEST_DATA.length; i++) { let entry = TEST_DATA[i]; diff --git a/extensions/spellcheck/hunspell/glue/moz.build b/extensions/spellcheck/hunspell/glue/moz.build index d2c0cb7f6d..ea26420aaf 100644 --- a/extensions/spellcheck/hunspell/glue/moz.build +++ b/extensions/spellcheck/hunspell/glue/moz.build @@ -11,12 +11,14 @@ UNIFIED_SOURCES += [ 'RemoteSpellCheckEngineParent.cpp', ] -CXXFLAGS += CONFIG['MOZ_HUNSPELL_CFLAGS'] - FINAL_LIBRARY = 'xul' +if CONFIG['MOZ_SYSTEM_HUNSPELL']: + CXXFLAGS += CONFIG['MOZ_HUNSPELL_CFLAGS'] +else: + LOCAL_INCLUDES += ['../src'] + LOCAL_INCLUDES += [ - '../src', '/dom/base', '/editor/libeditor', '/extensions/spellcheck/src', diff --git a/extensions/spellcheck/src/moz.build b/extensions/spellcheck/src/moz.build index 916382ee77..951f0d14fd 100644 --- a/extensions/spellcheck/src/moz.build +++ b/extensions/spellcheck/src/moz.build @@ -17,9 +17,13 @@ SOURCES += [ FINAL_LIBRARY = 'xul' +if CONFIG['MOZ_SYSTEM_HUNSPELL']: + CXXFLAGS += CONFIG['MOZ_HUNSPELL_CFLAGS'] +else: + LOCAL_INCLUDES += ['../hunspell/src'] + LOCAL_INCLUDES += [ '../hunspell/glue', - '../hunspell/src', '/dom/base', '/editor/libeditor', ] diff --git a/gfx/2d/DrawTargetCairo.cpp b/gfx/2d/DrawTargetCairo.cpp index 55096c0084..3e6a1ab2ec 100644 --- a/gfx/2d/DrawTargetCairo.cpp +++ b/gfx/2d/DrawTargetCairo.cpp @@ -1178,7 +1178,7 @@ DrawTargetCairo::ClearRect(const Rect& aRect) AutoPrepareForDrawing prep(this, mContext); - if (!mContext || aRect.Width() <= 0 || aRect.Height() <= 0 || + if (!mContext || aRect.Width() < 0 || aRect.Height() < 0 || !IsFinite(aRect.X()) || !IsFinite(aRect.Width()) || !IsFinite(aRect.Y()) || !IsFinite(aRect.Height())) { gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) << " with " << aRect.Width() << "x" << aRect.Height() << " [" << aRect.X() << ", " << aRect.Y() << "]"; diff --git a/gfx/2d/DrawTargetD2D1.cpp b/gfx/2d/DrawTargetD2D1.cpp index fb79d72a69..8a1a092611 100644 --- a/gfx/2d/DrawTargetD2D1.cpp +++ b/gfx/2d/DrawTargetD2D1.cpp @@ -363,7 +363,7 @@ DrawTargetD2D1::CopySurface(SourceSurface *aSurface, mDC->SetTransform(D2D1::IdentityMatrix()); mTransformDirty = true; - Matrix mat; + Matrix mat = Matrix::Translation(aDestination.x - aSourceRect.x, aDestination.y - aSourceRect.y); RefPtr image = GetImageForSurface(aSurface, mat, ExtendMode::CLAMP); if (!image) { @@ -371,11 +371,17 @@ DrawTargetD2D1::CopySurface(SourceSurface *aSurface, return; } - if (!mat.IsIdentity()) { - gfxDebug() << *this << ": At this point complex partial uploads are not supported for CopySurface."; + if (mat.HasNonIntegerTranslation()) { + gfxDebug() << *this << ": At this point scaled partial uploads are not supported for CopySurface."; return; } + IntRect sourceRect = aSourceRect; + sourceRect.x += (aDestination.x - aSourceRect.x) - mat._31; + sourceRect.width -= (aDestination.x - aSourceRect.x) - mat._31; + sourceRect.y += (aDestination.y - aSourceRect.y) - mat._32; + sourceRect.height -= (aDestination.y - aSourceRect.y) - mat._32; + RefPtr bitmap; image->QueryInterface((ID2D1Bitmap**)getter_AddRefs(bitmap)); @@ -391,7 +397,7 @@ DrawTargetD2D1::CopySurface(SourceSurface *aSurface, return; } - Rect srcRect(Float(aSourceRect.x), Float(aSourceRect.y), + Rect srcRect(Float(sourceRect.x), Float(sourceRect.y), Float(aSourceRect.width), Float(aSourceRect.height)); Rect dstRect(Float(aDestination.x), Float(aDestination.y), diff --git a/gfx/2d/DrawTargetD2D1.h b/gfx/2d/DrawTargetD2D1.h index 7e69c62039..0d1256eb3d 100644 --- a/gfx/2d/DrawTargetD2D1.h +++ b/gfx/2d/DrawTargetD2D1.h @@ -131,6 +131,9 @@ public: bool Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat); uint32_t GetByteSize() const; + // This function will get an image for a surface, it may adjust the source + // transform for any transformation of the resulting image relative to the + // oritingal SourceSurface. already_AddRefed GetImageForSurface(SourceSurface *aSurface, Matrix &aSourceTransform, ExtendMode aExtendMode, const IntRect* aSourceRect = nullptr); diff --git a/gfx/2d/DrawTargetRecording.cpp b/gfx/2d/DrawTargetRecording.cpp index 0411f3d5c2..6d72f82d4a 100644 --- a/gfx/2d/DrawTargetRecording.cpp +++ b/gfx/2d/DrawTargetRecording.cpp @@ -389,10 +389,6 @@ DrawTargetRecording::FillGlyphs(ScaledFont *aFont, { EnsurePatternDependenciesStored(aPattern); - if (aFont->GetType() != FontType::DWRITE && aFont->GetType() != FontType::GDI) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Unexpected ScaledFont type " << (int)aFont->GetType(); - } - if (!aFont->GetUserData(reinterpret_cast(mRecorder.get()))) { // TODO support font in b2g recordings #ifndef MOZ_WIDGET_GONK diff --git a/gfx/2d/DrawTargetSkia.cpp b/gfx/2d/DrawTargetSkia.cpp index 39290c4beb..8f2ba503d0 100644 --- a/gfx/2d/DrawTargetSkia.cpp +++ b/gfx/2d/DrawTargetSkia.cpp @@ -416,7 +416,8 @@ DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface, Float aSigma, CompositionOp aOperator) { - if (!(aSurface->GetType() == SurfaceType::SKIA || aSurface->GetType() == SurfaceType::DATA)) { + if (!(aSurface->GetType() == SurfaceType::SKIA || aSurface->GetType() == SurfaceType::DATA) || + aSurface->GetSize().IsEmpty()) { return; } @@ -636,10 +637,19 @@ DrawTargetSkia::FillGlyphs(ScaledFont *aFont, // SkFontHost_cairo does not support subpixel text, so only enable it for other font hosts. paint.mPaint.setSubpixelText(true); - if (aFont->GetType() == FontType::MAC) { + if (aFont->GetType() == FontType::MAC && + (shouldLCDRenderText || aOptions.mAntialiasMode == AntialiasMode::GRAY)) { + // SkFontHost_mac only enables CG Font Smoothing if hinting is disabled. + // CG Font Smoothing normally enables subpixel AA in CG, but Skia supports + // font smoothing with grayscale AA. // SkFontHost_mac only supports subpixel antialiasing when hinting is turned off. - // For grayscale AA, we want to disable font smoothing as the only time we should - // use grayscale AA is with explicit -moz-osx-font-smoothing + // We can get grayscale AA if we have -moz-osx-font-smoothing: grayscale + // explicitly enabled or for transparent surfaces. + // If we have AA grayscale explicit through the draw options, + // then we want to disable font smoothing. + // If we have a transparent surface, shouldLCDRenderText will be false. But unless + // grayscale font smoothing is explicitly requested, we still want Skia to use + // CG Font smoothing. paint.mPaint.setHinting(SkPaint::kNo_Hinting); } else { paint.mPaint.setHinting(SkPaint::kNormal_Hinting); diff --git a/gfx/2d/Path.cpp b/gfx/2d/Path.cpp index ed88ddd16f..b4ed643723 100644 --- a/gfx/2d/Path.cpp +++ b/gfx/2d/Path.cpp @@ -10,30 +10,42 @@ namespace mozilla { namespace gfx { -static float CubicRoot(float aValue) { +static double CubicRoot(double aValue) { if (aValue < 0.0) { return -CubicRoot(-aValue); } else { - return powf(aValue, 1.0f / 3.0f); + return pow(aValue, 1.0 / 3.0); } } +struct PointD : public BasePoint { + typedef BasePoint Super; + + PointD() : Super() {} + PointD(double aX, double aY) : Super(aX, aY) {} + MOZ_IMPLICIT PointD(const Point& aPoint) : Super(aPoint.x, aPoint.y) {} + + Point ToPoint() const { + return Point(static_cast(x), static_cast(y)); + } +}; + struct BezierControlPoints { BezierControlPoints() {} - BezierControlPoints(const Point &aCP1, const Point &aCP2, - const Point &aCP3, const Point &aCP4) + BezierControlPoints(const PointD &aCP1, const PointD &aCP2, + const PointD &aCP3, const PointD &aCP4) : mCP1(aCP1), mCP2(aCP2), mCP3(aCP3), mCP4(aCP4) { } - Point mCP1, mCP2, mCP3, mCP4; + PointD mCP1, mCP2, mCP3, mCP4; }; void FlattenBezier(const BezierControlPoints &aPoints, - PathSink *aSink, Float aTolerance); + PathSink *aSink, double aTolerance); Path::Path() @@ -205,18 +217,18 @@ static void SplitBezier(const BezierControlPoints &aControlPoints, BezierControlPoints *aFirstSegmentControlPoints, BezierControlPoints *aSecondSegmentControlPoints, - Float t) + double t) { MOZ_ASSERT(aSecondSegmentControlPoints); *aSecondSegmentControlPoints = aControlPoints; - Point cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t; - Point cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t; - Point cp1aa = cp1a + (cp2a - cp1a) * t; - Point cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t; - Point cp2aa = cp2a + (cp3a - cp2a) * t; - Point cp1aaa = cp1aa + (cp2aa - cp1aa) * t; + PointD cp1a = aControlPoints.mCP1 + (aControlPoints.mCP2 - aControlPoints.mCP1) * t; + PointD cp2a = aControlPoints.mCP2 + (aControlPoints.mCP3 - aControlPoints.mCP2) * t; + PointD cp1aa = cp1a + (cp2a - cp1a) * t; + PointD cp3a = aControlPoints.mCP3 + (aControlPoints.mCP4 - aControlPoints.mCP3) * t; + PointD cp2aa = cp2a + (cp3a - cp2a) * t; + PointD cp1aaa = cp1aa + (cp2aa - cp1aa) * t; aSecondSegmentControlPoints->mCP4 = aControlPoints.mCP4; if(aFirstSegmentControlPoints) { @@ -233,7 +245,7 @@ SplitBezier(const BezierControlPoints &aControlPoints, static void FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints, PathSink *aSink, - Float aTolerance) + double aTolerance) { /* The algorithm implemented here is based on: * http://cis.usouthal.edu/~hain/general/Publications/Bezier/Bezier%20Offset%20Curves.pdf @@ -245,47 +257,46 @@ FlattenBezierCurveSegment(const BezierControlPoints &aControlPoints, */ BezierControlPoints currentCP = aControlPoints; - Float t = 0; - while (t < 1.0f) { - Point cp21 = currentCP.mCP2 - currentCP.mCP3; - Point cp31 = currentCP.mCP3 - currentCP.mCP1; + double t = 0; + while (t < 1.0) { + PointD cp21 = currentCP.mCP2 - currentCP.mCP3; + PointD cp31 = currentCP.mCP3 - currentCP.mCP1; /* To remove divisions and check for divide-by-zero, this is optimized from: * Float s3 = (cp31.x * cp21.y - cp31.y * cp21.x) / hypotf(cp21.x, cp21.y); * t = 2 * Float(sqrt(aTolerance / (3. * std::abs(s3)))); */ - Float cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x; - Float h = hypotf(cp21.x, cp21.y); + double cp21x31 = cp31.x * cp21.y - cp31.y * cp21.x; + double h = hypot(cp21.x, cp21.y); if (cp21x31 * h == 0) { break; } - Float s3inv = h / cp21x31; - t = 2 * Float(sqrt(aTolerance * std::abs(s3inv) / 3.)); - if (t >= 1.0f) { + double s3inv = h / cp21x31; + t = 2 * sqrt(aTolerance * std::abs(s3inv) / 3.); + if (t >= 1.0) { break; } - Point prevCP2, prevCP3, nextCP1, nextCP2, nextCP3; SplitBezier(currentCP, nullptr, ¤tCP, t); - aSink->LineTo(currentCP.mCP1); + aSink->LineTo(currentCP.mCP1.ToPoint()); } - aSink->LineTo(currentCP.mCP4); + aSink->LineTo(currentCP.mCP4.ToPoint()); } static inline void FindInflectionApproximationRange(BezierControlPoints aControlPoints, - Float *aMin, Float *aMax, Float aT, - Float aTolerance) + double *aMin, double *aMax, double aT, + double aTolerance) { SplitBezier(aControlPoints, nullptr, &aControlPoints, aT); - Point cp21 = aControlPoints.mCP2 - aControlPoints.mCP1; - Point cp41 = aControlPoints.mCP4 - aControlPoints.mCP1; + PointD cp21 = aControlPoints.mCP2 - aControlPoints.mCP1; + PointD cp41 = aControlPoints.mCP4 - aControlPoints.mCP1; - if (cp21.x == 0.f && cp21.y == 0.f) { + if (cp21.x == 0. && cp21.y == 0.) { // In this case s3 becomes lim[n->0] (cp41.x * n) / n - (cp41.y * n) / n = cp41.x - cp41.y. // Use the absolute value so that Min and Max will correspond with the @@ -295,18 +306,18 @@ FindInflectionApproximationRange(BezierControlPoints aControlPoints, return; } - Float s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypotf(cp21.x, cp21.y); + double s3 = (cp41.x * cp21.y - cp41.y * cp21.x) / hypot(cp21.x, cp21.y); if (s3 == 0) { // This means within the precision we have it can be approximated // infinitely by a linear segment. Deal with this by specifying the // approximation range as extending beyond the entire curve. - *aMin = -1.0f; - *aMax = 2.0f; + *aMin = -1.0; + *aMax = 2.0; return; } - Float tf = CubicRoot(std::abs(aTolerance / s3)); + double tf = CubicRoot(std::abs(aTolerance / s3)); *aMin = aT - tf * (1 - aT); *aMax = aT + tf * (1 - aT); @@ -365,18 +376,18 @@ FindInflectionApproximationRange(BezierControlPoints aControlPoints, */ static inline void FindInflectionPoints(const BezierControlPoints &aControlPoints, - Float *aT1, Float *aT2, uint32_t *aCount) + double *aT1, double *aT2, uint32_t *aCount) { // Find inflection points. // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation // of this approach. - Point A = aControlPoints.mCP2 - aControlPoints.mCP1; - Point B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1; - Point C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1; + PointD A = aControlPoints.mCP2 - aControlPoints.mCP1; + PointD B = aControlPoints.mCP3 - (aControlPoints.mCP2 * 2) + aControlPoints.mCP1; + PointD C = aControlPoints.mCP4 - (aControlPoints.mCP3 * 3) + (aControlPoints.mCP2 * 3) - aControlPoints.mCP1; - Float a = Float(B.x) * C.y - Float(B.y) * C.x; - Float b = Float(A.x) * C.y - Float(A.y) * C.x; - Float c = Float(A.x) * B.y - Float(A.y) * B.x; + double a = B.x * C.y - B.y * C.x; + double b = A.x * C.y - A.y * C.x; + double c = A.x * B.y - A.y * B.x; if (a == 0) { // Not a quadratic equation. @@ -400,7 +411,7 @@ FindInflectionPoints(const BezierControlPoints &aControlPoints, *aCount = 1; return; } else { - Float discriminant = b * b - 4 * a * c; + double discriminant = b * b - 4 * a * c; if (discriminant < 0) { // No inflection points. @@ -415,13 +426,13 @@ FindInflectionPoints(const BezierControlPoints &aControlPoints, * t1 = q / a * t2 = c / q */ - Float q = sqrtf(discriminant); + double q = sqrt(discriminant); if (b < 0) { q = b - q; } else { q = b + q; } - q *= Float(-1./2); + q *= -1./2; *aT1 = q / a; *aT2 = c / q; @@ -437,10 +448,10 @@ FindInflectionPoints(const BezierControlPoints &aControlPoints, void FlattenBezier(const BezierControlPoints &aControlPoints, - PathSink *aSink, Float aTolerance) + PathSink *aSink, double aTolerance) { - Float t1; - Float t2; + double t1; + double t2; uint32_t count; FindInflectionPoints(aControlPoints, &t1, &t2, &count); @@ -451,7 +462,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, return; } - Float t1min = t1, t1max = t1, t2min = t2, t2max = t2; + double t1min = t1, t1max = t1, t2min = t2, t2max = t2; BezierControlPoints remainingCP = aControlPoints; @@ -470,7 +481,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // segments. if (count == 1 && t1min <= 0 && t1max >= 1.0) { // The whole range can be approximated by a line segment. - aSink->LineTo(aControlPoints.mCP4); + aSink->LineTo(aControlPoints.mCP4.ToPoint()); return; } @@ -487,7 +498,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // subsequently flatten up until the end or the next inflection point. SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); - aSink->LineTo(nextCPs.mCP1); + aSink->LineTo(nextCPs.mCP1.ToPoint()); if (count == 1 || (count > 1 && t2min >= 1.0)) { // No more inflection points to deal with, flatten the rest of the curve. @@ -497,7 +508,7 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // We've already concluded t2min <= t1max, so if this is true the // approximation range for the first inflection point runs past the // end of the curve, draw a line to the end and we're done. - aSink->LineTo(aControlPoints.mCP4); + aSink->LineTo(aControlPoints.mCP4.ToPoint()); return; } @@ -506,12 +517,12 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // In this case the t2 approximation range starts inside the t1 // approximation range. SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); - aSink->LineTo(nextCPs.mCP1); + aSink->LineTo(nextCPs.mCP1.ToPoint()); } else if (t2min > 0 && t1max > 0) { SplitBezier(aControlPoints, nullptr, &nextCPs, t1max); // Find a control points describing the portion of the curve between t1max and t2min. - Float t2mina = (t2min - t1max) / (1 - t1max); + double t2mina = (t2min - t1max) / (1 - t1max); SplitBezier(nextCPs, &prevCPs, &nextCPs, t2mina); FlattenBezierCurveSegment(prevCPs, aSink, aTolerance); } else if (t2min > 0) { @@ -525,11 +536,11 @@ FlattenBezier(const BezierControlPoints &aControlPoints, // Draw a line to the start, this is the approximation between t2min and // t2max. - aSink->LineTo(nextCPs.mCP1); + aSink->LineTo(nextCPs.mCP1.ToPoint()); FlattenBezierCurveSegment(nextCPs, aSink, aTolerance); } else { // Our approximation range extends beyond the end of the curve. - aSink->LineTo(aControlPoints.mCP4); + aSink->LineTo(aControlPoints.mCP4.ToPoint()); return; } } diff --git a/gfx/2d/PathD2D.cpp b/gfx/2d/PathD2D.cpp index eaa0c2e08b..8d81eb7843 100644 --- a/gfx/2d/PathD2D.cpp +++ b/gfx/2d/PathD2D.cpp @@ -90,6 +90,31 @@ private: bool mNeedsFigureEnded; }; +class MOZ_STACK_CLASS AutoRestoreFP +{ +public: + AutoRestoreFP() + { + // save the current floating point control word + _controlfp_s(&savedFPSetting, 0, 0); + UINT unused; + // set the floating point control word to its default value + _controlfp_s(&unused, _CW_DEFAULT, MCW_PC); + } + ~AutoRestoreFP() + { + UINT unused; + // restore the saved floating point control word + _controlfp_s(&unused, savedFPSetting, MCW_PC); + } +private: + UINT savedFPSetting; +}; + +// Note that overrides of ID2D1SimplifiedGeometrySink methods in this class may +// get called from D2D with nonstandard floating point settings (see comments in +// bug 1134549) - use AutoRestoreFP to reset the floating point control word to +// what we expect class StreamingGeometrySink : public ID2D1SimplifiedGeometrySink { public: @@ -129,11 +154,18 @@ public: STDMETHOD_(void, SetFillMode)(D2D1_FILL_MODE aMode) { return; } STDMETHOD_(void, BeginFigure)(D2D1_POINT_2F aPoint, D2D1_FIGURE_BEGIN aBegin) - { mSink->MoveTo(ToPoint(aPoint)); } + { + AutoRestoreFP resetFloatingPoint; + mSink->MoveTo(ToPoint(aPoint)); + } STDMETHOD_(void, AddLines)(const D2D1_POINT_2F *aLines, UINT aCount) - { for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } } + { + AutoRestoreFP resetFloatingPoint; + for (UINT i = 0; i < aCount; i++) { mSink->LineTo(ToPoint(aLines[i])); } + } STDMETHOD_(void, AddBeziers)(const D2D1_BEZIER_SEGMENT *aSegments, UINT aCount) { + AutoRestoreFP resetFloatingPoint; for (UINT i = 0; i < aCount; i++) { mSink->BezierTo(ToPoint(aSegments[i].point1), ToPoint(aSegments[i].point2), ToPoint(aSegments[i].point3)); } @@ -145,6 +177,7 @@ public: STDMETHOD_(void, EndFigure)(D2D1_FIGURE_END aEnd) { + AutoRestoreFP resetFloatingPoint; if (aEnd == D2D1_FIGURE_END_CLOSED) { return mSink->Close(); } diff --git a/gfx/2d/RecordedEvent.h b/gfx/2d/RecordedEvent.h index cc81e58f04..e4eabe1f04 100644 --- a/gfx/2d/RecordedEvent.h +++ b/gfx/2d/RecordedEvent.h @@ -659,7 +659,7 @@ private: class RecordedPopLayer : public RecordedDrawingEvent { public: - RecordedPopLayer(DrawTarget* aDT) + MOZ_IMPLICIT RecordedPopLayer(DrawTarget* aDT) : RecordedDrawingEvent(POPLAYER, aDT) { } diff --git a/gfx/2d/SFNTData.cpp b/gfx/2d/SFNTData.cpp index 2e0fa6e0fe..fd29f6694d 100644 --- a/gfx/2d/SFNTData.cpp +++ b/gfx/2d/SFNTData.cpp @@ -116,7 +116,7 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) // Check to see if this is a font collection. if (aDataLength < sizeof(TTCHeader)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Font data too short: length = " << aDataLength; + gfxWarning() << "Font data too short."; return nullptr; } @@ -124,7 +124,7 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) if (ttcHeader->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) { uint32_t numFonts = ttcHeader->numFonts; if (aDataLength < sizeof(TTCHeader) + (numFonts * sizeof(BigEndianUint32))) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Font data too short to contain full TTC Header: numFonts = " << numFonts << "; length = " << aDataLength; + gfxWarning() << "Font data too short to contain full TTC Header."; return nullptr; } @@ -134,7 +134,6 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) const BigEndianUint32* endOfOffsets = offset + numFonts; while (offset != endOfOffsets) { if (!sfntData->AddFont(aFontData, aDataLength, *offset)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to add font data from TTC"; return nullptr; } ++offset; @@ -145,7 +144,6 @@ SFNTData::Create(const uint8_t *aFontData, uint32_t aDataLength) UniquePtr sfntData(new SFNTData); if (!sfntData->AddFont(aFontData, aDataLength, 0)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to add single font data"; return nullptr; } @@ -206,11 +204,20 @@ SFNTData::GetU16FullNames(Vector& aU16FullNames) bool SFNTData::GetIndexForU16Name(const mozilla::u16string& aU16FullName, - uint32_t* aIndex) + uint32_t* aIndex, size_t aTruncatedLen) { for (size_t i = 0; i < mFonts.length(); ++i) { mozilla::u16string name; - if (mFonts[i]->GetU16FullName(name) && name == aU16FullName) { + if (!mFonts[i]->GetU16FullName(name)) { + continue; + } + + if (aTruncatedLen) { + MOZ_ASSERT(aU16FullName.length() <= aTruncatedLen); + name = name.substr(0, aTruncatedLen); + } + + if (name == aU16FullName) { *aIndex = i; return true; } @@ -225,7 +232,7 @@ SFNTData::AddFont(const uint8_t *aFontData, uint32_t aDataLength, { uint32_t remainingLength = aDataLength - aOffset; if (remainingLength < sizeof(OffsetTable)) { - gfxCriticalError() << "Font data too short to contain OffsetTable: offset = " << aOffset << "; length = " << aDataLength; + gfxWarning() << "Font data too short to contain OffsetTable " << aOffset; return false; } @@ -233,7 +240,7 @@ SFNTData::AddFont(const uint8_t *aFontData, uint32_t aDataLength, reinterpret_cast(aFontData + aOffset); if (remainingLength < sizeof(OffsetTable) + (offsetTable->numTables * sizeof(TableDirEntry))) { - gfxCriticalError() << "Font data too short to contain tables. numTables = " << offsetTable->numTables << "; offset = " << aOffset << "; length = " << aDataLength; + gfxWarning() << "Font data too short to contain tables."; return false; } diff --git a/gfx/2d/SFNTData.h b/gfx/2d/SFNTData.h index 5205dfa1c4..e10d63caed 100644 --- a/gfx/2d/SFNTData.h +++ b/gfx/2d/SFNTData.h @@ -70,9 +70,11 @@ public: * * @param aU16FullName full name to find. * @param aIndex out param for the index if found. + * @param aTruncatedLen length to truncate the compared font name to. * @return true if the full name is successfully read. */ - bool GetIndexForU16Name(const mozilla::u16string& aU16FullName, uint32_t* aIndex); + bool GetIndexForU16Name(const mozilla::u16string& aU16FullName, uint32_t* aIndex, + size_t aTruncatedLen = 0); private: diff --git a/gfx/2d/ScaledFontDWrite.cpp b/gfx/2d/ScaledFontDWrite.cpp index a9c9e8f805..06cfb6da2d 100644 --- a/gfx/2d/ScaledFontDWrite.cpp +++ b/gfx/2d/ScaledFontDWrite.cpp @@ -115,12 +115,42 @@ ScaledFontDWrite::GetPathForGlyphs(const GlyphBuffer &aBuffer, const DrawTarget #ifdef USE_SKIA +// This can happen if we have mixed backends which create DWrite +// fonts in a mixed environment. e.g. a cairo content backend +// but Skia canvas backend. +void +ScaledFontDWrite::GetFontDataFromSystemFonts(IDWriteFactory* aFactory) +{ + MOZ_ASSERT(mFontFace); + RefPtr systemFonts; + HRESULT hr = aFactory->GetSystemFontCollection(getter_AddRefs(systemFonts)); + if (FAILED(hr)) { + gfxWarning() << "Failed to get system font collection from file data. Code: " << hexa(hr); + return; + } + + hr = systemFonts->GetFontFromFontFace(mFontFace, getter_AddRefs(mFont)); + if (FAILED(hr)) { + gfxWarning() << "Failed to get system font from font face. Code: " << hexa(hr); + return; + } + + hr = mFont->GetFontFamily(getter_AddRefs(mFontFamily)); + if (FAILED(hr)) { + gfxWarning() << "Failed to get font family from font face. Code: " << hexa(hr); + return; + } +} + SkTypeface* ScaledFontDWrite::GetSkTypeface() { - MOZ_ASSERT(mFont); if (!mTypeface) { IDWriteFactory *factory = DrawTargetD2D1::GetDWriteFactory(); + if (!mFont || !mFontFamily) { + GetFontDataFromSystemFonts(factory); + } + mTypeface = SkCreateTypefaceFromDWriteFont(factory, mFontFace, mFont, mFontFamily); } return mTypeface; diff --git a/gfx/2d/ScaledFontDWrite.h b/gfx/2d/ScaledFontDWrite.h index 65f25d89ff..9a4d16264e 100644 --- a/gfx/2d/ScaledFontDWrite.h +++ b/gfx/2d/ScaledFontDWrite.h @@ -46,8 +46,10 @@ public: #ifdef USE_SKIA virtual SkTypeface* GetSkTypeface(); + void GetFontDataFromSystemFonts(IDWriteFactory* aFactory); #endif + // The font and font family are only used with Skia RefPtr mFont; RefPtr mFontFamily; RefPtr mFontFace; diff --git a/gfx/2d/ScaledFontWin.cpp b/gfx/2d/ScaledFontWin.cpp index 4395c76dda..abcbfed36a 100644 --- a/gfx/2d/ScaledFontWin.cpp +++ b/gfx/2d/ScaledFontWin.cpp @@ -40,7 +40,6 @@ ScaledFontWin::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) table = 0; tableSize = ::GetFontData(dc.GetDC(), table, 0, nullptr, 0); if (tableSize == GDI_ERROR) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to get font data from GDI"; return false; } } @@ -50,7 +49,6 @@ ScaledFontWin::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) uint32_t sizeGot = ::GetFontData(dc.GetDC(), table, 0, fontData.get(), tableSize); if (sizeGot != tableSize) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "GDI did not return enough data for font: wanted " << tableSize << ", got " << sizeGot; return false; } @@ -60,15 +58,17 @@ ScaledFontWin::GetFontFileData(FontFileDataOutput aDataCallback, void *aBaton) UniquePtr sfntData = SFNTData::Create(fontData.get(), tableSize); if (!sfntData) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to create SFNTData for GetFontFileData."; + gfxWarning() << "Failed to create SFNTData for GetFontFileData."; return false; } // We cast here because for VS2015 char16_t != wchar_t, even though they are // both 16 bit. if (!sfntData->GetIndexForU16Name( - reinterpret_cast(mLogFont.lfFaceName), &index)) { - gfxDevCrash(LogReason::GetFontFileDataFailed) << "Failed to get index for face name."; + reinterpret_cast(mLogFont.lfFaceName), &index, LF_FACESIZE - 1)) { + gfxWarning() << "Failed to get index for face name."; + gfxDevCrash(LogReason::GetFontFileDataFailed) << + "Failed to get index for face name |" << mLogFont.lfFaceName << "|."; return false; } } diff --git a/gfx/2d/SourceSurfaceCairo.cpp b/gfx/2d/SourceSurfaceCairo.cpp index 2ead9510f3..ba8498b844 100644 --- a/gfx/2d/SourceSurfaceCairo.cpp +++ b/gfx/2d/SourceSurfaceCairo.cpp @@ -22,6 +22,8 @@ CairoFormatToSurfaceFormat(cairo_format_t format) return SurfaceFormat::B8G8R8A8; case CAIRO_FORMAT_RGB24: return SurfaceFormat::B8G8R8X8; + case CAIRO_FORMAT_RGB16_565: + return SurfaceFormat::R5G6B5_UINT16; case CAIRO_FORMAT_A8: return SurfaceFormat::A8; default: diff --git a/gfx/2d/convolver.cpp b/gfx/2d/convolver.cpp index b4a23133f3..0c3416f768 100644 --- a/gfx/2d/convolver.cpp +++ b/gfx/2d/convolver.cpp @@ -322,13 +322,13 @@ void ConvolveHorizontally(const unsigned char* src_data, bool has_alpha, bool use_simd) { int width = filter.num_values(); int processed = 0; -#if defined(USE_SSE2) +#if defined(USE_SSE2) || defined(_MIPS_ARCH_LOONGSON3A) int simd_width = width & ~3; if (use_simd && simd_width) { // SIMD implementation works with 4 pixels at a time. // Therefore we process as much as we can using SSE and then use // C implementation for leftovers - ConvolveHorizontally_SSE2(src_data, filter, out_row); + ConvolveHorizontally_SIMD(src_data, filter, out_row); processed = simd_width; } #endif diff --git a/gfx/2d/convolverLS3.cpp b/gfx/2d/convolverLS3.cpp index 8c6b26cafd..795c1b8901 100644 --- a/gfx/2d/convolverLS3.cpp +++ b/gfx/2d/convolverLS3.cpp @@ -99,7 +99,8 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, ".set arch=loongson3a \n\t" // Load 4 coefficients => duplicate 1st and 2nd of them for all channels. // [16] xx xx xx xx c3 c2 c1 c0 - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // [16] xx xx xx xx c1 c1 c0 c0 _mm_pshuflh(coeff16, coeff, shuf_50) @@ -170,7 +171,8 @@ void ConvolveHorizontally_LS3(const unsigned char* src_data, asm volatile ( ".set push \n\t" ".set arch=loongson3a \n\t" - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // Mask out extra filter taps. "and %[coeffl], %[coeffl], %[mask] \n\t" @@ -305,7 +307,8 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], ".set push \n\t" ".set arch=loongson3a \n\t" // [16] xx xx xx xx c3 c2 c1 c0 - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // [16] xx xx xx xx c1 c1 c0 c0 _mm_pshuflh(coeff16lo, coeff, shuf_50) @@ -374,7 +377,8 @@ void ConvolveHorizontally4_LS3(const unsigned char* src_data[4], asm volatile ( ".set push \n\t" ".set arch=loongson3a \n\t" - "ldc1 %[coeffl], (%[fval]) \n\t" + "gsldlc1 %[coeffl], 7(%[fval]) \n\t" + "gsldrc1 %[coeffl], (%[fval]) \n\t" "xor %[coeffh], %[coeffh], %[coeffh] \n\t" // Mask out extra filter taps. "and %[coeffl], %[coeffl], %[mask] \n\t" @@ -500,7 +504,8 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values ".set arch=loongson3a \n\t" // Duplicate the filter coefficient 8 times. // [16] cj cj cj cj cj cj cj cj - "mtc1 %[fval], %[coeff16l] \n\t" + "gsldlc1 %[coeff16l], 7+%[fval] \n\t" + "gsldrc1 %[coeff16l], %[fval] \n\t" "pshufh %[coeff16l], %[coeff16l], %[zerol] \n\t" "mov.d %[coeff16h], %[coeff16l] \n\t" // Load four pixels (16 bytes) together. @@ -537,7 +542,7 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values [accum1h]"+f"(accum1h), [accum1l]"+f"(accum1l), [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l) :[zeroh]"f"(zero), [zerol]"f"(zero), - [fval]"r"(filter_values[filter_y]), + [fval]"m"(filter_values[filter_y]), [src]"r"(src) ); @@ -675,7 +680,8 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values asm volatile ( ".set push \n\t" ".set arch=loongson3a \n\t" - "mtc1 %[fval], %[coeff16l] \n\t" + "gsldlc1 %[coeff16l], 7+%[fval] \n\t" + "gsldrc1 %[coeff16l], %[fval] \n\t" "pshufh %[coeff16l], %[coeff16l], %[zerol] \n\t" "mov.d %[coeff16h], %[coeff16l] \n\t" // [8] a3 b3 g3 r3 a2 b2 g2 r2 a1 b1 g1 r1 a0 b0 g0 r0 @@ -711,7 +717,7 @@ void ConvolveVertically_LS3_impl(const ConvolutionFilter1D::Fixed* filter_values [accum2h]"+f"(accum2h), [accum2l]"+f"(accum2l), [coeff16h]"=&f"(coeff16h), [coeff16l]"=&f"(coeff16l) :[zeroh]"f"(zero), [zerol]"f"(zero), - [fval]"r"(filter_values[filter_y]), + [fval]"m"(filter_values[filter_y]), [src]"r"(src) ); } diff --git a/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp b/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp index aa34fd4de8..e01464a95b 100644 --- a/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp +++ b/gfx/angle/src/libANGLE/renderer/d3d/d3d11/StateManager11.cpp @@ -582,7 +582,9 @@ gl::Error StateManager11::setDepthStencilState(const gl::State &glState) gl::Error StateManager11::setRasterizerState(const gl::RasterizerState &rasterState) { - if (!mRasterizerStateIsDirty) + // TODO: Remove pointDrawMode and multiSample from gl::RasterizerState. + if (!mRasterizerStateIsDirty && rasterState.pointDrawMode == mCurRasterState.pointDrawMode && + rasterState.multiSample == mCurRasterState.multiSample) { return gl::Error(GL_NO_ERROR); } diff --git a/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp b/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp index 6b7f26555b..3fc974b9c4 100644 --- a/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp +++ b/gfx/angle/src/tests/gl_tests/PointSpritesTest.cpp @@ -434,6 +434,84 @@ TEST_P(PointSpritesTest, PointSizeDeclaredButUnused) glDeleteProgram(program); } +// Test to cover a bug where the D3D11 rasterizer state would not be update when switching between +// draw types. This causes the cull face to potentially be incorrect when drawing emulated point +// spites. +TEST_P(PointSpritesTest, PointSpriteAlternatingDrawTypes) +{ + // clang-format off + const std::string pointFS = SHADER_SOURCE + ( + precision mediump float; + void main() + { + gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); + } + ); + + const std::string pointVS = SHADER_SOURCE + ( + void main() + { + gl_PointSize = 16.0; + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + } + ); + + const std::string quadFS = SHADER_SOURCE + ( + precision mediump float; + void main() + { + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + } + ); + + const std::string quadVS = SHADER_SOURCE + ( + precision mediump float; + attribute vec4 pos; + void main() + { + gl_Position = pos; + } + ); + // clang-format on + + GLuint pointProgram = CompileProgram(pointVS, pointFS); + ASSERT_NE(pointProgram, 0u); + ASSERT_GL_NO_ERROR(); + + GLuint quadProgram = CompileProgram(quadVS, quadFS); + ASSERT_NE(pointProgram, 0u); + ASSERT_GL_NO_ERROR(); + + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + + const GLfloat quadVertices[] = { + -1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, -1.0f, -1.0f, 0.5f, + + -1.0f, 1.0f, 0.5f, 1.0f, 1.0f, 0.5f, 1.0f, -1.0f, 0.5f, + }; + + glUseProgram(quadProgram); + GLint positionLocation = glGetAttribLocation(quadProgram, "pos"); + glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices); + glEnableVertexAttribArray(positionLocation); + glDrawArrays(GL_TRIANGLES, 0, 6); + + glUseProgram(pointProgram); + glDrawArrays(GL_POINTS, 0, 1); + ASSERT_GL_NO_ERROR(); + + // expect the center pixel to be green + EXPECT_PIXEL_EQ(getWindowWidth() / 2, getWindowHeight() / 2, 0, 255, 0, 255); + + glDeleteProgram(pointProgram); + glDeleteProgram(quadProgram); +} + // Use this to select which configurations (e.g. which renderer, which GLES // major version) these tests should be run against. // diff --git a/gfx/doc/B2GInputFlow.svg b/gfx/doc/B2GInputFlow.svg new file mode 100644 index 0000000000..ee6f4332c2 --- /dev/null +++ b/gfx/doc/B2GInputFlow.svg @@ -0,0 +1,349 @@ + + + Touch input event flow on B2G + + + + diff --git a/gfx/doc/LayersHistory.md b/gfx/doc/LayersHistory.md new file mode 100644 index 0000000000..2833aa3c5b --- /dev/null +++ b/gfx/doc/LayersHistory.md @@ -0,0 +1,60 @@ +This is an overview of the major events in the history of our Layers infrastructure. + +- iPhone released in July 2007 (Built on a toolkit called LayerKit) + +- Core Animation (October 2007) LayerKit was publicly renamed to OS X 10.5 + +- Webkit CSS 3d transforms (July 2009) + +- Original layers API (March 2010) Introduced the idea of a layer manager that + would composite. One of the first use cases for this was hardware accelerated + YUV conversion for video. + +- Retained layers (July 7 2010 - Bug 564991) +This was an important concept that introduced the idea of persisting the layer +content across paints in gecko controlled buffers instead of just by the OS. This introduced +the concept of buffer rotation to deal with scrolling instead of using the +native scrolling APIs like ScrollWindowEx + +- Layers IPC (July 2010 - Bug 570294) +This introduced shadow layers and edit lists and was originally done for e10s v1 + +- 3d transforms (September 2011 - Bug 505115) + +- OMTC (December 2011 - Bug 711168) +This was prototyped on OS X but shipped first for Fennec + +- Tiling v1 (April 2012 - Bug 739679) +Originally done for Fennec. +This was done to avoid situations where we had to do a bunch of work for +scrolling a small amount. i.e. buffer rotation. It allowed us to have a +variety of interesting features like progressive painting and lower resolution +painting. + +- C++ Async pan zoom controller (July 2012 - Bug 750974) +The existing APZ code was in Java for Fennec so this was reimplemented. + +- Streaming WebGL Buffers (February 2013 - Bug 716859) +Infrastructure to allow OMTC WebGL and avoid the need to glFinish() every +frame. + +- Compositor API (April 2013 - Bug 825928) +The planning for this started around November 2012. +Layers refactoring created a compositor API that abstracted away the differences between the +D3D vs OpenGL. The main piece of API is DrawQuad. + +- Tiling v2 (Mar 7 2014 - Bug 963073) +Tiling for B2G. This work is mainly porting tiled layers to new textures, +implementing double-buffered tiles and implementing a texture client pool, to +be used by tiled content clients. + + A large motivation for the pool was the very slow performance of allocating tiles because +of the sync messages to the compositor. + + The slow performance of allocating was directly addressed by bug 959089 which allowed us +to allocate gralloc buffers without sync messages to the compositor thread. + +- B2G WebGL performance (May 2014 - Bug 1006957, 1001417, 1024144) +This work improved the synchronization mechanism between the compositor +and the producer. + diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 1b45639f35..96d118f247 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -155,6 +155,7 @@ static const char* const sExtensionNames[] = { "GL_NV_geometry_program4", "GL_NV_half_float", "GL_NV_instanced_arrays", + "GL_NV_texture_barrier", "GL_NV_transform_feedback", "GL_NV_transform_feedback2", "GL_OES_EGL_image", @@ -1556,6 +1557,14 @@ GLContext::LoadMoreSymbols(const char* prefix, bool trygl) fnLoadForExt(symbols, NV_fence); } + if (IsExtensionSupported(NV_texture_barrier)) { + const SymLoadStruct symbols[] = { + { (PRFuncPtr*) &mSymbols.fTextureBarrier, { "TextureBarrierNV", nullptr } }, + END_SYMBOLS + }; + fnLoadForExt(symbols, NV_texture_barrier); + } + if (IsSupported(GLFeature::read_buffer)) { const SymLoadStruct symbols[] = { { (PRFuncPtr*) &mSymbols.fReadBuffer, { "ReadBuffer", nullptr } }, diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index edef184e12..284b9b1b03 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -478,6 +478,7 @@ public: NV_geometry_program4, NV_half_float, NV_instanced_arrays, + NV_texture_barrier, NV_transform_feedback, NV_transform_feedback2, OES_EGL_image, @@ -2879,6 +2880,17 @@ public: AFTER_GL_CALL; } +// ----------------------------------------------------------------------------- +// Extension NV_texture_barrier +public: + void fTextureBarrier() + { + ASSERT_SYMBOL_PRESENT(fTextureBarrier); + BEFORE_GL_CALL; + mSymbols.fTextureBarrier(); + AFTER_GL_CALL; + } + // Core GL & Extension ARB_copy_buffer public: void fCopyBufferSubData(GLenum readtarget, GLenum writetarget, diff --git a/gfx/gl/GLContextSymbols.h b/gfx/gl/GLContextSymbols.h index efed07959d..aec5d2617e 100644 --- a/gfx/gl/GLContextSymbols.h +++ b/gfx/gl/GLContextSymbols.h @@ -690,6 +690,10 @@ struct GLContextSymbols // APPLE_framebuffer_multisample typedef void (GLAPIENTRY * PFNRESOLVEMULTISAMPLEFRAMEBUFFERAPPLE) (void); PFNRESOLVEMULTISAMPLEFRAMEBUFFERAPPLE fResolveMultisampleFramebufferAPPLE; + + // NV_texture_barrier + typedef void (GLAPIENTRY * PFNTEXTUREBARRIERPROC) (void); + PFNTEXTUREBARRIERPROC fTextureBarrier; }; } // namespace gl diff --git a/gfx/gl/GLLibraryEGL.h b/gfx/gl/GLLibraryEGL.h index ca1a2e396a..e792f0eb10 100644 --- a/gfx/gl/GLLibraryEGL.h +++ b/gfx/gl/GLLibraryEGL.h @@ -108,9 +108,52 @@ public: GLLibraryEGL() : mInitialized(false), mEGLLibrary(nullptr), + mEGLDisplay(EGL_NO_DISPLAY), mIsANGLE(false), mIsWARP(false) { + ClearSymbols(); + } + + void ClearSymbols() { + mSymbols.fGetDisplay = nullptr; + mSymbols.fGetPlatformDisplayEXT = nullptr; + mSymbols.fTerminate = nullptr; + mSymbols.fGetCurrentSurface = nullptr; + mSymbols.fGetCurrentContext = nullptr; + mSymbols.fMakeCurrent = nullptr; + mSymbols.fDestroyContext = nullptr; + mSymbols.fCreateContext = nullptr; + mSymbols.fDestroySurface = nullptr; + mSymbols.fCreateWindowSurface = nullptr; + mSymbols.fCreatePbufferSurface = nullptr; + mSymbols.fCreatePixmapSurface = nullptr; + mSymbols.fBindAPI = nullptr; + mSymbols.fInitialize = nullptr; + mSymbols.fChooseConfig = nullptr; + mSymbols.fGetError = nullptr; + mSymbols.fGetConfigAttrib = nullptr; + mSymbols.fGetConfigs = nullptr; + mSymbols.fWaitNative = nullptr; + mSymbols.fGetProcAddress = nullptr; + mSymbols.fSwapBuffers = nullptr; + mSymbols.fCopyBuffers = nullptr; + mSymbols.fQueryString = nullptr; + mSymbols.fQueryStringImplementationANDROID = nullptr; + mSymbols.fQueryContext = nullptr; + mSymbols.fBindTexImage = nullptr; + mSymbols.fReleaseTexImage = nullptr; + mSymbols.fCreateImage = nullptr; + mSymbols.fDestroyImage = nullptr; + mSymbols.fLockSurface = nullptr; + mSymbols.fUnlockSurface = nullptr; + mSymbols.fQuerySurface = nullptr; + mSymbols.fQuerySurfacePointerANGLE = nullptr; + mSymbols.fCreateSync = nullptr; + mSymbols.fDestroySync = nullptr; + mSymbols.fClientWaitSync = nullptr; + mSymbols.fGetSyncAttrib = nullptr; + mSymbols.fDupNativeFenceFDANDROID = nullptr; } void InitClientExtensions(); diff --git a/gfx/gl/GLScreenBuffer.cpp b/gfx/gl/GLScreenBuffer.cpp index 22011ccacb..a3ca56bc37 100755 --- a/gfx/gl/GLScreenBuffer.cpp +++ b/gfx/gl/GLScreenBuffer.cpp @@ -854,7 +854,7 @@ DrawBuffer::Create(GLContext* const gl, DrawBuffer::~DrawBuffer() { - if(!mGL->MakeCurrent()) + if (!mGL->MakeCurrent()) return; GLuint fb = mFB; @@ -932,7 +932,7 @@ ReadBuffer::Create(GLContext* gl, ReadBuffer::~ReadBuffer() { - if(!mGL->MakeCurrent()) + if (!mGL->MakeCurrent()) return; GLuint fb = mFB; diff --git a/gfx/gl/GLXLibrary.h b/gfx/gl/GLXLibrary.h index 1ebe90a72c..7ce332fa7d 100644 --- a/gfx/gl/GLXLibrary.h +++ b/gfx/gl/GLXLibrary.h @@ -32,13 +32,36 @@ namespace gl { class GLXLibrary { public: - GLXLibrary() : mInitialized(false), mTriedInitializing(false), - mUseTextureFromPixmap(false), mDebug(false), - mHasRobustness(false), mHasCreateContextAttribs(false), - mIsATI(false), mIsNVIDIA(false), - mClientIsMesa(false), mGLXMajorVersion(0), - mGLXMinorVersion(0), - mOGLLibrary(nullptr) {} + MOZ_CONSTEXPR GLXLibrary() + : xDestroyContextInternal(nullptr) + , xMakeCurrentInternal(nullptr) + , xGetCurrentContextInternal(nullptr) + , xGetProcAddressInternal(nullptr) + , xChooseFBConfigInternal(nullptr) + , xGetFBConfigsInternal(nullptr) + , xCreateNewContextInternal(nullptr) + , xGetFBConfigAttribInternal(nullptr) + , xSwapBuffersInternal(nullptr) + , xQueryExtensionsStringInternal(nullptr) + , xGetClientStringInternal(nullptr) + , xQueryServerStringInternal(nullptr) + , xCreatePixmapInternal(nullptr) + , xCreateGLXPixmapWithConfigInternal(nullptr) + , xDestroyPixmapInternal(nullptr) + , xQueryVersionInternal(nullptr) + , xBindTexImageInternal(nullptr) + , xReleaseTexImageInternal(nullptr) + , xWaitGLInternal(nullptr) + , xWaitXInternal(nullptr) + , xCreateContextAttribsInternal(nullptr) + , mInitialized(false), mTriedInitializing(false) + , mUseTextureFromPixmap(false), mDebug(false) + , mHasRobustness(false), mHasCreateContextAttribs(false) + , mIsATI(false), mIsNVIDIA(false) + , mClientIsMesa(false), mGLXMajorVersion(0) + , mGLXMinorVersion(0) + , mOGLLibrary(nullptr) + {} void xDestroyContext(Display* display, GLXContext context); Bool xMakeCurrent(Display* display, diff --git a/gfx/gl/SharedSurfaceEGL.cpp b/gfx/gl/SharedSurfaceEGL.cpp index e911f3f3ec..abd94bd5b7 100644 --- a/gfx/gl/SharedSurfaceEGL.cpp +++ b/gfx/gl/SharedSurfaceEGL.cpp @@ -39,7 +39,7 @@ SharedSurface_EGLImage::Create(GLContext* prodGL, return Move(ret); } - EGLClientBuffer buffer = reinterpret_cast(prodTex); + EGLClientBuffer buffer = reinterpret_cast(uintptr_t(prodTex)); EGLImage image = egl->fCreateImage(egl->Display(), context, LOCAL_EGL_GL_TEXTURE_2D, buffer, nullptr); diff --git a/gfx/ipc/GfxMessageUtils.h b/gfx/ipc/GfxMessageUtils.h index 0507ca0ec8..22476cf8bb 100644 --- a/gfx/ipc/GfxMessageUtils.h +++ b/gfx/ipc/GfxMessageUtils.h @@ -906,7 +906,7 @@ struct ParamTraits : public ContiguousEnumSerializer< mozilla::StereoMode, mozilla::StereoMode::MONO, - mozilla::StereoMode::TOP_BOTTOM> + mozilla::StereoMode::MAX> {}; template <> diff --git a/gfx/layers/ImageTypes.h b/gfx/layers/ImageTypes.h index 9619f29724..3cd4e96cc5 100644 --- a/gfx/layers/ImageTypes.h +++ b/gfx/layers/ImageTypes.h @@ -99,7 +99,8 @@ enum class StereoMode { LEFT_RIGHT, RIGHT_LEFT, BOTTOM_TOP, - TOP_BOTTOM + TOP_BOTTOM, + MAX, }; } // namespace mozilla diff --git a/gfx/layers/LayerTreeInvalidation.cpp b/gfx/layers/LayerTreeInvalidation.cpp index 77b99835d5..215ebb1236 100644 --- a/gfx/layers/LayerTreeInvalidation.cpp +++ b/gfx/layers/LayerTreeInvalidation.cpp @@ -135,7 +135,7 @@ struct LayerPropertiesBase : public LayerProperties , mPostXScale(aLayer->GetPostXScale()) , mPostYScale(aLayer->GetPostYScale()) , mOpacity(aLayer->GetLocalOpacity()) - , mUseClipRect(!!aLayer->GetEffectiveClipRect()) + , mUseClipRect(!!aLayer->GetLocalClipRect()) { MOZ_COUNT_CTOR(LayerPropertiesBase); if (aLayer->GetMaskLayer()) { @@ -146,7 +146,7 @@ struct LayerPropertiesBase : public LayerProperties mAncestorMaskLayers.AppendElement(CloneLayerTreePropertiesInternal(maskLayer, true)); } if (mUseClipRect) { - mClipRect = *aLayer->GetEffectiveClipRect(); + mClipRect = *aLayer->GetLocalClipRect(); } mTransform = GetTransformForInvalidation(aLayer); } @@ -173,7 +173,7 @@ struct LayerPropertiesBase : public LayerProperties bool transformChanged = !mTransform.FuzzyEqual(GetTransformForInvalidation(mLayer)) || mLayer->GetPostXScale() != mPostXScale || mLayer->GetPostYScale() != mPostYScale; - const Maybe& otherClip = mLayer->GetEffectiveClipRect(); + const Maybe& otherClip = mLayer->GetLocalClipRect(); nsIntRegion result; bool ancestorMaskChanged = mAncestorMaskLayers.Length() != mLayer->GetAncestorMaskLayerCount(); @@ -191,7 +191,7 @@ struct LayerPropertiesBase : public LayerProperties ancestorMaskChanged || (mUseClipRect != !!otherClip) || mLayer->GetLocalOpacity() != mOpacity || - transformChanged) + transformChanged) { aGeometryChanged = true; result = OldTransformedBounds(); @@ -220,7 +220,7 @@ struct LayerPropertiesBase : public LayerProperties if (mUseClipRect && otherClip) { if (!mClipRect.IsEqualInterior(*otherClip)) { aGeometryChanged = true; - nsIntRegion tmp; + nsIntRegion tmp; tmp.Xor(mClipRect.ToUnknownRect(), otherClip->ToUnknownRect()); AddRegion(result, tmp); } @@ -474,7 +474,7 @@ struct ImageLayerProperties : public LayerPropertiesBase bool& aGeometryChanged) { ImageLayer* imageLayer = static_cast(mLayer.get()); - + if (!imageLayer->GetLocalVisibleRegion().ToUnknownRegion().IsEqual(mVisibleRegion)) { aGeometryChanged = true; IntRect result = NewTransformedBounds(); @@ -585,7 +585,7 @@ LayerProperties::CloneFrom(Layer* aRoot) return CloneLayerTreePropertiesInternal(aRoot); } -/* static */ void +/* static */ void LayerProperties::ClearInvalidations(Layer *aLayer) { aLayer->ClearInvalidRect(); diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index b75983d781..2f8f57242a 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -600,7 +600,7 @@ Layer::CanUseOpaqueSurface() // NB: eventually these methods will be defined unconditionally, and // can be moved into Layers.h const Maybe& -Layer::GetEffectiveClipRect() +Layer::GetLocalClipRect() { if (LayerComposite* shadow = AsLayerComposite()) { return shadow->GetShadowClipRect(); @@ -786,7 +786,7 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) // ContainerState::SetupScrollingMetadata() may install a clip on // the layer. Layer *clipLayer = - containerChild && containerChild->GetEffectiveClipRect() ? + containerChild && containerChild->GetLocalClipRect() ? containerChild : this; // Establish initial clip rect: it's either the one passed in, or @@ -798,7 +798,7 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) currentClip = aCurrentScissorRect; } - if (!clipLayer->GetEffectiveClipRect()) { + if (!clipLayer->GetLocalClipRect()) { return currentClip; } @@ -815,7 +815,7 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) } const RenderTargetIntRect clipRect = - ViewAs(*clipLayer->GetEffectiveClipRect(), + ViewAs(*clipLayer->GetLocalClipRect(), PixelCastJustification::RenderTargetIsParentLayerForRoot); if (clipRect.IsEmpty()) { // We might have a non-translation transform in the container so we can't @@ -904,18 +904,10 @@ Layer::GetTransformTyped() const const Matrix4x4 Layer::GetLocalTransform() { - Matrix4x4 transform; if (LayerComposite* shadow = AsLayerComposite()) - transform = shadow->GetShadowBaseTransform(); + return shadow->GetShadowTransform(); else - transform = mTransform; - - transform.PostScale(GetPostXScale(), GetPostYScale(), 1.0f); - if (ContainerLayer* c = AsContainerLayer()) { - transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); - } - - return transform; + return GetTransform(); } const LayerToParentLayerMatrix4x4 @@ -981,7 +973,7 @@ Layer::GetEffectiveOpacity() } return opacity; } - + CompositionOp Layer::GetEffectiveMixBlendMode() { @@ -1061,8 +1053,8 @@ Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, // If the parent layer clips its lower layers, clip the visible region // we're accumulating. - if (layer->GetEffectiveClipRect()) { - aResult.AndWith(layer->GetEffectiveClipRect()->ToUnknownRect()); + if (layer->GetLocalClipRect()) { + aResult.AndWith(layer->GetLocalClipRect()->ToUnknownRect()); } // Now we need to walk across the list of siblings for this parent layer, @@ -1086,7 +1078,7 @@ Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult, siblingVisibleRegion.MoveBy(-siblingOffset.x, -siblingOffset.y); // Apply the sibling's clip. // Layer clip rects are not affected by the layer's transform. - Maybe clipRect = sibling->GetEffectiveClipRect(); + Maybe clipRect = sibling->GetLocalClipRect(); if (clipRect) { siblingVisibleRegion.AndWith(clipRect->ToUnknownRect()); } @@ -1327,7 +1319,7 @@ ContainerLayer::HasMultipleChildren() { uint32_t count = 0; for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { - const Maybe& clipRect = child->GetEffectiveClipRect(); + const Maybe& clipRect = child->GetLocalClipRect(); if (clipRect && clipRect->IsEmpty()) continue; if (child->GetLocalVisibleRegion().IsEmpty()) @@ -1447,7 +1439,7 @@ ContainerLayer::DefaultComputeEffectiveTransforms(const Matrix4x4& aTransformToS if (checkClipRect || checkMaskLayers) { for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { - const Maybe& clipRect = child->GetEffectiveClipRect(); + const Maybe& clipRect = child->GetLocalClipRect(); /* We can't (easily) forward our transform to children with a non-empty clip * rect since it would need to be adjusted for the transform. See * the calculations performed by CalculateScissorRect above. @@ -1578,7 +1570,7 @@ RefLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs) aAttrs = RefLayerAttributes(GetReferentId(), mEventRegionsOverride); } -/** +/** * StartFrameTimeRecording, together with StopFrameTimeRecording * enable recording of frame intervals. * diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index eb4d516614..11707b5830 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -1505,7 +1505,7 @@ public: // These getters can be used anytime. They return the effective // values that should be used when drawing this layer to screen, // accounting for this layer possibly being a shadow. - const Maybe& GetEffectiveClipRect(); + const Maybe& GetLocalClipRect(); const LayerIntRegion& GetLocalVisibleRegion(); bool Extend3DContext() { diff --git a/gfx/layers/ReadbackProcessor.cpp b/gfx/layers/ReadbackProcessor.cpp index de8042efb5..80693c78f3 100644 --- a/gfx/layers/ReadbackProcessor.cpp +++ b/gfx/layers/ReadbackProcessor.cpp @@ -79,7 +79,7 @@ FindBackgroundLayer(ReadbackLayer* aLayer, nsIntPoint* aOffset) } // cliprects are post-transform - const Maybe& clipRect = l->GetEffectiveClipRect(); + const Maybe& clipRect = l->GetLocalClipRect(); if (clipRect && !clipRect->Contains(ViewAs(IntRect(transformOffset, aLayer->GetSize())))) return nullptr; diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index a09407fa79..45efbc5e4d 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -781,6 +781,16 @@ APZCTreeManager::ReceiveInputEvent(InputData& aEvent, return result; } + // If/when we enable support for pan inputs off-main-thread, we'll need + // to duplicate this EventStateManager code or something. See the other + // call to GetUserPrefsForWheelEvent in this file for why these fields + // are stored separately. + MOZ_ASSERT(NS_IsMainThread()); + WidgetWheelEvent wheelEvent = panInput.ToWidgetWheelEvent(nullptr); + EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent, + &panInput.mUserDeltaMultiplierX, + &panInput.mUserDeltaMultiplierY); + RefPtr apzc = GetTargetAPZC(panInput.mPanStartPoint, &hitResult); if (apzc) { diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 04da8ed97b..885a35298f 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -1750,7 +1750,7 @@ AsyncPanZoomController::CanScroll(const InputData& aEvent) const delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput()); } else if (aEvent.mInputType == PANGESTURE_INPUT) { const PanGestureInput& panInput = aEvent.AsPanGestureInput(); - delta = ToParentLayerCoordinates(panInput.mPanDisplacement, panInput.mPanStartPoint); + delta = ToParentLayerCoordinates(panInput.UserMultipliedPanDisplacement(), panInput.mPanStartPoint); } if (!delta.x && !delta.y) { return false; @@ -1896,6 +1896,7 @@ nsEventStatus AsyncPanZoomController::OnScrollWheel(const ScrollWheelInput& aEve // If we're scroll snapping, use a smooth scroll animation to get // the desired physics. Note that SmoothScrollTo() will re-use an // existing smooth scroll animation if there is one. + APZC_LOG("%p wheel scrolling to snap point %s\n", this, Stringify(startPosition).c_str()); SmoothScrollTo(startPosition); break; } @@ -2018,19 +2019,27 @@ nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, bool return OnPanBegin(aEvent); } + // Note that there is a multiplier that applies onto the "physical" pan + // displacement (how much the user's fingers moved) that produces the "logical" + // pan displacement (how much the page should move). For some of the code + // below it makes more sense to use the physical displacement rather than + // the logical displacement, and vice-versa. + ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement; + ParentLayerPoint logicalPanDisplacement = aEvent.UserMultipliedLocalPanDisplacement(); + // We need to update the axis velocity in order to get a useful display port // size and position. We need to do so even if this is a momentum pan (i.e. // aFingersOnTouchpad == false); in that case the "with touch" part is not // really appropriate, so we may want to rethink this at some point. - mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.x, aEvent.mLocalPanDisplacement.x, aEvent.mTime); - mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.y, aEvent.mLocalPanDisplacement.y, aEvent.mTime); + mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.x, logicalPanDisplacement.x, aEvent.mTime); + mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.y, logicalPanDisplacement.y, aEvent.mTime); - HandlePanningUpdate(aEvent.mPanDisplacement); + HandlePanningUpdate(physicalPanDisplacement); mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS, (uint32_t) ScrollInputMethod::ApzPanGesture); - ScreenPoint panDistance(fabs(aEvent.mPanDisplacement.x), fabs(aEvent.mPanDisplacement.y)); + ScreenPoint panDistance(fabs(physicalPanDisplacement.x), fabs(physicalPanDisplacement.y)); OverscrollHandoffState handoffState( *CurrentPanGestureBlock()->GetOverscrollHandoffChain(), panDistance, @@ -2044,7 +2053,7 @@ nsEventStatus AsyncPanZoomController::OnPan(const PanGestureInput& aEvent, bool // the motion of the scrolled contents, not of the scroll position, they need // to move in the opposite direction of the pan displacement. ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint; - ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint - aEvent.mLocalPanDisplacement; + ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint - logicalPanDisplacement; CallDispatchScroll(startPoint, endPoint, handoffState); return nsEventStatus_eConsumeNoDefault; @@ -3852,11 +3861,14 @@ AsyncPanZoomController::GetZoomConstraints() const void AsyncPanZoomController::PostDelayedTask(already_AddRefed aTask, int aDelayMs) { APZThreadUtils::AssertOnControllerThread(); + RefPtr task = aTask; RefPtr controller = GetGeckoContentController(); if (controller) { - controller->PostDelayedTask(Move(aTask), aDelayMs); + controller->PostDelayedTask(task.forget(), aDelayMs); } - // XXX khuey what is supposed to happen if there's no controller? We were leaking tasks ... + // If there is no controller, that means this APZC has been destroyed, and + // we probably don't need to run the task. It will get destroyed when the + // RefPtr goes out of scope. } bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid) @@ -3965,6 +3977,7 @@ void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) { if (Maybe snapPoint = FindSnapPointNear(aDestination, nsIScrollableFrame::DEVICE_PIXELS)) { if (*snapPoint != mFrameMetrics.GetScrollOffset()) { + APZC_LOG("%p smooth scrolling to snap point %s\n", this, Stringify(*snapPoint).c_str()); SmoothScrollTo(*snapPoint); } } diff --git a/gfx/layers/apz/src/InputQueue.cpp b/gfx/layers/apz/src/InputQueue.cpp index 72180c3d7b..0909465d6a 100644 --- a/gfx/layers/apz/src/InputQueue.cpp +++ b/gfx/layers/apz/src/InputQueue.cpp @@ -400,7 +400,7 @@ InputQueue::CancelAnimationsForNewBlock(CancelableBlockState* aBlock) // being processed) we only do this animation-cancellation if there are no older // touch blocks still in the queue. if (aBlock == CurrentBlock()) { - aBlock->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll); + aBlock->GetOverscrollHandoffChain()->CancelAnimations(ExcludeOverscroll | ScrollSnap); } } diff --git a/gfx/layers/apz/src/WheelScrollAnimation.cpp b/gfx/layers/apz/src/WheelScrollAnimation.cpp index a17f7d8937..d7cb338e6d 100644 --- a/gfx/layers/apz/src/WheelScrollAnimation.cpp +++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp @@ -55,7 +55,10 @@ WheelScrollAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& ParentLayerPoint displacement = (CSSPoint::FromAppUnits(sampledDest) - aFrameMetrics.GetScrollOffset()) * zoom; - if (!IsZero(displacement)) { + if (finished) { + mApzc.mX.SetVelocity(0); + mApzc.mY.SetVelocity(0); + } else if (!IsZero(displacement)) { // Velocity is measured in ParentLayerCoords / Milliseconds float xVelocity = displacement.x / aDelta.ToMilliseconds(); float yVelocity = displacement.y / aDelta.ToMilliseconds(); diff --git a/gfx/layers/apz/test/gtest/APZTestCommon.h b/gfx/layers/apz/test/gtest/APZTestCommon.h index 6bec600805..aa12dfb342 100644 --- a/gfx/layers/apz/test/gtest/APZTestCommon.h +++ b/gfx/layers/apz/test/gtest/APZTestCommon.h @@ -66,6 +66,11 @@ private: &(gfxPrefs::Set##prefBase), \ prefValue) +static TimeStamp GetStartupTime() { + static TimeStamp sStartupTime = TimeStamp::Now(); + return sStartupTime; +} + class MockContentController : public GeckoContentController { public: MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&)); @@ -85,7 +90,7 @@ public: class MockContentControllerDelayed : public MockContentController { public: MockContentControllerDelayed() - : mTime(TimeStamp::Now()) + : mTime(GetStartupTime()) { } @@ -265,11 +270,6 @@ public: mWaitForMainThread = true; } - static TimeStamp GetStartupTime() { - static TimeStamp sStartupTime = TimeStamp::Now(); - return sStartupTime; - } - private: bool mWaitForMainThread; MockContentControllerDelayed* mcc; @@ -300,7 +300,7 @@ TestFrameMetrics() uint32_t MillisecondsSinceStartup(TimeStamp aTime) { - return (aTime - TestAsyncPanZoomController::GetStartupTime()).ToMilliseconds(); + return (aTime - GetStartupTime()).ToMilliseconds(); } #endif // mozilla_layers_APZTestCommon_h diff --git a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp index 15ad393ee3..635fe72de1 100644 --- a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp +++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp @@ -605,6 +605,25 @@ TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) { Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId); EXPECT_NE(touchBlockId, wheelBlockId); mcc->AdvanceByMillis(1000); - - +} + +TEST_F(APZCGestureDetectorTester, TapTimeoutInterruptedByWheel) { + // In this test, even though the wheel block comes right after the tap, the + // tap should still be dispatched because it completes fully before the wheel + // block arrived. + EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); + + // We make the APZC zoomable so the gesture detector needs to wait to + // distinguish between tap and double-tap. During that timeout is when we + // insert the wheel event. + MakeApzcZoomable(); + + uint64_t touchBlockId = 0; + uint64_t wheelBlockId = 0; + Tap(apzc, ScreenIntPoint(10, 10), mcc, TimeDuration::FromMilliseconds(100), + nullptr, &touchBlockId); + mcc->AdvanceByMillis(10); + Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId); + EXPECT_NE(touchBlockId, wheelBlockId); + while (mcc->RunThroughDelayedTasks()); } diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html new file mode 100644 index 0000000000..3db9f2969e --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html @@ -0,0 +1,27 @@ + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html new file mode 100644 index 0000000000..479363f3fb --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html @@ -0,0 +1,53 @@ + + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/reftest.list b/gfx/layers/apz/test/reftest/reftest.list index 5dc127bc26..e7aca21364 100644 --- a/gfx/layers/apz/test/reftest/reftest.list +++ b/gfx/layers/apz/test/reftest/reftest.list @@ -15,3 +15,5 @@ skip-if(!asyncZoom) fuzzy-if(B2G,94,146) == async-scrollbar-zoom-2.html async-sc # Meta-viewport tag support skip-if(!asyncZoom) == initial-scale-1.html initial-scale-1-ref.html + +skip-if(!asyncPan) == frame-reconstruction-scroll-clamping.html frame-reconstruction-scroll-clamping-ref.html diff --git a/gfx/layers/apz/util/APZEventState.cpp b/gfx/layers/apz/util/APZEventState.cpp index a88c1e7453..d8078388d5 100644 --- a/gfx/layers/apz/util/APZEventState.cpp +++ b/gfx/layers/apz/util/APZEventState.cpp @@ -10,6 +10,7 @@ #include "gfxPrefs.h" #include "LayersLogging.h" #include "mozilla/BasicEvents.h" +#include "mozilla/IntegerPrintfMacros.h" #include "mozilla/Move.h" #include "mozilla/Preferences.h" #include "mozilla/TouchEvents.h" diff --git a/gfx/layers/apz/util/APZThreadUtils.cpp b/gfx/layers/apz/util/APZThreadUtils.cpp index 96fbc53aca..d9fb0a4d9e 100644 --- a/gfx/layers/apz/util/APZThreadUtils.cpp +++ b/gfx/layers/apz/util/APZThreadUtils.cpp @@ -80,23 +80,6 @@ APZThreadUtils::RunOnControllerThread(already_AddRefed aTask) #endif } -/*static*/ void -APZThreadUtils::RunDelayedTaskOnCurrentThread(already_AddRefed aTask, - const TimeDuration& aDelay) -{ - if (MessageLoop* messageLoop = MessageLoop::current()) { - messageLoop->PostDelayedTask(Move(aTask), aDelay.ToMilliseconds()); - } else { -#ifdef MOZ_ANDROID_APZ - // Fennec does not have a MessageLoop::current() on the controller thread. - AndroidBridge::Bridge()->PostTaskToUiThread(Move(aTask), aDelay.ToMilliseconds()); -#else - // Other platforms should. - MOZ_RELEASE_ASSERT(false, "This non-Fennec platform should have a MessageLoop::current()"); -#endif - } -} - /*static*/ bool APZThreadUtils::IsControllerThread() { diff --git a/gfx/layers/apz/util/APZThreadUtils.h b/gfx/layers/apz/util/APZThreadUtils.h index 0accd19afc..b16c14789a 100644 --- a/gfx/layers/apz/util/APZThreadUtils.h +++ b/gfx/layers/apz/util/APZThreadUtils.h @@ -7,7 +7,6 @@ #define mozilla_layers_APZThreadUtils_h #include "base/message_loop.h" -#include "mozilla/TimeStamp.h" // for TimeDuration #include "nsITimer.h" namespace mozilla { @@ -53,12 +52,6 @@ public: */ static void RunOnControllerThread(already_AddRefed aTask); - /** - * Runs the given task on the current thread after a delay of |aDelay|. - */ - static void RunDelayedTaskOnCurrentThread(already_AddRefed aTask, - const TimeDuration& aDelay); - /** * Returns true if currently on APZ "controller thread". */ diff --git a/gfx/layers/basic/BasicLayerManager.cpp b/gfx/layers/basic/BasicLayerManager.cpp index 9f1a0ffac5..9fe56e036d 100644 --- a/gfx/layers/basic/BasicLayerManager.cpp +++ b/gfx/layers/basic/BasicLayerManager.cpp @@ -413,7 +413,7 @@ MarkLayersHidden(Layer* aLayer, const IntRect& aClipRect, } { - const Maybe& clipRect = aLayer->GetEffectiveClipRect(); + const Maybe& clipRect = aLayer->GetLocalClipRect(); if (clipRect) { IntRect cr = clipRect->ToUnknownRect(); // clipRect is in the container's coordinate system. Get it into the @@ -492,7 +492,7 @@ ApplyDoubleBuffering(Layer* aLayer, const IntRect& aVisibleRect) IntRect newVisibleRect(aVisibleRect); { - const Maybe& clipRect = aLayer->GetEffectiveClipRect(); + const Maybe& clipRect = aLayer->GetLocalClipRect(); if (clipRect) { IntRect cr = clipRect->ToUnknownRect(); // clipRect is in the container's coordinate system. Get it into the @@ -771,7 +771,7 @@ BasicLayerManager::FlushGroup(PaintLayerContext& aPaintContext, bool aNeedsClipT static void InstallLayerClipPreserves3D(gfxContext* aTarget, Layer* aLayer) { - const Maybe &clipRect = aLayer->GetEffectiveClipRect(); + const Maybe &clipRect = aLayer->GetLocalClipRect(); if (!clipRect) { return; @@ -823,7 +823,7 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget, RenderTraceScope trace("BasicLayerManager::PaintLayer", "707070"); - const Maybe& clipRect = aLayer->GetEffectiveClipRect(); + const Maybe& clipRect = aLayer->GetLocalClipRect(); BasicContainerLayer* container = static_cast(aLayer->AsContainerLayer()); bool needsGroup = container && container->UseIntermediateSurface(); @@ -871,7 +871,7 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget, // Don't need to clip to visible region again needsClipToVisibleRegion = false; } - + if (is2D) { paintLayerContext.AnnotateOpaqueRect(); } diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp index 64a8b19916..d03786c3c0 100644 --- a/gfx/layers/client/CanvasClient.cpp +++ b/gfx/layers/client/CanvasClient.cpp @@ -390,6 +390,9 @@ CanvasClientSharedSurface::UpdateRenderer(gfx::IntSize aSize, Renderer& aRendere } } else { mShSurfClient = gl->Screen()->Front(); + if (mShSurfClient && mShSurfClient->GetAllocator() != GetForwarder()) { + mShSurfClient = CloneSurface(mShSurfClient->Surf(), gl->Screen()->Factory()); + } if (!mShSurfClient) { return; } diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp index 20bbd9ea1b..cb819b8243 100644 --- a/gfx/layers/client/ClientLayerManager.cpp +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -352,10 +352,6 @@ ClientLayerManager::EndTransaction(DrawPaintedLayerCallback aCallback, MakeSnapshotIfRequired(); } - for (size_t i = 0; i < mTexturePools.Length(); i++) { - mTexturePools[i]->ReturnDeferredClients(); - } - mInTransaction = false; mTransactionStart = TimeStamp(); } @@ -432,6 +428,10 @@ ClientLayerManager::DidComposite(uint64_t aTransactionId, for (size_t i = 0; i < mDidCompositeObservers.Length(); i++) { mDidCompositeObservers[i]->DidComposite(); } + + for (size_t i = 0; i < mTexturePools.Length(); i++) { + mTexturePools[i]->ReturnDeferredClients(); + } } void diff --git a/gfx/layers/client/SingleTiledContentClient.h b/gfx/layers/client/SingleTiledContentClient.h index 42b5359a63..d8239ea045 100644 --- a/gfx/layers/client/SingleTiledContentClient.h +++ b/gfx/layers/client/SingleTiledContentClient.h @@ -33,7 +33,7 @@ public: // TextureClientAllocator already_AddRefed GetTextureClient() override; - void ReturnTextureClientDeferred(TextureClient* aClient) override {} + void ReturnTextureClientDeferred(TextureClient* aClient, gfxSharedReadLock* aLock) override {} void ReportClientLost() override {} // ClientTiledLayerBuffer diff --git a/gfx/layers/client/TextureClientPool.cpp b/gfx/layers/client/TextureClientPool.cpp index aec63e311b..8502de933a 100644 --- a/gfx/layers/client/TextureClientPool.cpp +++ b/gfx/layers/client/TextureClientPool.cpp @@ -6,6 +6,7 @@ #include "TextureClientPool.h" #include "CompositableClient.h" #include "mozilla/layers/CompositableForwarder.h" +#include "mozilla/layers/TiledContentClient.h" #include "gfxPrefs.h" @@ -157,16 +158,17 @@ TextureClientPool::ReturnTextureClient(TextureClient *aClient) } void -TextureClientPool::ReturnTextureClientDeferred(TextureClient *aClient) +TextureClientPool::ReturnTextureClientDeferred(TextureClient *aClient, gfxSharedReadLock* aLock) { if (!aClient) { return; } + MOZ_ASSERT(aLock); #ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL DebugOnly ok = TestClientPool("defer", aClient, this); MOZ_ASSERT(ok); #endif - mTextureClientsDeferred.push(aClient); + mTextureClientsDeferred.push_back(TextureClientHolder(aClient, aLock)); TCP_LOG("TexturePool %p had client %p defer-returned, size %u outstanding %u\n", this, aClient, mTextureClientsDeferred.size(), mOutstandingClients); ShrinkToMaximumSize(); @@ -188,9 +190,9 @@ TextureClientPool::ShrinkToMaximumSize() MOZ_ASSERT(mOutstandingClients > 0); mOutstandingClients--; TCP_LOG("TexturePool %p dropped deferred client %p; %u remaining\n", - this, mTextureClientsDeferred.top().get(), + this, mTextureClientsDeferred.front().mTextureClient.get(), mTextureClientsDeferred.size() - 1); - mTextureClientsDeferred.pop(); + mTextureClientsDeferred.pop_front(); } else { if (!mTextureClients.size()) { // Getting here means we're over our desired number of TextureClients @@ -211,6 +213,16 @@ TextureClientPool::ShrinkToMaximumSize() void TextureClientPool::ShrinkToMinimumSize() { + ReturnUnlockedClients(); + + while (!mTextureClientsDeferred.empty()) { + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + TCP_LOG("TexturePool %p releasing deferred client %p\n", + this, mTextureClientsDeferred.front().mTextureClient.get()); + mTextureClientsDeferred.pop_front(); + } + TCP_LOG("TexturePool %p shrinking to minimum size %u\n", this, sMinCacheSize); while (mTextureClients.size() > sMinCacheSize) { TCP_LOG("TexturePool %p popped %p; shrunk to %u\n", @@ -224,13 +236,12 @@ TextureClientPool::ReturnDeferredClients() { TCP_LOG("TexturePool %p returning %u deferred clients to pool\n", this, mTextureClientsDeferred.size()); - while (!mTextureClientsDeferred.empty()) { - mTextureClients.push(mTextureClientsDeferred.top()); - mTextureClientsDeferred.pop(); - MOZ_ASSERT(mOutstandingClients > 0); - mOutstandingClients--; + if (mTextureClientsDeferred.empty()) { + return; } + + ReturnUnlockedClients(); ShrinkToMaximumSize(); // Kick off the pool shrinking timer if there are still more unused texture @@ -242,6 +253,24 @@ TextureClientPool::ReturnDeferredClients() } } +void +TextureClientPool::ReturnUnlockedClients() +{ + for (auto it = mTextureClientsDeferred.begin(); it != mTextureClientsDeferred.end();) { + MOZ_ASSERT((*it).mLock->GetReadCount() >= 1); + // Last count is held by the lock itself. + if ((*it).mLock->GetReadCount() == 1) { + mTextureClients.push((*it).mTextureClient); + it = mTextureClientsDeferred.erase(it); + + MOZ_ASSERT(mOutstandingClients > 0); + mOutstandingClients--; + } else { + it++; + } + } +} + void TextureClientPool::ReportClientLost() { @@ -264,8 +293,8 @@ TextureClientPool::Clear() MOZ_ASSERT(mOutstandingClients > 0); mOutstandingClients--; TCP_LOG("TexturePool %p releasing deferred client %p\n", - this, mTextureClientsDeferred.top().get()); - mTextureClientsDeferred.pop(); + this, mTextureClientsDeferred.front().mTextureClient.get()); + mTextureClientsDeferred.pop_front(); } } diff --git a/gfx/layers/client/TextureClientPool.h b/gfx/layers/client/TextureClientPool.h index 9c72030055..e6b764a3ca 100644 --- a/gfx/layers/client/TextureClientPool.h +++ b/gfx/layers/client/TextureClientPool.h @@ -12,12 +12,14 @@ #include "TextureClient.h" #include "nsITimer.h" #include +#include namespace mozilla { namespace layers { class ISurfaceAllocator; class CompositableForwarder; +class gfxSharedReadLock; class TextureClientAllocator { @@ -32,7 +34,7 @@ public: * Return a TextureClient that is not yet ready to be reused, but will be * imminently. */ - virtual void ReturnTextureClientDeferred(TextureClient *aClient) = 0; + virtual void ReturnTextureClientDeferred(TextureClient *aClient, gfxSharedReadLock* aLock) = 0; virtual void ReportClientLost() = 0; }; @@ -70,7 +72,7 @@ public: * Return a TextureClient that is not yet ready to be reused, but will be * imminently. */ - void ReturnTextureClientDeferred(TextureClient *aClient) override; + void ReturnTextureClientDeferred(TextureClient *aClient, gfxSharedReadLock* aLock) override; /** * Attempt to shrink the pool so that there are no more than @@ -111,6 +113,8 @@ public: void Destroy(); private: + void ReturnUnlockedClients(); + // The minimum size of the pool (the number of tiles that will be kept after // shrinking). static const uint32_t sMinCacheSize = 0; @@ -137,11 +141,21 @@ private: /// existence is always mOutstandingClients + the size of mTextureClients. uint32_t mOutstandingClients; + struct TextureClientHolder { + RefPtr mTextureClient; + RefPtr mLock; + + TextureClientHolder(TextureClient* aTextureClient, gfxSharedReadLock* aLock) + : mTextureClient(aTextureClient), mLock(aLock) + {} + }; + // On b2g gonk, std::queue might be a better choice. // On ICS, fence wait happens implicitly before drawing. // Since JB, fence wait happens explicitly when fetching a client from the pool. std::stack > mTextureClients; - std::stack > mTextureClientsDeferred; + + std::list mTextureClientsDeferred; RefPtr mTimer; RefPtr mSurfaceAllocator; }; diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp index 213e6a60f0..f767e10949 100644 --- a/gfx/layers/client/TiledContentClient.cpp +++ b/gfx/layers/client/TiledContentClient.cpp @@ -358,7 +358,8 @@ gfxMemorySharedReadLock::gfxMemorySharedReadLock() gfxMemorySharedReadLock::~gfxMemorySharedReadLock() { - MOZ_ASSERT(mReadCount == 0); + // One read count that is added in constructor. + MOZ_ASSERT(mReadCount == 1); MOZ_COUNT_DTOR(gfxMemorySharedReadLock); } @@ -405,6 +406,12 @@ gfxShmSharedReadLock::gfxShmSharedReadLock(ClientIPCAllocator* aAllocator) gfxShmSharedReadLock::~gfxShmSharedReadLock() { + auto fwd = mAllocator->AsLayerForwarder(); + if (fwd) { + // Release one read count that is added in constructor. + // The count is kept for calling GetReadCount() by TextureClientPool. + ReadUnlock(); + } MOZ_COUNT_DTOR(gfxShmSharedReadLock); } @@ -673,12 +680,11 @@ TileClient::DiscardFrontBuffer() mFrontBuffer->RemoveFromCompositable(mCompositableClient); } - mAllocator->ReturnTextureClientDeferred(mFrontBuffer); + mAllocator->ReturnTextureClientDeferred(mFrontBuffer, mFrontLock); if (mFrontBufferOnWhite) { mFrontBufferOnWhite->RemoveFromCompositable(mCompositableClient); - mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite); + mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite, mFrontLock); } - mFrontLock->ReadUnlock(); if (mFrontBuffer->IsLocked()) { mFrontBuffer->Unlock(); } @@ -705,12 +711,11 @@ TileClient::DiscardBackBuffer() mAllocator->ReportClientLost(); } } else { - mAllocator->ReturnTextureClientDeferred(mBackBuffer); + mAllocator->ReturnTextureClientDeferred(mBackBuffer, mBackLock); if (mBackBufferOnWhite) { - mAllocator->ReturnTextureClientDeferred(mBackBufferOnWhite); + mAllocator->ReturnTextureClientDeferred(mBackBufferOnWhite, mBackLock); } } - mBackLock->ReadUnlock(); if (mBackBuffer->IsLocked()) { mBackBuffer->Unlock(); } @@ -744,11 +749,6 @@ TileClient::GetBackBuffer(const nsIntRegion& aDirtyRegion, if (!mBackBuffer || mBackLock->GetReadCount() > 1) { - if (mBackLock) { - // Before we Replacing the lock by another one we need to unlock it! - mBackLock->ReadUnlock(); - } - if (mBackBuffer) { // Our current back-buffer is still locked by the compositor. This can occur // when the client is producing faster than the compositor can consume. In diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp index 9f9ab69465..557ec6185b 100755 --- a/gfx/layers/composite/ContainerLayerComposite.cpp +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -542,8 +542,8 @@ RenderMinimap(ContainerT* aContainer, LayerManagerComposite* aManager, float scaleFactorX; float scaleFactorY; Rect dest = Rect(aClipRect.ToUnknownRect()); - if (aLayer->GetEffectiveClipRect()) { - dest = Rect(aLayer->GetEffectiveClipRect().value().ToUnknownRect()); + if (aLayer->GetLocalClipRect()) { + dest = Rect(aLayer->GetLocalClipRect().value().ToUnknownRect()); } else { dest = aContainer->GetEffectiveTransform().Inverse().TransformBounds(dest); } diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp index e72e0663b7..369076405c 100644 --- a/gfx/layers/composite/ImageHost.cpp +++ b/gfx/layers/composite/ImageHost.cpp @@ -332,10 +332,6 @@ ImageHost::Composite(LayerComposite* aLayer, TimedImage* img = &mImages[imageIndex]; img->mTextureHost->SetCompositor(GetCompositor()); SetCurrentTextureHost(img->mTextureHost); - // Make sure the front buffer has a compositor - if (mCurrentTextureSource) { - mCurrentTextureSource->SetCompositor(GetCompositor()); - } { AutoLockCompositableHost autoLock(this); diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp index 35ce432398..a49450e015 100644 --- a/gfx/layers/composite/LayerManagerComposite.cpp +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -164,11 +164,11 @@ void LayerManagerComposite::BeginTransaction() { mInTransaction = true; - + if (!mCompositor->Ready()) { return; } - + mIsCompositorReady = true; } @@ -176,7 +176,7 @@ void LayerManagerComposite::BeginTransactionWithDrawTarget(DrawTarget* aTarget, const IntRect& aRect) { mInTransaction = true; - + if (!mCompositor->Ready()) { return; } @@ -257,7 +257,7 @@ LayerManagerComposite::PostProcessLayers(Layer* aLayer, localOpaque.MoveBy(-*integerTranslation); } } - + // Compute a clip that's the combination of our layer clip with the clip // from our ancestors. LayerComposite* composite = aLayer->AsLayerComposite(); @@ -1565,6 +1565,19 @@ LayerComposite::GetFullyRenderedRegion() { } } +const Matrix4x4 +LayerComposite::GetShadowTransform() { + Matrix4x4 transform = mShadowTransform; + Layer* layer = GetLayer(); + + transform.PostScale(layer->GetPostXScale(), layer->GetPostYScale(), 1.0f); + if (const ContainerLayer* c = layer->AsContainerLayer()) { + transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); + } + + return transform; +} + bool LayerComposite::HasStaleCompositor() const { diff --git a/gfx/layers/composite/LayerManagerComposite.h b/gfx/layers/composite/LayerManagerComposite.h index e5f30143ac..e6fbfd6f2d 100644 --- a/gfx/layers/composite/LayerManagerComposite.h +++ b/gfx/layers/composite/LayerManagerComposite.h @@ -541,6 +541,7 @@ public: const Maybe& GetShadowClipRect() { return mShadowClipRect; } const LayerIntRegion& GetShadowVisibleRegion() { return mShadowVisibleRegion; } const gfx::Matrix4x4& GetShadowBaseTransform() { return mShadowTransform; } + const gfx::Matrix4x4 GetShadowTransform(); bool GetShadowTransformSetByAnimation() { return mShadowTransformSetByAnimation; } bool HasLayerBeenComposited() { return mLayerComposited; } gfx::IntRect GetClearRect() { return mClearRect; } diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp index e66b9cfda3..c552cf1e14 100644 --- a/gfx/layers/composite/TextureHost.cpp +++ b/gfx/layers/composite/TextureHost.cpp @@ -558,7 +558,8 @@ BufferTextureHost::PrepareTextureSource(CompositableTextureSourceRef& aTexture) || (mFormat == gfx::SurfaceFormat::YUV && mCompositor && mCompositor->SupportsEffect(EffectTypes::YCBCR) - && texture->GetNextSibling()) + && texture->GetNextSibling() + && texture->GetNextSibling()->GetNextSibling()) || (mFormat == gfx::SurfaceFormat::YUV && mCompositor && !mCompositor->SupportsEffect(EffectTypes::YCBCR) @@ -573,6 +574,15 @@ BufferTextureHost::PrepareTextureSource(CompositableTextureSourceRef& aTexture) mFirstSource = texture; mFirstSource->SetOwner(this); mNeedsFullUpdate = true; + + // It's possible that texture belonged to a different compositor, + // so make sure we update it (and all of its siblings) to the + // current one. + RefPtr it = mFirstSource; + while (it) { + it->SetCompositor(mCompositor); + it = it->GetNextSibling(); + } } } @@ -784,7 +794,8 @@ ShmemTextureHost::ShmemTextureHost(const ipc::Shmem& aShmem, // available, even though we did on the child process. // As a result this texture will be in an invalid state and Lock will // always fail. - gfxCriticalError() << "Failed to create a valid ShmemTextureHost"; + + gfxCriticalNote << "Failed to create a valid ShmemTextureHost"; } MOZ_COUNT_CTOR(ShmemTextureHost); diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index 257be9d487..3aef5cfc5e 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -1092,6 +1092,7 @@ CompositorOGL::DrawQuad(const Rect& aRect, aOpacity = 1.f; } + bool createdMixBlendBackdropTexture = false; GLuint mixBlendBackdrop = 0; gfx::CompositionOp blendMode = gfx::CompositionOp::OP_OVER; @@ -1124,9 +1125,19 @@ CompositorOGL::DrawQuad(const Rect& aRect, if (BlendOpIsMixBlendMode(blendMode)) { gfx::Matrix4x4 backdropTransform; - gfx::IntRect rect = ComputeBackdropCopyRect(aRect, aClipRect, aTransform, &backdropTransform); - mixBlendBackdrop = CreateTexture(rect, true, mCurrentRenderTarget->GetFBO()); + if (gl()->IsExtensionSupported(GLContext::NV_texture_barrier)) { + // The NV_texture_barrier extension lets us read directly from the + // backbuffer. Let's do that. + // We need to tell OpenGL about this, so that it can make sure everything + // on the GPU is happening in the right order. + gl()->fTextureBarrier(); + mixBlendBackdrop = mCurrentRenderTarget->GetTextureHandle(); + } else { + gfx::IntRect rect = ComputeBackdropCopyRect(aRect, aClipRect, aTransform, &backdropTransform); + mixBlendBackdrop = CreateTexture(rect, true, mCurrentRenderTarget->GetFBO()); + createdMixBlendBackdropTexture = true; + } program->SetBackdropTransform(backdropTransform); } @@ -1443,7 +1454,7 @@ CompositorOGL::DrawQuad(const Rect& aRect, gl()->fBlendFuncSeparate(LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA, LOCAL_GL_ONE, LOCAL_GL_ONE_MINUS_SRC_ALPHA); } - if (mixBlendBackdrop) { + if (createdMixBlendBackdropTexture) { gl()->fDeleteTextures(1, &mixBlendBackdrop); } diff --git a/gfx/tests/crashtests/1134549-1.svg b/gfx/tests/crashtests/1134549-1.svg new file mode 100644 index 0000000000..1d0d5484a8 --- /dev/null +++ b/gfx/tests/crashtests/1134549-1.svg @@ -0,0 +1,14 @@ + + + + + + + + Eisack + + + diff --git a/gfx/tests/crashtests/crashtests.list b/gfx/tests/crashtests/crashtests.list index b577715a31..ccce929571 100644 --- a/gfx/tests/crashtests/crashtests.list +++ b/gfx/tests/crashtests/crashtests.list @@ -116,3 +116,4 @@ load 944579.svg load 944579.html pref(security.fileuri.strict_origin_policy,false) load 950000.html load 1034403-1.html +load 1134549-1.svg diff --git a/gfx/thebes/gfxFontEntry.cpp b/gfx/thebes/gfxFontEntry.cpp index 7863274930..91e0e0f53a 100644 --- a/gfx/thebes/gfxFontEntry.cpp +++ b/gfx/thebes/gfxFontEntry.cpp @@ -1098,6 +1098,44 @@ gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, aSizes->mFontTableCacheSize += mFontTableCache->SizeOfIncludingThis(aMallocSizeOf); } + + // If the font has UVS data, we count that as part of the character map. + if (mUVSData) { + aSizes->mCharMapsSize += aMallocSizeOf(mUVSData.get()); + } + + // The following, if present, are essentially cached forms of font table + // data, so we'll accumulate them together with the basic table cache. + if (mUserFontData) { + aSizes->mFontTableCacheSize += + mUserFontData->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSVGGlyphs) { + aSizes->mFontTableCacheSize += + mSVGGlyphs->SizeOfIncludingThis(aMallocSizeOf); + } + if (mMathTable) { + aSizes->mFontTableCacheSize += + mMathTable->SizeOfIncludingThis(aMallocSizeOf); + } + if (mSupportedFeatures) { + aSizes->mFontTableCacheSize += + mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + if (mFeatureInputs) { + aSizes->mFontTableCacheSize += + mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = mFeatureInputs->ConstIter(); !iter.Done(); + iter.Next()) { + // There's no API to get the real size of an hb_set, so we'll use + // an approximation based on knowledge of the implementation. + aSizes->mFontTableCacheSize += 8192; // vector of 64K bits + } + } + // We don't include the size of mCOLR/mCPAL here, because (depending on the + // font backend implementation) they will either wrap blocks of data owned + // by the system (and potentially shared), or tables that are in our font + // table cache and therefore already counted. } void diff --git a/gfx/thebes/gfxMathTable.cpp b/gfx/thebes/gfxMathTable.cpp index c08d5d4c69..b95a76a35d 100644 --- a/gfx/thebes/gfxMathTable.cpp +++ b/gfx/thebes/gfxMathTable.cpp @@ -474,3 +474,13 @@ gfxMathTable::SelectGlyphConstruction(uint32_t aGlyphID, bool aVertical) mGlyphConstruction = reinterpret_cast(start + offset); } + +size_t +gfxMathTable::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + // We don't include the size of mMathTable here, because (depending on the + // font backend implementation) it will either wrap a block of data owned + // by the system (and potentially shared), or a table that's in our font + // table cache and therefore already counted. + return aMallocSizeOf(this); +} diff --git a/gfx/thebes/gfxMathTable.h b/gfx/thebes/gfxMathTable.h index 55bd6686a8..4745c0802f 100644 --- a/gfx/thebes/gfxMathTable.h +++ b/gfx/thebes/gfxMathTable.h @@ -76,6 +76,8 @@ public: bool GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, uint32_t aGlyphs[4]); + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + protected: friend class gfxFontEntry; // This allows gfxFontEntry to verify the validity of the main headers diff --git a/gfx/thebes/gfxSVGGlyphs.cpp b/gfx/thebes/gfxSVGGlyphs.cpp index a38b44ac9a..f53f19daa0 100644 --- a/gfx/thebes/gfxSVGGlyphs.cpp +++ b/gfx/thebes/gfxSVGGlyphs.cpp @@ -256,6 +256,22 @@ gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId) return !!GetGlyphElement(aGlyphId); } +size_t +gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + // We don't include the size of mSVGData here, because (depending on the + // font backend implementation) it will either wrap a block of data owned + // by the system (and potentially shared), or a table that's in our font + // table cache and therefore already counted. + size_t result = aMallocSizeOf(this) + + mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mGlyphDocs.ConstIter(); !iter.Done(); iter.Next()) { + result += iter.Data()->SizeOfIncludingThis(aMallocSizeOf); + } + return result; +} + Element * gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId) { @@ -437,6 +453,15 @@ gfxSVGGlyphsDocument::InsertGlyphId(Element *aGlyphElement) mGlyphIdMap.Put(id, aGlyphElement); } +size_t +gfxSVGGlyphsDocument::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + +} + void gfxTextContextPaint::InitStrokeGeometry(gfxContext *aContext, float devUnitsPerSVGUnit) diff --git a/gfx/thebes/gfxSVGGlyphs.h b/gfx/thebes/gfxSVGGlyphs.h index 1a81cd0107..34c6898ac2 100644 --- a/gfx/thebes/gfxSVGGlyphs.h +++ b/gfx/thebes/gfxSVGGlyphs.h @@ -49,6 +49,8 @@ public: virtual void DidRefresh() override; + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + private: nsresult ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen); @@ -128,6 +130,8 @@ public: bool GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace, gfxRect *aResult); + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + private: Element *GetGlyphElement(uint32_t aGlyphId); diff --git a/gfx/thebes/gfxUserFontSet.cpp b/gfx/thebes/gfxUserFontSet.cpp index ad3b86c358..3d416e0d71 100644 --- a/gfx/thebes/gfxUserFontSet.cpp +++ b/gfx/thebes/gfxUserFontSet.cpp @@ -52,11 +52,11 @@ public: NS_Free(mPtr); } - // return the buffer, and give up ownership of it - // so the caller becomes responsible to call NS_Free - // when finished with it + // Return the buffer, resized to fit its contents (as it may have been + // over-allocated during growth), and give up ownership of it so the + // caller becomes responsible to call free() when finished with it. void* forget() { - void* p = mPtr; + void* p = moz_xrealloc(mPtr, mOff); mPtr = nullptr; return p; } @@ -249,14 +249,14 @@ gfxUserFontEntry::SanitizeOpenTypeData(const uint8_t* aData, ExpandingMemoryStream output(lengthHint, 1024 * 1024 * 256); gfxOTSContext otsContext(this); - - if (otsContext.Process(&output, aData, aLength)) { - aSaneLength = output.Tell(); - return static_cast(output.forget()); - } else { + if (!otsContext.Process(&output, aData, aLength)) { + // Failed to decode/sanitize the font, so discard it. aSaneLength = 0; return nullptr; } + + aSaneLength = output.Tell(); + return static_cast(output.forget()); } void @@ -295,6 +295,16 @@ gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry, } } +size_t +gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + return aMallocSizeOf(this) + + mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf) + + mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf) + + mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + // Not counting mURI and mPrincipal, as those will be shared. +} + void gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName, nsACString& aURI) diff --git a/gfx/thebes/gfxUserFontSet.h b/gfx/thebes/gfxUserFontSet.h index d9be9a9648..d6a67fe489 100644 --- a/gfx/thebes/gfxUserFontSet.h +++ b/gfx/thebes/gfxUserFontSet.h @@ -101,6 +101,8 @@ public: { } virtual ~gfxUserFontData() { } + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + nsTArray mMetadata; // woff metadata block (compressed), if any nsCOMPtr mURI; // URI of the source, if it was url() nsCOMPtr mPrincipal; // principal for the download, if url() diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e8c6bf7bb47dff6b81c2cf7a349eb7e912c9fbe2 GIT binary patch literal 53638 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%0AB1q=av0)K=-*!|(RDoglJ z%yj=#1J2PtI1Xj-Cm3ITnE0IBU`Ba&$U$Kg`;b;fCo{a6a#kmibCO#vccKu<+z^6J z1cO%%CGV?ad>Lh2KXKkHuW|0J+AU>uw0{G7-4_auHiccCd_WDFhZeOYi{PSW)whVH+-JH&epwcU;QARK-6}q z0ZK34eeYOf7*^Q28*9+GIoG7I(-q^YFAbp~8%E@{XYekp-wL?@Ca-zdrC!y+rNki4 z6h9m{42ztIeDjFvNH?ydVgcs`hQQJQ1Cy3Z@0#t@j&}Dlr2iJHvLPGR8%-8{Tdg=) zkai5A4~<<6(}_#+CQRAbY;MtRJ(4eT!{~0-qP~2(TWvo3Rz!ZI0`BBClZZ=?FVU+6 z&QOZCksLR;Q;8Oz0d_~tERAab}&Jwa+HS>TTF zJ@Z-JUdM%N`s%!n0#ly@8v7U3KAFHKy(_}*W1IRco%7oj0d@`-6|910*~`icYO*^< z_x+ipryQNlvfGLS$KHIL5xS#CSwiQ8mZfVV0v~G0%ia;e`lrfcs@VJN0bY_}AQJ=$ z1>=zebA(V!cLCERMKHfheBn^#+426v@#FyD1<|Eb%1uy2N#x;u&X5?$X5sgjZ{84a zm%?t7Er&A3!wV4&7ezA$)*x*mm1XZ!x{KX6<#>ro8qVJw099fEOIggpS0cX1w~r(Z z*uH4Q%sCTfHH$TM7ZF`6G_?(c(pRHNES@DYv#^y%=fyuijfo1+nuy@3Tmivq4>m|X zux4sRwM>gAXTUPF=hWmBp#a;rm!*bi89q!X_3x=YLgE~Dvb0Ol=`;(f-;1r$ZE2%^Pu=W~`+kn!2r@5<^QaH9((uzEo0^S;HlMGyEESJMm-`G9I1oJWs4cJ8xu(jtE*jP z>|l(OAFmH_{51*HU*kVLDBA~t-x2`8ezZ9V#>t# zNodQ;rp)FIMU1E6p>@G-*EbaPB9LApd5ae{^brH z7NrrKQm$TSC@w;Gd&)xCg7*WaDq{ypj1<1kI!uA5;1aK>WT0QXQ7tafH?+7Xw!krS zckT@qLbEEJUZJz;2xo>n)Z9$;GFy5Re1S9Ys4QR#?sr0=qhjlDv0`00DLYMiJ@C!z zslI6$Boxf!40xmb-QlS|mzFu+jWJyo%Lh85o6RXEM#(ZIlZ+$O8a;W?Q#*_b`9ofp8|@RD1^z6~%@D0^fu$53B;bEnjQg(fFSjo#I zV9|i#%IZz%SR|*W-wlcsg_zJddPAk48d@6$3qSdtmG8K{J%X{&lv2t1hW7~XkmW}AlX%Y@u`TRlUV+&)vL8F{ zf-YiWMD7qn?1a5ToAO}Et_Rs$atT5`)c7xnx~A@{$k5lBhK!n@Fb%1+nG$r>A~1&3 zUnMk{D^Sg+704^eXeg8!yniEqNzC42z7IjFI11XWD#4@7wftc*HHSGoh$O{hhd(3< ztqt|lVnIQYxIDa`AcKF9{Zpo7Bb-~IQ;N=*uF!g2pl{=MGK=8g7mokF-L;* z?!)tqrrEMo`)+%w#Kw*UR{;xR)N?ok_L2nqUdQT(Eu4#m!lePKkLA~J0}D+tYbr2~ z#GB~yLx)K&cKO`zPj)uFw7Rra=v|7faj$DcM`=Gev1WD|dtrF^EX4w}Z7@aU78A~2 z78s1YQ}5BkIuk;QNs{N6@1uPyUqzGKqjD{5%yfM&EUYF9)(j~mincby@Ufzk4vWnc zHrmGfOn=&DAngom%)Xa~#Mgz(r;u6$^%{raWcGAiG9_Of;MlkhF--Q`v(@!TJi z&C3FuCf(|vQCU?e)l(Hd&}^jd?yfB<*?t-LL=jwm)D?Z)UvKtysk`@)rKobeWK&UL zlL+y&6bNf{Q^m4Tql+QCu9z~RtsD+-hIKvh%^=z1> z=DzQxP$dSM$s_5s@k2zVn^RV=;dt@6kj%9+<$?Vt3Qo*ZG7Q80r(Vv_QILIBJauQ| zw6Z)aGPk=~e%}N^5Xg(JxVs1oI?u}9dtWpHK?2jm$?{Dd8o9`Eo#~Vt)JiUDAJU6@ zbbPGY%CPE8TS5AOQVn-Is|$BKr^|wdgE{{=X9fgZ7YAw|+P;TG9EJPQeo7t1hZ%Yz zDrOZ@DVyQMu+=#l{pOLvckCs^m(B$9q$R;8dHGc5f^6O}Zi&jz?C zx`)8pC(N)3`%6eYZF9a}($Q9dw;(=HMEfZC?4Om*4$d&DjJ7`>Ww-Ok`_4lmp}_QX z1@#Dpem+607V{4is}v4KN6fbh9}lobOEP}>4)%r;EGgsG_T<*~(&_>v=n7Y@MepOop4yr4gkx8G)mNJa~L0Jii$9ly*+ zm2gkV;C#q9b6?|R?s#&4dhrg4hD!hSYk6rRUqOU>hOFG3z|b@a+PrA*%sx6=avKfL zvk#3l9XH$6d7JH_5YWhOBjz?L0tMB>*U772{EJllkLYOji=ex8wTCyL4Vyiad}{`2 zg(_SlE5mn~@<}DTkhRqWSl-SnB5Kqf#V@NMp?gi3n%nK6EwNF>md&#oS~7_Z*kOjd zaQnt@)70zj2X-Hl&zTjBzh&y*xl(GGc&DuO#)5@MM?>+YiM5pi_G^Y@`t&4Wd|5={ zbRHl3)N~$<)=u@*nIoFW2~+D~1%`OOsMyH3QnT?M+&~rNF!FY%6U(iU@vC!PRkGaX|TPdOUsO< z%|m)JZE)N#*JtqMq?5NS5-hQ-n<-~m64e{gct`#MXUKf#@PS#P+_y*%$L}k~sn>`o zNklnGY5_{q<+{^*1l@JfC0Mp}XdOhJ-m7|~_0@+&v3y}~5xE5susjF?6(~0LktUJ8 z`d$}l*(9_0nieU(&${g|62wQrn@L)n>8HX!FFCT0ecFt@il^OxDCCaa(y<+n#_c6% zL{oPbMKV!|BfSvYk63qbqS=im=Ok%=O@Q-daD~Ot9n0QGsP7FW4bJ6ytS`uiDg^zn zdP#kE@DU63A2|sUo}NwL^_`V=$$+Pz7;u{PU-4c1@fZj=>DU_l5diZSzKb#?z*;4{W%qP8O6(l!>lT8Ha0Dr@ zH-bUBMxy#;s>o*6*w{#lU4hf?>EqX{ZxG%tr4-RUaBy7JUrYqwXQg42Gv*UP2=&&2 zqXBkLSVRa{vXOysml@8W$~n&z47?sJvuWuHAp1u6 zVZafKDN6f3@yNV&m-sc!!*XG3`w2(Ed~MTSioXW@3DZ(m$fH^F;F_|`7k{r{9n2K! zlXI2UcXoyHln-6D*dq9cK(?=CagN?9GiNqb*ULs0w8b40yd0Vbo4lPrGrqqs%JK40{73 zRmTm>OtJOq6#;hg>WL)$VqLGoSTxZSy;0F7J_sgxw+c7+n)XWL4j!B%t@#`lxJ%pP zdIY_5z!KdB{jlOe{-Jzf%7yyqW$SSluGVr0il#_Y9{uBR0?)yM`OSxnOtV+Gh2ax8 zZwj4^K)Y>weeBg=@`&WVHjWB3uHGAV1&}czrO3#^EHLqkWi< zCFtA|>sBNGEa48R$J;R)B(7oHa;h&haYC7hP}N|YZ6fFgw^n~&u~LAF!(suV_Kdze zBYqwD{}SQ#EJ!axUPh{$AJGF4gFRhQO!0}B1zNO1hDux>6&M<7wc()IT!OuNCH|2O z;StCyzELIzG)@pBF~gR*`Oyf6x=a5P# zh(eckvQTPp&W4M_TKQISIaRV>8Qxm8$7~z2zt`;x#NX>zXaarr5D*dR4tOK@S2T8l z28LF)2BIdmcJ>Mmx*UYgY<64~NC1%sC5Q zIcB5K2hN+o7%UT+9*$Vg@ddX5>;B~I%GEwlht^0vL9zFZQ;B-9=8S?`jj}?-O1hYq0|=aZY-U7(`m_62QfOOLFAzngSzH2 z_2y*uh}i65MD2;ww8Z)X4TqL-N>&dhN}RwPwS{8!3Nz5O3sS6;!(dr#N&^0t;3*gM zK!GXB`C@}Tf`w)iYxgmR?Z_3?TI0BijKO7)D+pF4r3lI5cN)#|gZAnLtwm0^)Kj&J4JcF%j~qwCMHpIm5a~`Sj7_76b%lGWe2WvixpZ z4j}CGvd;uN7)jfuIqIYCe(fdq80xpdE@sdhhD%$5oZR^s+!$@5HJ8O)H~jQ{!;nL` z@M@*-Dpt~YE+%uo)iBPWM}s8Qu=YSGX=Dq=)Z7?^7srHwpm-kf+Y-9iZxBhv~kz$4Vtcqb~HNRbkC%f zKkO$v*VN`u*Y2e{M8frUerZI6-wo3!(#UX?CBf@1^Nl_BE5YB#kT@Xjl<$$%Ujbt@ z=dWY5f{UHKfyKY^DP+VBOLp_427a0^v8bz`HJuSzW)(yc?g_w%M#p$V|N6i)6@My* z6oq_Ba8**p7lxoS2${kJu}GjtEuHz|)6-~rQ>#~N`-{#O=1GJI5ya-GIcol+z~*S# zB7|N;ugE_^=`Y?#^Vbjei)Swy;jc=sAceYd*~Y&)vkT)AqE=;*?I}&K^zM|;f?28s z!{atqhi8V+A)IJKSr#M00@pfip2iUib8KQ)APz+VX2mhkg|_k-Y!u6$n0<({7nKQe zt>=vPb*Zne_f&PGq_VS?e#dU`vJzBEnHKI=QPp`cH}t9_-cT4!F&jt(I8Q7ax94@g z3aZc63rUM|W6$ccJ^~g_ffG~bvbu>%xUwm`ZesxLjL~#Tt|cPJ*M8QbC9Uk2qCTk8 zu^o1m5S&-*^25L(W4W8v*eTQ~33dkFr##Fm|2GiYzGXxD0HE?#!4nq^Zf#7Ep!=ZY})X&G8y`OLX^q zcbj44ytfdyfHy9S1${qIlgUhioAfjHz4WCuFV9C{YoLBn>?Jm)`LSHnEi6acX*w3j z6qU@&+&O>6HXA_(?vLYSrH(uY_}bwQ<@c2Lyl#>!?+Cx50N%c5?|ZH~kW(JLtyDO<uZne-ZbcCv< zF$2qdHyUPJhp=LxJ!9_Eq#g9O;s7^i?Kr9T>k|4vF5@E$oUAOA;wKcDO1F;z*o|jA zZMP9A$%&Sf2rz2CN?s^z*BUY~ZZ5~~jdMiiH41o6PuIL7@Apm#iCa}YzxbE+j;>H9 zn)|t7h8bHI&|4cI`KoPn(13hXWw?ZNhH{#>RsA9wz54CN77h)#`J-WN+|WQ(rW~+p zIWRf103$ecl{Eiix`FY&A=MS$?wkfg8sms4a%^YMBKD~drZpad?s^cV(A!9{x#QHT z(K8D}ZiNv_A6nyqQHf2d#J;?;lBp5g%nWAoE^Gg7t9g8f4Se zf+1BpQz^ZCL#Zk<@iwOin-VGx{2a|4Xbr^3{&kAY*S+wlO(Bj`320QQv@V9NtQt4R z8+Rk8nikHENB0MK!axzE(O{NIWV8w2VDV}J`ao-`&PwkkO=fDr&SYPq4A8#2u)I>ff5(8*wK=hy!I+0vl1#L1$6!Q*_bxf_$sV3t^Eu$ zN9Kagh_R+#*J-wDPVODMV6ok;Cr04PX=34faIRES6iNz8 zVHnJ`w^^WIe2#trdt~%wBcnS^t#`u}V@E2S-VfGCeT*>}%VT(eI*z&`Ig%;uMJ7I! zXL{VaP>Ht~GC3pJJM}JDd}e!213#USi5R2B<^n6T{&;>PvIKvr#u2=<_lpj#@9Kfw z#mGdwB3KRPU>y_=5?fe5C9!mWE?O*3Og_dzS*p&#jVOxM{k-iVxO1v0q7>H2mdh1a zqn7^~GF-GsGRV|8oVSI!L7J5yC64`Wnu9nSevFD-NbVq7yrPs!&F;y(NW2p`qL?J7 zjnA?*U4T1rEJZ!dslGs7g=XG*RwT0yjc6M7_U6*OFE+ z{+jo$cLZLqd@pE05USA{=w)`)dU7vV*#}Mx!TBN6jsQk)7-0%ZkkQM{L()N|+__i8 zuEAY%{1m#FFT6~95h>b|_O(8Xsl%e+V;F`MyxM&G_os5X_#>1UV2~F9d5~3msiZ5kEpCf!v0GR6j za{;Ei9~UCfH-S{Z_wRg2|C9po-t?Cg?LSidU{Ly}gaFRcza%{Pd%_i`QpXBPnndt(6kt%=@GX6NS?_l{NViUF3E03YJNS#$*C;Wri~{w%{EZ<){WK#N>g zGy!>)2c)L^8{RvBv-mf7VzxT^<_1at{&{gL3j=8@D>Db{-$UbWYAy8wK<)v(^!twR zpI5C91VD}JH_!mmUvmdLz`-(66LSLrJv{?EI~g5I9iu;`7M|yQ1WsG@JB9Wgz@hi| zyyF2xO@CuRQ#K-}_pAF!W|9#%&J@8C+09Aqo$UDQI83){JeeMu{3;n!3 z#Q|y&=Km|>`7R92%7Bam0cz*_kn!hL>qCqFTXbnFqyJe8W41@3Xn;TyOF)^wpNsnQ zs`cprXn_2t7XBuC_xs{=tk$pK03NYpz$ifYCm`Tn>w^jS`5R2YsX%)jbMxP$t#nF< z^Z+U|0WdKAy`5|T{o*%hf>u`c-*q>vWpqp|0Z!MSY8%hHfcR_!=@g)iNB~9su9@)X zRqNvo5dHfN^p7qe;%uPj@L&4$=cxYJc_q~VR6zhL^`DS{d#z83&~H)ycqo2gf6w&q zE8@B4?w`=LK5??Y#r;vO|7-Ypu5tJi&ra#Lcz{RQ^N+a=?#z43d! zJ=fs)iHch#(pKBfbw1i^(FD(5vQV38w>%S@=JU8{6YW}CGC)0m2^*@N` zpTj@rO#ca=VE#|=KU(6S7}TGedQQIj(^P}~zcBSjrTt&k^_;)*r;!Dxe_`aG*({%z z=sDfrPh%Kv|77g1+tJT4pOa1f#60o-C(IuW@(1)EKmM}2_uR^JR->O*4t;;I@*lKc zIgXxVK4(<;iP;tUE9SrFS9osc`6>UOb`ryXvGeQT@vqMOKeze(toBcv!cqUX&EF4K z&x2}zVxHyv-0rG8(6=ka4d4fGZMV&KnN;@^0FpO@r$ zyvI*NUZuYn`lY-6)8qDh5B(={K*cY}zjmfSpnt!k{=5*+_bz{$a<2X-Q~$?Y`h3go zC+vL9Kf(SOF#cf!?|A{9JNAEC!fpJ;()0I+ztr9TQK09}=AUNHntn0!7uX*K`s4Nw zW8ZVH<4=6i)_;NjzkQI;>+88A<)@+Lj{hzU|Cd|kwG \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/hal/gonk/GonkDiskSpaceWatcher.cpp b/hal/gonk/GonkDiskSpaceWatcher.cpp index 777619bc9f..048fccf137 100644 --- a/hal/gonk/GonkDiskSpaceWatcher.cpp +++ b/hal/gonk/GonkDiskSpaceWatcher.cpp @@ -7,17 +7,16 @@ #include #include #include +#include "base/message_loop.h" +#include "DiskSpaceWatcher.h" +#include "fanotify.h" #include "nsIObserverService.h" #include "nsIDiskSpaceWatcher.h" -#include "mozilla/ModuleUtils.h" -#include "nsAutoPtr.h" #include "nsThreadUtils.h" -#include "base/message_loop.h" +#include "nsXULAppAPI.h" +#include "mozilla/ModuleUtils.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" -#include "nsXULAppAPI.h" -#include "fanotify.h" -#include "DiskSpaceWatcher.h" using namespace mozilla; @@ -177,15 +176,17 @@ GonkDiskSpaceWatcher::DoStart() NS_ASSERTION(XRE_GetIOMessageLoop() == MessageLoopForIO::current(), "Not on the correct message loop"); - mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC); + mFd = fanotify_init(FAN_CLASS_NOTIF, FAN_CLOEXEC | O_LARGEFILE); if (mFd == -1) { if (errno == ENOSYS) { - NS_WARNING("Warning: No fanotify support in this device's kernel.\n"); + // Don't change these printf_stderr since we need these logs even + // in opt builds. + printf_stderr("Warning: No fanotify support in this device's kernel.\n"); #if ANDROID_VERSION >= 19 MOZ_CRASH("Fanotify support must be enabled in the kernel."); #endif } else { - NS_WARNING("Error calling fanotify_init()"); + printf_stderr("Error calling fanotify_init()"); } return; } diff --git a/hal/gonk/GonkHal.cpp b/hal/gonk/GonkHal.cpp index 05fe4a6c14..d395526185 100644 --- a/hal/gonk/GonkHal.cpp +++ b/hal/gonk/GonkHal.cpp @@ -1125,7 +1125,7 @@ EnableAlarm() return false; } - nsAutoPtr alarmData(new AlarmData(alarmFd)); + UniquePtr alarmData = MakeUnique(alarmFd); struct sigaction actions; memset(&actions, 0, sizeof(actions)); @@ -1144,7 +1144,7 @@ EnableAlarm() int status = pthread_create(&sAlarmFireWatcherThread, &attr, WaitForAlarm, alarmData.get()); if (status) { - alarmData = nullptr; + alarmData.reset(); HAL_LOG("Failed to create alarm-watcher thread. Status: %d.", status); return false; } @@ -1152,7 +1152,7 @@ EnableAlarm() pthread_attr_destroy(&attr); // The thread owns this now. We only hold a pointer. - sAlarmData = alarmData.forget(); + sAlarmData = alarmData.release(); return true; } @@ -1295,7 +1295,7 @@ OomVictimLogger::Observe( // deprecated the old klog defs. // Our current bionic does not hit this // change yet so handle the future change. - // (ICS doesn't have KLOG_SIZE_BUFFER but + // (ICS doesn't have KLOG_SIZE_BUFFER but // JB and onwards does.) #define KLOG_SIZE_BUFFER KLOG_WRITE #endif @@ -1461,7 +1461,7 @@ private: nsCString cgroupName = mGroup; /* If mGroup is empty, our cgroup.procs file is the root procs file, - * located at /sys/fs/cgroup/memory/cgroup.procs. Otherwise our procs + * located at /sys/fs/cgroup/memory/cgroup.procs. Otherwise our procs * file is /sys/fs/cgroup/memory/NAME/cgroup.procs. */ if (!mGroup.IsEmpty()) { diff --git a/hal/gonk/GonkSensorsInterface.cpp b/hal/gonk/GonkSensorsInterface.cpp index c6c7658065..51e1ff50c5 100644 --- a/hal/gonk/GonkSensorsInterface.cpp +++ b/hal/gonk/GonkSensorsInterface.cpp @@ -259,7 +259,7 @@ GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandl mResultHandlerQ.AppendElement(aRes); if (!mProtocol) { - mProtocol = new GonkSensorsProtocol(); + mProtocol = MakeUnique(); } if (!mListenSocket) { @@ -269,7 +269,7 @@ GonkSensorsInterface::Connect(GonkSensorsNotificationHandler* aNotificationHandl // Init, step 1: Listen for data channel... */ if (!mDataSocket) { - mDataSocket = new DaemonSocket(mProtocol, this, DATA_SOCKET); + mDataSocket = new DaemonSocket(mProtocol.get(), this, DATA_SOCKET); } else if (mDataSocket->GetConnectionStatus() == SOCKET_CONNECTED) { // Command channel should not be open; let's close it. mDataSocket->Close(); @@ -334,24 +334,24 @@ GonkSensorsRegistryInterface* GonkSensorsInterface::GetSensorsRegistryInterface() { if (mRegistryInterface) { - return mRegistryInterface; + return mRegistryInterface.get(); } - mRegistryInterface = new GonkSensorsRegistryInterface(mProtocol); + mRegistryInterface = MakeUnique(mProtocol.get()); - return mRegistryInterface; + return mRegistryInterface.get(); } GonkSensorsPollInterface* GonkSensorsInterface::GetSensorsPollInterface() { if (mPollInterface) { - return mPollInterface; + return mPollInterface.get(); } - mPollInterface = new GonkSensorsPollInterface(mProtocol); + mPollInterface = MakeUnique(mProtocol.get()); - return mPollInterface; + return mPollInterface.get(); } GonkSensorsInterface::GonkSensorsInterface() diff --git a/hal/gonk/GonkSensorsInterface.h b/hal/gonk/GonkSensorsInterface.h index 44126ce876..6e356dc364 100644 --- a/hal/gonk/GonkSensorsInterface.h +++ b/hal/gonk/GonkSensorsInterface.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "SensorsTypes.h" namespace mozilla { @@ -174,14 +175,14 @@ private: nsCString mListenSocketName; RefPtr mListenSocket; RefPtr mDataSocket; - nsAutoPtr mProtocol; + UniquePtr mProtocol; nsTArray > mResultHandlerQ; GonkSensorsNotificationHandler* mNotificationHandler; - nsAutoPtr mRegistryInterface; - nsAutoPtr mPollInterface; + UniquePtr mRegistryInterface; + UniquePtr mPollInterface; }; } // namespace hal diff --git a/hal/gonk/GonkSensorsPollInterface.cpp b/hal/gonk/GonkSensorsPollInterface.cpp index 010deb656a..d4edc2e7af 100644 --- a/hal/gonk/GonkSensorsPollInterface.cpp +++ b/hal/gonk/GonkSensorsPollInterface.cpp @@ -6,6 +6,7 @@ #include "GonkSensorsPollInterface.h" #include "HalLog.h" +#include namespace mozilla { namespace hal { @@ -124,18 +125,18 @@ GonkSensorsPollModule::EnableSensorCmd(int32_t aId, GonkSensorsPollResultHandler { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_ENABLE_SENSOR, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_ENABLE_SENSOR, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } @@ -144,18 +145,18 @@ GonkSensorsPollModule::DisableSensorCmd(int32_t aId, GonkSensorsPollResultHandle { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_DISABLE_SENSOR, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_DISABLE_SENSOR, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } @@ -165,8 +166,8 @@ GonkSensorsPollModule::SetPeriodCmd(int32_t aId, uint64_t aPeriod, { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_SET_PERIOD, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_SET_PERIOD, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { @@ -176,11 +177,11 @@ GonkSensorsPollModule::SetPeriodCmd(int32_t aId, uint64_t aPeriod, if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } diff --git a/hal/gonk/GonkSensorsPollInterface.h b/hal/gonk/GonkSensorsPollInterface.h index d25fed4f3e..89381a9bdb 100644 --- a/hal/gonk/GonkSensorsPollInterface.h +++ b/hal/gonk/GonkSensorsPollInterface.h @@ -273,7 +273,8 @@ private: class GonkSensorsPollInterface final { public: - friend class GonkSensorsInterface; + GonkSensorsPollInterface(GonkSensorsPollModule* aModule); + ~GonkSensorsPollInterface(); /** * This method sets the notification handler for poll notifications. Call @@ -326,11 +327,7 @@ public: */ void SetPeriod(int32_t aId, uint64_t aPeriod, GonkSensorsPollResultHandler* aRes); - ~GonkSensorsPollInterface(); - private: - GonkSensorsPollInterface(GonkSensorsPollModule* aModule); - void DispatchError(GonkSensorsPollResultHandler* aRes, SensorsError aError); void DispatchError(GonkSensorsPollResultHandler* aRes, nsresult aRv); diff --git a/hal/gonk/GonkSensorsRegistryInterface.cpp b/hal/gonk/GonkSensorsRegistryInterface.cpp index 0fc318fe2a..601dc7a2a6 100644 --- a/hal/gonk/GonkSensorsRegistryInterface.cpp +++ b/hal/gonk/GonkSensorsRegistryInterface.cpp @@ -7,6 +7,7 @@ #include "GonkSensorsRegistryInterface.h" #include "GonkSensorsHelpers.h" #include "HalLog.h" +#include namespace mozilla { namespace hal { @@ -80,18 +81,18 @@ GonkSensorsRegistryModule::RegisterModuleCmd( { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_REGISTER_MODULE, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_REGISTER_MODULE, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } @@ -101,18 +102,18 @@ GonkSensorsRegistryModule::UnregisterModuleCmd( { MOZ_ASSERT(NS_IsMainThread()); - nsAutoPtr pdu( - new DaemonSocketPDU(SERVICE_ID, OPCODE_UNREGISTER_MODULE, 0)); + UniquePtr pdu = + MakeUnique(SERVICE_ID, OPCODE_UNREGISTER_MODULE, 0); nsresult rv = PackPDU(aId, *pdu); if (NS_FAILED(rv)) { return rv; } - rv = Send(pdu, aRes); + rv = Send(pdu.get(), aRes); if (NS_FAILED(rv)) { return rv; } - Unused << pdu.forget(); + Unused << pdu.release(); return NS_OK; } diff --git a/hal/gonk/GonkSensorsRegistryInterface.h b/hal/gonk/GonkSensorsRegistryInterface.h index e7d64cb1d8..a9d98d653f 100644 --- a/hal/gonk/GonkSensorsRegistryInterface.h +++ b/hal/gonk/GonkSensorsRegistryInterface.h @@ -145,7 +145,8 @@ protected: class GonkSensorsRegistryInterface final { public: - friend class GonkSensorsInterface; + GonkSensorsRegistryInterface(GonkSensorsRegistryModule* aModule); + ~GonkSensorsRegistryInterface(); /** * Sends a RegisterModule command to the Sensors daemon. When the @@ -166,11 +167,7 @@ public: */ void UnregisterModule(uint8_t aId, GonkSensorsRegistryResultHandler* aRes); - ~GonkSensorsRegistryInterface(); - private: - GonkSensorsRegistryInterface(GonkSensorsRegistryModule* aModule); - void DispatchError(GonkSensorsRegistryResultHandler* aRes, SensorsError aError); void DispatchError(GonkSensorsRegistryResultHandler* aRes, diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 53bce6ab4d..277f4a6318 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -2174,6 +2174,11 @@ ScrollFrameHelper::ScrollToWithOrigin(nsPoint aScrollPosition, nsRect scrollRange = GetScrollRangeForClamping(); mDestination = scrollRange.ClampPoint(aScrollPosition); + if (mDestination != aScrollPosition && aOrigin == nsGkAtoms::restore) { + // If we're doing a restore but the scroll position is clamped, promote + // the origin from one that APZ can clobber to one that it can't clobber. + aOrigin = nsGkAtoms::other; + } nsRect range = aRange ? *aRange : nsRect(aScrollPosition, nsSize(0, 0)); @@ -2743,25 +2748,29 @@ ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOri usingDisplayPort, displayPort.IsEqualEdges(oldDisplayPort), mScrollableByAPZ, HasPluginFrames()); if (usingDisplayPort && displayPort.IsEqualEdges(oldDisplayPort)) { - if (LastScrollOrigin() == nsGkAtoms::apz) { - schedulePaint = false; - PAINT_SKIP_LOG("Skipping due to APZ scroll\n"); - } else if (mScrollableByAPZ && !HasPluginFrames()) { - nsIWidget* widget = presContext->GetNearestWidget(); - LayerManager* manager = widget ? widget->GetLayerManager() : nullptr; - if (manager) { - mozilla::layers::FrameMetrics::ViewID id; - DebugOnly success = nsLayoutUtils::FindIDFor(content, &id); - MOZ_ASSERT(success); // we have a displayport, we better have an ID - - // Schedule an empty transaction to carry over the scroll offset update, - // instead of a full transaction. This empty transaction might still get - // squashed into a full transaction if something happens to trigger one. + bool haveScrollLinkedEffects = content->GetComposedDoc()->HasScrollLinkedEffect(); + bool apzDisabled = haveScrollLinkedEffects && gfxPrefs::APZDisableForScrollLinkedEffects(); + if (!apzDisabled) { + if (LastScrollOrigin() == nsGkAtoms::apz) { schedulePaint = false; - manager->SetPendingScrollUpdateForNextTransaction(id, - { mScrollGeneration, CSSPoint::FromAppUnits(GetScrollPosition()) }); - mOuter->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY); - PAINT_SKIP_LOG("Skipping due to APZ-forwarded main-thread scroll\n"); + PAINT_SKIP_LOG("Skipping due to APZ scroll\n"); + } else if (mScrollableByAPZ && !HasPluginFrames()) { + nsIWidget* widget = presContext->GetNearestWidget(); + LayerManager* manager = widget ? widget->GetLayerManager() : nullptr; + if (manager) { + mozilla::layers::FrameMetrics::ViewID id; + DebugOnly success = nsLayoutUtils::FindIDFor(content, &id); + MOZ_ASSERT(success); // we have a displayport, we better have an ID + + // Schedule an empty transaction to carry over the scroll offset update, + // instead of a full transaction. This empty transaction might still get + // squashed into a full transaction if something happens to trigger one. + schedulePaint = false; + manager->SetPendingScrollUpdateForNextTransaction(id, + { mScrollGeneration, CSSPoint::FromAppUnits(GetScrollPosition()) }); + mOuter->SchedulePaint(nsIFrame::PAINT_COMPOSITE_ONLY); + PAINT_SKIP_LOG("Skipping due to APZ-forwarded main-thread scroll\n"); + } } } } diff --git a/layout/generic/nsGridContainerFrame.cpp b/layout/generic/nsGridContainerFrame.cpp index da78dd1bee..6f88a03df7 100644 --- a/layout/generic/nsGridContainerFrame.cpp +++ b/layout/generic/nsGridContainerFrame.cpp @@ -4022,14 +4022,17 @@ nsGridContainerFrame::LineRange::ToPositionAndLengthForAbsPos( // done } else { const nscoord endPos = *aPos + *aLength; - nscoord startPos = - aTracks.GridLineEdge(mStart, GridLineSide::eAfterGridGap); + auto side = mStart == aTracks.mSizes.Length() ? GridLineSide::eBeforeGridGap + : GridLineSide::eAfterGridGap; + nscoord startPos = aTracks.GridLineEdge(mStart, side); *aPos = aGridOrigin + startPos; *aLength = std::max(endPos - *aPos, 0); } } else { if (mStart == kAutoLine) { - nscoord endPos = aTracks.GridLineEdge(mEnd, GridLineSide::eBeforeGridGap); + auto side = mEnd == 0 ? GridLineSide::eAfterGridGap + : GridLineSide::eBeforeGridGap; + nscoord endPos = aTracks.GridLineEdge(mEnd, side); *aLength = std::max(aGridOrigin + endPos, 0); } else { nscoord pos; diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index fb2bc63674..15fc76ae09 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -643,6 +643,29 @@ nsHTMLReflowState::InitResizeFlags(nsPresContext* aPresContext, nsIAtom* aFrameT // Possibly; in that case we should at least be checking // NS_SUBTREE_DIRTY, I'd think. SetBResize(mCBReflowState->IsBResize()); + } else if (mCBReflowState && !nsLayoutUtils::GetAsBlock(frame)) { + // Some non-block frames (e.g. table frames) aggressively optimize out their + // BSize recomputation when they don't have the BResize flag set. This + // means that if they go from having a computed non-auto height to having an + // auto height and don't have that flag set, they will not actually compute + // their auto height and will just remain at whatever size they already + // were. We can end up in that situation if the child has a percentage + // specified height and the parent changes from non-auto height to auto + // height. When that happens, the parent will typically have the BResize + // flag set, and we want to propagate that flag to the kid. + // + // Ideally it seems like we'd do this for blocks too, of course... but we'd + // really want to restrict it to the percentage height case or something, to + // avoid extra reflows in common cases. Maybe we should be examining + // mStylePosition->BSize(wm).GetUnit() for that purpose? + // + // Note that we _also_ need to set the BResize flag if we have auto + // ComputedBSize() and a dirty subtree, since that might require us to + // change BSize due to kids having been added or removed. + SetBResize(mCBReflowState->IsBResize()); + if (ComputedBSize() == NS_AUTOHEIGHT) { + SetBResize(IsBResize() || NS_SUBTREE_DIRTY(frame)); + } } else if (ComputedBSize() == NS_AUTOHEIGHT) { if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && mCBReflowState) { diff --git a/layout/reftests/bugs/1263845-ref.html b/layout/reftests/bugs/1263845-ref.html new file mode 100644 index 0000000000..65c67a95a7 --- /dev/null +++ b/layout/reftests/bugs/1263845-ref.html @@ -0,0 +1,10 @@ + +
+ + + + +
+ This is some text +
+
diff --git a/layout/reftests/bugs/1263845.html b/layout/reftests/bugs/1263845.html new file mode 100644 index 0000000000..3bc9c28b99 --- /dev/null +++ b/layout/reftests/bugs/1263845.html @@ -0,0 +1,15 @@ + +
+ + + + +
+ This is some text +
+
+ diff --git a/layout/reftests/bugs/reftest.list b/layout/reftests/bugs/reftest.list index ca9a4e8f64..8f34f7c6cf 100644 --- a/layout/reftests/bugs/reftest.list +++ b/layout/reftests/bugs/reftest.list @@ -1951,4 +1951,5 @@ random-if(OSX==1006) == 1238243-2.html 1238243-2-ref.html # fails on 10.6 with d fuzzy(100,2000) == 1239564.html 1239564-ref.html == 1242172-1.html 1242172-1-ref.html == 1242172-2.html 1242172-2-ref.html +== 1263845.html 1263845-ref.html == 1260543-1.html 1260543-1-ref.html diff --git a/layout/reftests/canvas/reftest.list b/layout/reftests/canvas/reftest.list index 6672e62dac..4a10279130 100644 --- a/layout/reftests/canvas/reftest.list +++ b/layout/reftests/canvas/reftest.list @@ -28,7 +28,7 @@ random-if(cocoaWidget||(Android&&AndroidVersion==15)) == subpixel-1.html about:b == text-rtl-alignment-test.html text-rtl-alignment-ref.html fuzzy-if((B2G||Mulet)&&azureSkiaGL,1,256) == text-horzline-with-bottom.html text-horzline.html # Initial mulet triage: parity with B2G/B2G Desktop -fuzzy-if((B2G||Mulet)&&azureSkiaGL,1,256) fails-if(azureSkia&&OSX>=1008) == text-horzline-with-top.html text-horzline.html # Initial mulet triage: parity with B2G/B2G Desktop, Skia OS X failure tracked in bug 1152044 +fuzzy-if((B2G||Mulet)&&azureSkiaGL,1,256) fails-if(azureSkia&&OSX>=1008) == text-horzline-with-top.html text-horzline.html # Initial mulet triage: parity with B2G/B2G Desktop != text-big-stroke.html text-blank.html != text-big-stroke.html text-big-fill.html diff --git a/layout/reftests/css-grid/grid-abspos-items-013-ref.html b/layout/reftests/css-grid/grid-abspos-items-013-ref.html index 9104efb42a..3e4ef381b1 100644 --- a/layout/reftests/css-grid/grid-abspos-items-013-ref.html +++ b/layout/reftests/css-grid/grid-abspos-items-013-ref.html @@ -96,7 +96,7 @@ x:nth-of-type(4) { left:365px; }
- +
@@ -106,12 +106,12 @@ x:nth-of-type(4) { left:365px; }
- +
- +
@@ -126,7 +126,7 @@ x:nth-of-type(4) { left:365px; }
- +
diff --git a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html index 3c9999f850..3bc177eb27 100644 --- a/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html +++ b/layout/reftests/css-grid/grid-placement-abspos-implicit-001-ref.html @@ -98,7 +98,7 @@ span {
- +
diff --git a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html index 81ba8aff8f..5f3cebf6a7 100644 --- a/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html +++ b/layout/reftests/css-grid/grid-repeat-auto-fill-fit-007-ref.html @@ -100,7 +100,7 @@ float { float:left; margin-right:20px; } .x2 { grid-template-columns: repeat(2,20px); } .t1.x5 a { grid-column:5/auto; } -.c2.t1.x5 a , .c3.t1.x5 a { display:none; } +.c2.t1.x5 a , .c3.t1.x5 a { grid-column-start:-2; left:-2px; } .c1.t1.x4 a { grid-column:4/auto; } .c1.t1.x3 a { grid-column:3/auto; } .c1.t1.x2 a { grid-column:2/auto; } diff --git a/netwerk/base/Predictor.cpp b/netwerk/base/Predictor.cpp index a48912ff05..3197202dc9 100644 --- a/netwerk/base/Predictor.cpp +++ b/netwerk/base/Predictor.cpp @@ -2375,7 +2375,8 @@ Predictor::UpdateCacheability(nsIURI *sourceURI, nsIURI *targetURI, RefPtr self = sSelf; if (self) { - const nsCString method = requestHead.Method(); + nsAutoCString method; + requestHead.Method(method); self->UpdateCacheabilityInternal(sourceURI, targetURI, httpStatus, method); } diff --git a/netwerk/base/ProxyAutoConfig.cpp b/netwerk/base/ProxyAutoConfig.cpp index c91f475c7c..19b0597235 100644 --- a/netwerk/base/ProxyAutoConfig.cpp +++ b/netwerk/base/ProxyAutoConfig.cpp @@ -109,7 +109,8 @@ static const char *sPacUtils = " var wd1 = getDay(arguments[0]);\n" " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" " return (wd1 == -1 || wd2 == -1) ? false\n" - " : (wd1 <= wday && wday <= wd2);\n" + " : (wd1 <= wd2) ? (wd1 <= wday && wday <= wd2)\n" + " : (wd2 >= wday || wday >= wd1);\n" "}\n" "" "function dateRange() {\n" @@ -184,7 +185,8 @@ static const char *sPacUtils = " tmp.setSeconds(date.getUTCSeconds());\n" " date = tmp;\n" " }\n" - " return ((date1 <= date) && (date <= date2));\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" "}\n" "" "function timeRange() {\n" @@ -237,7 +239,9 @@ static const char *sPacUtils = " date.setMinutes(date.getUTCMinutes());\n" " date.setSeconds(date.getUTCSeconds());\n" " }\n" - " return ((date1 <= date) && (date <= date2));\n" + " return (date1 <= date2) ? (date1 <= date) && (date <= date2)\n" + " : (date2 >= date) || (date >= date1);\n" + "\n" "}\n" ""; diff --git a/netwerk/base/ReferrerPolicy.h b/netwerk/base/ReferrerPolicy.h index bf8e92047e..efd09136ad 100644 --- a/netwerk/base/ReferrerPolicy.h +++ b/netwerk/base/ReferrerPolicy.h @@ -28,7 +28,7 @@ enum ReferrerPolicy { RP_Unsafe_URL = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL, /* referrer policy is not set */ - RP_Unset = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE + RP_Unset = nsIHttpChannel::REFERRER_POLICY_UNSET, }; /* spec tokens: never no-referrer */ @@ -96,10 +96,10 @@ IsValidReferrerPolicy(const nsAString& content) inline bool IsValidAttributeReferrerPolicy(const nsAString& aContent) { - // Spec allows only these three policies at the moment - // See bug 1178337 return aContent.LowerCaseEqualsLiteral(kRPS_No_Referrer) || aContent.LowerCaseEqualsLiteral(kRPS_Origin) + || aContent.LowerCaseEqualsLiteral(kRPS_No_Referrer_When_Downgrade) + || aContent.LowerCaseEqualsLiteral(kRPS_Origin_When_Cross_Origin) || aContent.LowerCaseEqualsLiteral(kRPS_Unsafe_URL); } diff --git a/netwerk/protocol/http/ConnectionDiagnostics.cpp b/netwerk/protocol/http/ConnectionDiagnostics.cpp index d1ab03e843..184adf95d2 100644 --- a/netwerk/protocol/http/ConnectionDiagnostics.cpp +++ b/netwerk/protocol/http/ConnectionDiagnostics.cpp @@ -236,8 +236,9 @@ nsHttpTransaction::PrintDiagnostics(nsCString &log) if (!mRequestHead) return; - log.AppendPrintf(" ::: uri = %s\n", - nsAutoCString(mRequestHead->RequestURI()).get()); + nsAutoCString requestURI; + mRequestHead->RequestURI(requestURI); + log.AppendPrintf(" ::: uri = %s\n", requestURI.get()); log.AppendPrintf(" caps = 0x%x\n", mCaps); log.AppendPrintf(" priority = %d\n", mPriority); log.AppendPrintf(" restart count = %u\n", mRestartCount); diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index 724bff95c2..c15ae65bd2 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -2126,7 +2126,7 @@ Http2Session::RecvAltSvc(Http2Session *self) return NS_OK; } - origin.Assign(self->mInputFrameDataStream->Transaction()->RequestHead()->Origin()); + self->mInputFrameDataStream->Transaction()->RequestHead()->Origin(origin); } else if (!self->mInputFrameID) { // ID 0 streams must supply their own origin if (origin.IsEmpty()) { diff --git a/netwerk/protocol/http/Http2Stream.cpp b/netwerk/protocol/http/Http2Stream.cpp index 7c7993c55d..bad33199db 100644 --- a/netwerk/protocol/http/Http2Stream.cpp +++ b/netwerk/protocol/http/Http2Stream.cpp @@ -397,7 +397,7 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, this, avail, mUpstreamState)); mFlatHttpRequestHeaders.Append(buf, avail); - const nsHttpRequestHead *head = mTransaction->RequestHead(); + nsHttpRequestHead *head = mTransaction->RequestHead(); // We can use the simple double crlf because firefox is the // only client we are parsing @@ -424,9 +424,11 @@ Http2Stream::ParseHttpRequestHeaders(const char *buf, nsAutoCString hashkey; head->GetHeader(nsHttp::Host, authorityHeader); + nsAutoCString requestURI; + head->RequestURI(requestURI); CreatePushHashKey(nsDependentCString(head->IsHTTPS() ? "https" : "http"), authorityHeader, mSession->Serial(), - head->RequestURI(), + requestURI, mOrigin, hashkey); // check the push cache for GET @@ -496,9 +498,11 @@ Http2Stream::GenerateOpen() mOpenGenerated = 1; - const nsHttpRequestHead *head = mTransaction->RequestHead(); + nsHttpRequestHead *head = mTransaction->RequestHead(); + nsAutoCString requestURI; + head->RequestURI(requestURI); LOG3(("Http2Stream %p Stream ID 0x%X [session=%p] for URI %s\n", - this, mStreamID, mSession, nsCString(head->RequestURI()).get())); + this, mStreamID, mSession, requestURI.get())); if (mStreamID >= 0x80000000) { // streamID must fit in 31 bits. Evading This is theoretically possible @@ -538,9 +542,13 @@ Http2Stream::GenerateOpen() authorityHeader.AppendInt(ci->OriginPort()); } + nsAutoCString method; + nsAutoCString path; + head->Method(method); + head->Path(path); mSession->Compressor()->EncodeHeaderBlock(mFlatHttpRequestHeaders, - head->Method(), - head->Path(), + method, + path, authorityHeader, scheme, head->IsConnect(), @@ -606,7 +614,7 @@ Http2Stream::GenerateOpen() LOG3(("Http2Stream %p Generating %d bytes of HEADERS for stream 0x%X with " "priority weight %u dep 0x%X frames %u uri=%s\n", this, mTxInlineFrameUsed, mStreamID, mPriorityWeight, - mPriorityDependency, numFrames, nsCString(head->RequestURI()).get())); + mPriorityDependency, numFrames, requestURI.get())); uint32_t outputOffset = 0; uint32_t compressedDataOffset = 0; @@ -650,7 +658,7 @@ Http2Stream::GenerateOpen() // The size of the input headers is approximate uint32_t ratio = compressedData.Length() * 100 / - (11 + head->RequestURI().Length() + + (11 + requestURI.Length() + mFlatHttpRequestHeaders.Length()); mFlatHttpRequestHeaders.Truncate(); diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index b55872ec45..ee9b1cfa7b 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -52,6 +52,7 @@ #include "nsIURL.h" #include "nsIConsoleService.h" #include "mozilla/BinarySearch.h" +#include "nsIHttpHeaderVisitor.h" #include @@ -189,7 +190,7 @@ HttpBaseChannel::Init(nsIURI *aURI, rv = mRequestHead.SetHeader(nsHttp::Host, hostLine); if (NS_FAILED(rv)) return rv; - rv = gHttpHandler->AddStandardRequestHeaders(&mRequestHead.Headers(), isHTTPS); + rv = gHttpHandler->AddStandardRequestHeaders(&mRequestHead, isHTTPS); if (NS_FAILED(rv)) return rv; nsAutoCString type; @@ -1172,7 +1173,7 @@ HttpBaseChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize) NS_IMETHODIMP HttpBaseChannel::GetRequestMethod(nsACString& aMethod) { - aMethod = mRequestHead.Method(); + mRequestHead.Method(aMethod); return NS_OK; } @@ -1601,14 +1602,14 @@ HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) NS_IMETHODIMP HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor) { - return mRequestHead.Headers().VisitHeaders(visitor); + return mRequestHead.VisitHeaders(visitor); } NS_IMETHODIMP HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor) { - return mRequestHead.Headers().VisitHeaders( - visitor, nsHttpHeaderArray::eFilterSkipDefault); + return mRequestHead.VisitHeaders(visitor, + nsHttpHeaderArray::eFilterSkipDefault); } NS_IMETHODIMP @@ -2728,6 +2729,35 @@ bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) HttpAtomComparator(aHeader), &unused); } +class SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor +{ +public: + NS_DECL_ISUPPORTS + + explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel) + : mChannel(aChannel) + { + } + + NS_IMETHOD VisitHeader(const nsACString& aHeader, + const nsACString& aValue) override + { + nsHttpAtom atom = nsHttp::ResolveAtom(aHeader); + if (!IsHeaderBlacklistedForRedirectCopy(atom)) { + mChannel->SetRequestHeader(aHeader, aValue, false); + } + return NS_OK; + } +private: + ~SetupReplacementChannelHeaderVisitor() + { + } + + nsCOMPtr mChannel; +}; + +NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor) + nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, nsIChannel *newChannel, @@ -2806,31 +2836,35 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, // replicate original call to SetUploadStream... if (uploadChannel2) { - const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type); - if (!ctype) - ctype = ""; - const char *clen = mRequestHead.PeekHeader(nsHttp::Content_Length); - int64_t len = clen ? nsCRT::atoll(clen) : -1; + nsAutoCString ctype; + // If header is not present mRequestHead.HasHeaderValue will truncated + // it. + mRequestHead.GetHeader(nsHttp::Content_Type, ctype); + nsAutoCString clen; + mRequestHead.GetHeader(nsHttp::Content_Length, clen); + nsAutoCString method; + mRequestHead.Method(method); + int64_t len = clen.IsEmpty() ? -1 : nsCRT::atoll(clen.get()); uploadChannel2->ExplicitSetUploadStream( - mUploadStream, nsDependentCString(ctype), len, - mRequestHead.Method(), + mUploadStream, ctype, len, + method, mUploadStreamHasHeaders); } else { if (mUploadStreamHasHeaders) { uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1); } else { - const char *ctype = - mRequestHead.PeekHeader(nsHttp::Content_Type); - const char *clen = - mRequestHead.PeekHeader(nsHttp::Content_Length); - if (!ctype) { - ctype = "application/octet-stream"; + nsAutoCString ctype; + if (NS_FAILED(mRequestHead.GetHeader(nsHttp::Content_Type, ctype))) { + ctype = NS_LITERAL_CSTRING("application/octet-stream"); } - if (clen) { + nsAutoCString clen; + if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Content_Length, clen)) + && + !clen.IsEmpty()) { uploadChannel->SetUploadStream(mUploadStream, - nsDependentCString(ctype), - nsCRT::atoll(clen)); + ctype, + nsCRT::atoll(clen.get())); } } } @@ -2840,7 +2874,9 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, // we set the upload stream above. This means SetRequestMethod() will // be called twice if ExplicitSetUploadStream() gets called above. - httpChannel->SetRequestMethod(mRequestHead.Method()); + nsAutoCString method; + mRequestHead.Method(method); + httpChannel->SetRequestMethod(method); } // convey the referrer if one was used for this channel to the next one if (mReferrer) @@ -2970,18 +3006,9 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL | nsIChannelEventSink::REDIRECT_STS_UPGRADE)) { // Copy non-origin related headers to the new channel. - nsHttpHeaderArray& requestHeaders = mRequestHead.Headers(); - uint32_t requestHeaderCount = requestHeaders.Count(); - for (uint32_t i = 0; i < requestHeaderCount; ++i) { - nsHttpAtom header; - const char *val = requestHeaders.PeekHeaderAt(i, header); - if (!val || IsHeaderBlacklistedForRedirectCopy(header)) { - continue; - } - - httpChannel->SetRequestHeader(nsDependentCString(header.get()), - nsDependentCString(val), false); - } + nsCOMPtr visitor = + new SetupReplacementChannelHeaderVisitor(httpChannel); + mRequestHead.VisitHeaders(visitor); } // This channel has been redirected. Don't report timing info. diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index da2936c7f1..f54dbe9dfb 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -451,7 +451,7 @@ HttpChannelChild::OnStartRequest(const nsresult& channelStatus, mCacheKey = container; // replace our request headers with what actually got sent in the parent - mRequestHead.Headers() = requestHeaders; + mRequestHead.SetHeaders(requestHeaders); // Note: this is where we would notify "http-on-examine-response" observers. // We have deliberately disabled this for child processes (see bug 806753) @@ -1741,9 +1741,9 @@ HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext) if (NS_FAILED(rv)) return rv; - const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie); - if (cookieHeader) { - mUserSetCookieHeader = cookieHeader; + nsAutoCString cookie; + if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) { + mUserSetCookieHeader = cookie; } AddCookiesToRequest(); @@ -1854,7 +1854,7 @@ HttpChannelChild::ContinueAsyncOpen() SerializeURI(mAPIRedirectToURI, openArgs.apiRedirectTo()); openArgs.loadFlags() = mLoadFlags; openArgs.requestHeaders() = mClientSetRequestHeaders; - openArgs.requestMethod() = mRequestHead.Method(); + mRequestHead.Method(openArgs.requestMethod()); nsTArray fds; SerializeInputStream(mUploadStream, openArgs.uploadStream(), fds); diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp index a8c14c1ebf..c6bed6cd92 100644 --- a/netwerk/protocol/http/HttpChannelParent.cpp +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -1057,6 +1057,9 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) } } + // !!! We need to lock headers and please don't forget to unlock them !!! + requestHead->Lock(); + nsresult rv = NS_OK; if (mIPCClosed || !SendOnStartRequest(channelStatus, responseHead ? *responseHead : nsHttpResponseHead(), @@ -1069,9 +1072,10 @@ HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) redirectCount, cacheKeyValue)) { - return NS_ERROR_UNEXPECTED; + rv = NS_ERROR_UNEXPECTED; } - return NS_OK; + requestHead->Unlock(); + return rv; } NS_IMETHODIMP diff --git a/netwerk/protocol/http/SpdyStream31.cpp b/netwerk/protocol/http/SpdyStream31.cpp index 75ff4f0e29..247f965c70 100644 --- a/netwerk/protocol/http/SpdyStream31.cpp +++ b/netwerk/protocol/http/SpdyStream31.cpp @@ -296,10 +296,11 @@ SpdyStream31::ParseHttpRequestHeaders(const char *buf, nsAutoCString hostHeader; nsAutoCString hashkey; mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader); - + nsAutoCString requestURI; + mTransaction->RequestHead()->RequestURI(requestURI); CreatePushHashKey(nsDependentCString(mTransaction->RequestHead()->IsHTTPS() ? "https" : "http"), hostHeader, mSession->Serial(), - mTransaction->RequestHead()->RequestURI(), + requestURI, mOrigin, hashkey); // check the push cache for GET @@ -418,7 +419,7 @@ SpdyStream31::GenerateSynFrame() // even though we are parsing the actual text stream because // it is legit to append headers. nsClassHashtable - hdrHash(mTransaction->RequestHead()->Headers().Count()); + hdrHash(mTransaction->RequestHead()->HeaderCount()); const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading(); @@ -482,8 +483,9 @@ SpdyStream31::GenerateSynFrame() // contain auth. The http transaction already logs the sanitized request // headers at this same level so it is not necessary to do so here. - const char *methodHeader = mTransaction->RequestHead()->Method().get(); - LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, methodHeader)); + nsAutoCString method; + mTransaction->RequestHead()->Method(method); + LOG3(("Stream method %p 0x%X %s\n", this, mStreamID, method.get())); // The header block length uint16_t count = hdrHash.Count() + 4; /* :method, :path, :version, :host */ @@ -497,11 +499,13 @@ SpdyStream31::GenerateSynFrame() // :method, :path, :version comprise a HTTP/1 request line, so send those first // to make life easy for any gateways CompressToFrame(NS_LITERAL_CSTRING(":method")); - CompressToFrame(methodHeader, strlen(methodHeader)); + CompressToFrame(method); CompressToFrame(NS_LITERAL_CSTRING(":path")); if (!mTransaction->RequestHead()->IsConnect()) { - CompressToFrame(mTransaction->RequestHead()->Path()); + nsAutoCString path; + mTransaction->RequestHead()->Path(path); + CompressToFrame(path); } else { MOZ_ASSERT(mTransaction->QuerySpdyConnectTransaction()); mIsTunnel = true; @@ -568,11 +572,12 @@ SpdyStream31::GenerateSynFrame() mTxInlineFrame[4] = SpdySession31::kFlag_Data_FIN; } + nsAutoCString requestURI; + mTransaction->RequestHead()->RequestURI(requestURI); // The size of the input headers is approximate uint32_t ratio = (mTxInlineFrameUsed - 18) * 100 / - (11 + mTransaction->RequestHead()->RequestURI().Length() + - mFlatHttpRequestHeaders.Length()); + (11 + requestURI.Length() + mFlatHttpRequestHeaders.Length()); return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 3ed350d1ba..49834643c4 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -702,9 +702,11 @@ nsHttpChannel::SetupTransaction() // (4) request method is non-idempotent // (5) request is marked slow (e.g XHR) // + nsAutoCString method; + mRequestHead.Method(method); if (!mAllowPipelining || (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) || - !SafeForPipelining(mRequestHead.ParsedMethod(), mRequestHead.Method())) { + !SafeForPipelining(mRequestHead.ParsedMethod(), method)) { LOG((" pipelining disallowed\n")); mCaps &= ~NS_HTTP_ALLOW_PIPELINING; } @@ -2359,7 +2361,7 @@ nsHttpChannel::ResolveProxy() } bool -nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) const +nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) { nsresult rv; nsAutoCString buf, metaKey; @@ -2403,33 +2405,37 @@ nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) const // Look for value of "Cookie" in the request headers nsHttpAtom atom = nsHttp::ResolveAtom(token); - const char *newVal = mRequestHead.PeekHeader(atom); + nsAutoCString newVal; + bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, + newVal)); if (!lastVal.IsEmpty()) { // value for this header in cache, but no value in request - if (!newVal) + if (!hasHeader) { return true; // yes - response would vary + } // If this is a cookie-header, stored metadata is not // the value itself but the hash. So we also hash the // outgoing value here in order to compare the hashes nsAutoCString hash; if (atom == nsHttp::Cookie) { - rv = Hash(newVal, hash); + rv = Hash(newVal.get(), hash); // If hash failed, be conservative (the cached hash // exists at this point) and claim response would vary if (NS_FAILED(rv)) return true; - newVal = hash.get(); + newVal = hash; LOG(("nsHttpChannel::ResponseWouldVary [this=%p] " \ "set-cookie value hashed to %s\n", - this, newVal)); + this, newVal.get())); } - if (strcmp(newVal, lastVal)) + if (!newVal.Equals(lastVal)) { return true; // yes, response would vary + } - } else if (newVal) { // old value is empty, but newVal is set + } else if (hasHeader) { // old value is empty, but newVal is set return true; } @@ -2497,10 +2503,11 @@ nsHttpChannel::EnsureAssocReq() return NS_OK; // check the method - int32_t methodlen = strlen(mRequestHead.Method().get()); - if ((methodlen != (endofmethod - method)) || + nsAutoCString methodHead; + mRequestHead.Method(methodHead); + if ((((int32_t)methodHead.Length()) != (endofmethod - method)) || PL_strncmp(method, - mRequestHead.Method().get(), + methodHead.get(), endofmethod - method)) { LOG((" Assoc-Req failure Method %s", method)); if (mConnectionInfo) @@ -2518,7 +2525,7 @@ nsHttpChannel::EnsureAssocReq() mResponseHead->PeekHeader(nsHttp::Assoc_Req), message); message += NS_LITERAL_STRING(" expected method "); - AppendASCIItoUTF16(mRequestHead.Method().get(), message); + AppendASCIItoUTF16(methodHead, message); consoleService->LogStringMessage(message.get()); } @@ -3010,10 +3017,10 @@ nsHttpChannel::ContinueProcessFallback(nsresult rv) static bool IsSubRangeRequest(nsHttpRequestHead &aRequestHead) { - if (!aRequestHead.PeekHeader(nsHttp::Range)) - return false; nsAutoCString byteRange; - aRequestHead.GetHeader(nsHttp::Range, byteRange); + if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) { + return false; + } return !byteRange.EqualsLiteral("bytes=0-"); } @@ -3283,11 +3290,11 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC // Remember the request is a custom conditional request so that we can // process any 304 response correctly. mCustomConditionalRequest = - mRequestHead.PeekHeader(nsHttp::If_Modified_Since) || - mRequestHead.PeekHeader(nsHttp::If_None_Match) || - mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) || - mRequestHead.PeekHeader(nsHttp::If_Match) || - mRequestHead.PeekHeader(nsHttp::If_Range); + mRequestHead.HasHeader(nsHttp::If_Modified_Since) || + mRequestHead.HasHeader(nsHttp::If_None_Match) || + mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) || + mRequestHead.HasHeader(nsHttp::If_Match) || + mRequestHead.HasHeader(nsHttp::If_Range); // Be pessimistic: assume the cache entry has no useful data. *aResult = ENTRY_WANTED; @@ -3517,13 +3524,13 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC doValidation = true; } - if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) && + nsAutoCString requestedETag; + if (!doValidation && + NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) && (methodWasGet || methodWasHead)) { - const char *requestedETag, *cachedETag; - cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag); - requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match); + const char *cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag); if (cachedETag && (!strncmp(cachedETag, "W/", 2) || - strcmp(requestedETag, cachedETag))) { + !requestedETag.Equals(cachedETag))) { // User has defined If-Match header, if the cached entry is not // matching the provided header value or the cached ETag is weak, // force validation. @@ -3546,7 +3553,7 @@ nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appC entry->GetMetaDataElement("auth", getter_Copies(buf)); doValidation = (fromPreviousSession && !buf.IsEmpty()) || - (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization)); + (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization)); } // Bug #561276: We maintain a chain of cache-keys which returns cached @@ -4454,8 +4461,9 @@ DoAddCacheEntryHeaders(nsHttpChannel *self, // Store the HTTP request method with the cache entry so we can distinguish // for example GET and HEAD responses. - rv = entry->SetMetaDataElement("request-method", - requestHead->Method().get()); + nsAutoCString method; + requestHead->Method(method); + rv = entry->SetMetaDataElement("request-method", method.get()); if (NS_FAILED(rv)) return rv; // Store the HTTP authorization scheme used if any... @@ -4486,27 +4494,28 @@ DoAddCacheEntryHeaders(nsHttpChannel *self, "processing %s", self, token)); if (*token != '*') { nsHttpAtom atom = nsHttp::ResolveAtom(token); - const char *val = requestHead->PeekHeader(atom); + nsAutoCString val; nsAutoCString hash; - if (val) { + if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) { // If cookie-header, store a hash of the value if (atom == nsHttp::Cookie) { LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \ - "cookie-value %s", self, val)); - rv = Hash(val, hash); + "cookie-value %s", self, val.get())); + rv = Hash(val.get(), hash); // If hash failed, store a string not very likely // to be the result of subsequent hashes - if (NS_FAILED(rv)) - val = ""; - else - val = hash.get(); + if (NS_FAILED(rv)) { + val = NS_LITERAL_CSTRING(""); + } else { + val = hash; + } - LOG((" hashed to %s\n", val)); + LOG((" hashed to %s\n", val.get())); } // build cache meta data key and set meta data element... metaKey = prefix + nsDependentCString(token); - entry->SetMetaDataElement(metaKey.get(), val); + entry->SetMetaDataElement(metaKey.get(), val.get()); } else { LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \ "clearing metadata for %s", self, token)); @@ -4555,13 +4564,14 @@ nsresult StoreAuthorizationMetaData(nsICacheEntry *entry, nsHttpRequestHead *requestHead) { // Not applicable to proxy authorization... - const char *val = requestHead->PeekHeader(nsHttp::Authorization); - if (!val) + nsAutoCString val; + if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) { return NS_OK; + } // eg. [Basic realm="wally world"] nsAutoCString buf; - GetAuthType(val, buf); + GetAuthType(val.get(), buf); return entry->SetMetaDataElement("auth", buf.get()); } @@ -5195,8 +5205,8 @@ nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) } // Remember the cookie header that was set, if any - const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie); - if (cookieHeader) { + nsAutoCString cookieHeader; + if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) { mUserSetCookieHeader = cookieHeader; } @@ -5229,7 +5239,7 @@ nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) // Remember we have Authorization header set here. We need to check on it // just once and early, AsyncOpen is the best place. - mCustomAuthHeader = !!mRequestHead.PeekHeader(nsHttp::Authorization); + mCustomAuthHeader = mRequestHead.HasHeader(nsHttp::Authorization); // the only time we would already know the proxy information at this // point would be if we were proxying a non-http protocol like ftp @@ -5442,7 +5452,7 @@ nsHttpChannel::BeginConnect() } // if this somehow fails we can go on without it - gHttpHandler->AddConnectionHeader(&mRequestHead.Headers(), mCaps); + gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps); if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags)) mCaps |= NS_HTTP_REFRESH_DNS; diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index 73ec19fbed..52c18b691b 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -418,7 +418,7 @@ private: void UpdateAggregateCallbacks(); static bool HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri); - bool ResponseWouldVary(nsICacheEntry* entry) const; + bool ResponseWouldVary(nsICacheEntry* entry); bool IsResumable(int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen = false) const; nsresult MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength, diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index cf3f12dd60..84eff8fcaf 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -1133,21 +1133,25 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, } } - const char *upgradeReq = requestHead->PeekHeader(nsHttp::Upgrade); + nsAutoCString upgradeReq; + bool hasUpgradeReq = NS_SUCCEEDED(requestHead->GetHeader(nsHttp::Upgrade, + upgradeReq)); // Don't use persistent connection for Upgrade unless there's an auth failure: // some proxies expect to see auth response on persistent connection. - if (upgradeReq && responseStatus != 401 && responseStatus != 407) { + if (hasUpgradeReq && responseStatus != 401 && responseStatus != 407) { LOG(("HTTP Upgrade in play - disable keepalive\n")); DontReuse(); } if (responseStatus == 101) { const char *upgradeResp = responseHead->PeekHeader(nsHttp::Upgrade); - if (!upgradeReq || !upgradeResp || - !nsHttp::FindToken(upgradeResp, upgradeReq, + if (!hasUpgradeReq || !upgradeResp || + !nsHttp::FindToken(upgradeResp, upgradeReq.get(), HTTP_HEADER_VALUE_SEPS)) { LOG(("HTTP 101 Upgrade header mismatch req = %s, resp = %s\n", - upgradeReq, upgradeResp)); + upgradeReq.get(), + upgradeResp ? upgradeResp : + "RESPONSE's nsHttp::Upgrade is empty")); Close(NS_ERROR_ABORT); } else { @@ -1968,11 +1972,13 @@ nsHttpConnection::MakeConnectString(nsAHttpTransaction *trans, // may seem redundant in this case; see bug 82388). request->SetHeader(nsHttp::Host, result); - const char *val = trans->RequestHead()->PeekHeader(nsHttp::Proxy_Authorization); - if (val) { + nsAutoCString val; + if (NS_SUCCEEDED(trans->RequestHead()->GetHeader( + nsHttp::Proxy_Authorization, + val))) { // we don't know for sure if this authorization is intended for the // SSL proxy, so we add it just in case. - request->SetHeader(nsHttp::Proxy_Authorization, nsDependentCString(val)); + request->SetHeader(nsHttp::Proxy_Authorization, val); } result.Truncate(); diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index f81f175ed4..5581c932c9 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -434,7 +434,7 @@ nsHttpHandler::InitConnectionMgr() } nsresult -nsHttpHandler::AddStandardRequestHeaders(nsHttpHeaderArray *request, bool isSecure) +nsHttpHandler::AddStandardRequestHeaders(nsHttpRequestHead *request, bool isSecure) { nsresult rv; @@ -487,7 +487,7 @@ nsHttpHandler::AddStandardRequestHeaders(nsHttpHeaderArray *request, bool isSecu } nsresult -nsHttpHandler::AddConnectionHeader(nsHttpHeaderArray *request, +nsHttpHandler::AddConnectionHeader(nsHttpRequestHead *request, uint32_t caps) { // RFC2616 section 19.6.2 states that the "Connection: keep-alive" diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 178fd431c6..ebe42dc73f 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -67,8 +67,8 @@ public: nsHttpHandler(); nsresult Init(); - nsresult AddStandardRequestHeaders(nsHttpHeaderArray *, bool isSecure); - nsresult AddConnectionHeader(nsHttpHeaderArray *, + nsresult AddStandardRequestHeaders(nsHttpRequestHead *, bool isSecure); + nsresult AddConnectionHeader(nsHttpRequestHead *, uint32_t capabilities); bool IsAcceptableEncoding(const char *encoding, bool isSecure); diff --git a/netwerk/protocol/http/nsHttpHeaderArray.cpp b/netwerk/protocol/http/nsHttpHeaderArray.cpp index d557efa8b3..ee3baa05e6 100644 --- a/netwerk/protocol/http/nsHttpHeaderArray.cpp +++ b/netwerk/protocol/http/nsHttpHeaderArray.cpp @@ -137,6 +137,14 @@ nsHttpHeaderArray::GetHeader(nsHttpAtom header, nsACString &result) const return NS_OK; } +bool +nsHttpHeaderArray::HasHeader(nsHttpAtom header) const +{ + const nsEntry *entry = nullptr; + LookupEntry(header, &entry); + return entry; +} + nsresult nsHttpHeaderArray::VisitHeaders(nsIHttpHeaderVisitor *visitor, nsHttpHeaderArray::VisitorFilter filter) { diff --git a/netwerk/protocol/http/nsHttpHeaderArray.h b/netwerk/protocol/http/nsHttpHeaderArray.h index 9cd4bf3da8..b3d99c1143 100644 --- a/netwerk/protocol/http/nsHttpHeaderArray.h +++ b/netwerk/protocol/http/nsHttpHeaderArray.h @@ -59,6 +59,8 @@ public: return FindHeaderValue(header, value) != nullptr; } + bool HasHeader(nsHttpAtom header) const; + enum VisitorFilter { eFilterAll, diff --git a/netwerk/protocol/http/nsHttpRequestHead.cpp b/netwerk/protocol/http/nsHttpRequestHead.cpp index aa58ce0211..165163e559 100644 --- a/netwerk/protocol/http/nsHttpRequestHead.cpp +++ b/netwerk/protocol/http/nsHttpRequestHead.cpp @@ -7,6 +7,7 @@ #include "HttpLog.h" #include "nsHttpRequestHead.h" +#include "nsIHttpHeaderVisitor.h" //----------------------------------------------------------------------------- // nsHttpRequestHead @@ -20,6 +21,7 @@ nsHttpRequestHead::nsHttpRequestHead() , mVersion(NS_HTTP_VERSION_1_1) , mParsedMethod(kMethod_Get) , mHTTPS(false) + , mLock("nsHttpRequestHead.mLock") { MOZ_COUNT_CTOR(nsHttpRequestHead); } @@ -29,9 +31,201 @@ nsHttpRequestHead::~nsHttpRequestHead() MOZ_COUNT_DTOR(nsHttpRequestHead); } +// Don't use this function. It is only used by HttpChannelParent to avoid +// copying of request headers!!! +const nsHttpHeaderArray & +nsHttpRequestHead::Headers() const +{ + mLock.AssertCurrentThreadOwns(); + return mHeaders; +} + +void +nsHttpRequestHead::SetHeaders(const nsHttpHeaderArray& aHeaders) +{ + mHeaders = aHeaders; +} + +void +nsHttpRequestHead::SetVersion(nsHttpVersion version) +{ + MutexAutoLock lock(mLock); + mVersion = version; +} + +void +nsHttpRequestHead::SetRequestURI(const nsCSubstring &s) +{ + MutexAutoLock lock(mLock); + mRequestURI = s; +} + +void +nsHttpRequestHead::SetPath(const nsCSubstring &s) +{ + MutexAutoLock lock(mLock); + mPath = s; +} + +uint32_t +nsHttpRequestHead::HeaderCount() +{ + MutexAutoLock lock(mLock); + return mHeaders.Count(); +} + +nsresult +nsHttpRequestHead::VisitHeaders(nsIHttpHeaderVisitor *visitor, + nsHttpHeaderArray::VisitorFilter filter /* = nsHttpHeaderArray::eFilterAll*/) +{ + MutexAutoLock lock(mLock); + return mHeaders.VisitHeaders(visitor, filter); +} + +void +nsHttpRequestHead::Method(nsACString &aMethod) +{ + MutexAutoLock lock(mLock); + aMethod = mMethod; +} + +nsHttpVersion +nsHttpRequestHead::Version() +{ + MutexAutoLock lock(mLock); + return mVersion; +} + +void +nsHttpRequestHead::RequestURI(nsACString &aRequestURI) +{ + MutexAutoLock lock(mLock); + aRequestURI = mRequestURI; +} + +void +nsHttpRequestHead::Path(nsACString &aPath) +{ + MutexAutoLock lock(mLock); + aPath = mPath.IsEmpty() ? mRequestURI : mPath; +} + +void +nsHttpRequestHead::SetHTTPS(bool val) +{ + MutexAutoLock lock(mLock); + mHTTPS = val; +} + +void +nsHttpRequestHead::Origin(nsACString &aOrigin) +{ + MutexAutoLock lock(mLock); + aOrigin = mOrigin; +} + +nsresult +nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v, + bool m /*= false*/) +{ + MutexAutoLock lock(mLock); + return mHeaders.SetHeader(h, v, m); +} + +nsresult +nsHttpRequestHead::SetHeader(nsHttpAtom h, const nsACString &v, bool m, + nsHttpHeaderArray::HeaderVariety variety) +{ + MutexAutoLock lock(mLock); + return mHeaders.SetHeader(h, v, m, variety); +} + +nsresult +nsHttpRequestHead::SetEmptyHeader(nsHttpAtom h) +{ + MutexAutoLock lock(mLock); + return mHeaders.SetEmptyHeader(h); +} + +nsresult +nsHttpRequestHead::GetHeader(nsHttpAtom h, nsACString &v) +{ + v.Truncate(); + MutexAutoLock lock(mLock); + return mHeaders.GetHeader(h, v); +} + +void +nsHttpRequestHead::ClearHeader(nsHttpAtom h) +{ + MutexAutoLock lock(mLock); + mHeaders.ClearHeader(h); +} + +void +nsHttpRequestHead::ClearHeaders() +{ + MutexAutoLock lock(mLock); + mHeaders.Clear(); +} + +bool +nsHttpRequestHead::HasHeader(nsHttpAtom h) +{ + MutexAutoLock lock(mLock); + return mHeaders.HasHeader(h); +} + +bool +nsHttpRequestHead::HasHeaderValue(nsHttpAtom h, const char *v) +{ + MutexAutoLock lock(mLock); + return mHeaders.HasHeaderValue(h, v); +} + +nsresult +nsHttpRequestHead::SetHeaderOnce(nsHttpAtom h, const char *v, + bool merge /*= false */) +{ + MutexAutoLock lock(mLock); + if (!merge || !mHeaders.HasHeaderValue(h, v)) { + return mHeaders.SetHeader(h, nsDependentCString(v), merge); + } + return NS_OK; +} + +nsHttpRequestHead::ParsedMethodType +nsHttpRequestHead::ParsedMethod() +{ + MutexAutoLock lock(mLock); + return mParsedMethod; +} + +bool +nsHttpRequestHead::EqualsMethod(ParsedMethodType aType) +{ + MutexAutoLock lock(mLock); + return mParsedMethod == aType; +} + +void +nsHttpRequestHead::ParseHeaderSet(char *buffer) +{ + MutexAutoLock lock(mLock); + mHeaders.ParseHeaderSet(buffer); +} + +bool +nsHttpRequestHead::IsHTTPS() +{ + MutexAutoLock lock(mLock); + return mHTTPS; +} + void nsHttpRequestHead::SetMethod(const nsACString &method) { + MutexAutoLock lock(mLock); mParsedMethod = kMethod_Custom; mMethod = method; if (!strcmp(mMethod.get(), "GET")) { @@ -52,8 +246,10 @@ nsHttpRequestHead::SetMethod(const nsACString &method) } void -nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host, int32_t port) +nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host, + int32_t port) { + MutexAutoLock lock(mLock); mOrigin.Assign(scheme); mOrigin.Append(NS_LITERAL_CSTRING("://")); mOrigin.Append(host); @@ -64,11 +260,14 @@ nsHttpRequestHead::SetOrigin(const nsACString &scheme, const nsACString &host, i } bool -nsHttpRequestHead::IsSafeMethod() const +nsHttpRequestHead::IsSafeMethod() { - // This code will need to be extended for new safe methods, otherwise - // they'll default to "not safe". - if (IsGet() || IsHead() || IsOptions() || IsTrace()) { + MutexAutoLock lock(mLock); + // This code will need to be extended for new safe methods, otherwise + // they'll default to "not safe". + if ((mParsedMethod == kMethod_Get) || (mParsedMethod == kMethod_Head) || + (mParsedMethod == kMethod_Options) || (mParsedMethod == kMethod_Trace) + ) { return true; } @@ -84,6 +283,7 @@ nsHttpRequestHead::IsSafeMethod() const void nsHttpRequestHead::Flatten(nsACString &buf, bool pruneProxyHeaders) { + MutexAutoLock lock(mLock); // note: the first append is intentional. buf.Append(mMethod); diff --git a/netwerk/protocol/http/nsHttpRequestHead.h b/netwerk/protocol/http/nsHttpRequestHead.h index 755750e83d..d58ee5e748 100644 --- a/netwerk/protocol/http/nsHttpRequestHead.h +++ b/netwerk/protocol/http/nsHttpRequestHead.h @@ -9,6 +9,9 @@ #include "nsHttp.h" #include "nsHttpHeaderArray.h" #include "nsString.h" +#include "mozilla/Mutex.h" + +class nsIHttpHeaderVisitor; namespace mozilla { namespace net { @@ -23,57 +26,56 @@ public: nsHttpRequestHead(); ~nsHttpRequestHead(); + // The following function is only used in HttpChannelParent to avoid + // copying headers. If you use it be careful to do it only under + // nsHttpRequestHead lock!!! + const nsHttpHeaderArray &Headers() const; + void Lock() { mLock.Lock(); } + void Unlock() { mLock.Unlock(); } + + void SetHeaders(const nsHttpHeaderArray& aHeaders); + void SetMethod(const nsACString &method); - void SetVersion(nsHttpVersion version) { mVersion = version; } - void SetRequestURI(const nsCSubstring &s) { mRequestURI = s; } - void SetPath(const nsCSubstring &s) { mPath = s; } + void SetVersion(nsHttpVersion version); + void SetRequestURI(const nsCSubstring &s); + void SetPath(const nsCSubstring &s); + uint32_t HeaderCount(); - const nsHttpHeaderArray &Headers() const { return mHeaders; } - nsHttpHeaderArray & Headers() { return mHeaders; } - const nsCString &Method() const { return mMethod; } - nsHttpVersion Version() const { return mVersion; } - const nsCSubstring &RequestURI() const { return mRequestURI; } - const nsCSubstring &Path() const { return mPath.IsEmpty() ? mRequestURI : mPath; } + // Using this function it is possible to itereate through all headers + // automatically under one lock. + nsresult VisitHeaders(nsIHttpHeaderVisitor *visitor, + nsHttpHeaderArray::VisitorFilter filter = + nsHttpHeaderArray::eFilterAll); + void Method(nsACString &aMethod); + nsHttpVersion Version(); + void RequestURI(nsACString &RequestURI); + void Path(nsACString &aPath); + void SetHTTPS(bool val); + bool IsHTTPS(); - void SetHTTPS(bool val) { mHTTPS = val; } - bool IsHTTPS() const { return mHTTPS; } + void SetOrigin(const nsACString &scheme, const nsACString &host, + int32_t port); + void Origin(nsACString &aOrigin); - void SetOrigin(const nsACString &scheme, const nsACString &host, int32_t port); - const nsCString &Origin() const { return mOrigin; } + nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false); + nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m, + nsHttpHeaderArray::HeaderVariety variety); + nsresult SetEmptyHeader(nsHttpAtom h); + nsresult GetHeader(nsHttpAtom h, nsACString &v); - const char *PeekHeader(nsHttpAtom h) const - { - return mHeaders.PeekHeader(h); - } - nsresult SetHeader(nsHttpAtom h, const nsACString &v, bool m=false) { return mHeaders.SetHeader(h, v, m); } - nsresult SetEmptyHeader(nsHttpAtom h) { return mHeaders.SetEmptyHeader(h); } - nsresult GetHeader(nsHttpAtom h, nsACString &v) const - { - return mHeaders.GetHeader(h, v); - } - void ClearHeader(nsHttpAtom h) { mHeaders.ClearHeader(h); } - void ClearHeaders() { mHeaders.Clear(); } - - const char *FindHeaderValue(nsHttpAtom h, const char *v) const - { - return mHeaders.FindHeaderValue(h, v); - } - bool HasHeaderValue(nsHttpAtom h, const char *v) const - { - return mHeaders.HasHeaderValue(h, v); - } + void ClearHeader(nsHttpAtom h); + void ClearHeaders(); + bool HasHeaderValue(nsHttpAtom h, const char *v); + // This function returns true if header is set even if it is an empty + // header. + bool HasHeader(nsHttpAtom h); void Flatten(nsACString &, bool pruneProxyHeaders = false); // Don't allow duplicate values - nsresult SetHeaderOnce(nsHttpAtom h, const char *v, bool merge = false) - { - if (!merge || !HasHeaderValue(h, v)) - return mHeaders.SetHeader(h, nsDependentCString(v), merge); - return NS_OK; - } + nsresult SetHeaderOnce(nsHttpAtom h, const char *v, bool merge = false); - bool IsSafeMethod() const; + bool IsSafeMethod(); enum ParsedMethodType { @@ -87,17 +89,16 @@ public: kMethod_Trace }; - ParsedMethodType ParsedMethod() const { return mParsedMethod; } - bool EqualsMethod(ParsedMethodType aType) const { return mParsedMethod == aType; } - bool IsGet() const { return EqualsMethod(kMethod_Get); } - bool IsPost() const { return EqualsMethod(kMethod_Post); } - bool IsOptions() const { return EqualsMethod(kMethod_Options); } - bool IsConnect() const { return EqualsMethod(kMethod_Connect); } - bool IsHead() const { return EqualsMethod(kMethod_Head); } - bool IsPut() const { return EqualsMethod(kMethod_Put); } - bool IsTrace() const { return EqualsMethod(kMethod_Trace); } - void ParseHeaderSet(char *buffer) { mHeaders.ParseHeaderSet(buffer); } - + ParsedMethodType ParsedMethod(); + bool EqualsMethod(ParsedMethodType aType); + bool IsGet() { return EqualsMethod(kMethod_Get); } + bool IsPost() { return EqualsMethod(kMethod_Post); } + bool IsOptions() { return EqualsMethod(kMethod_Options); } + bool IsConnect() { return EqualsMethod(kMethod_Connect); } + bool IsHead() { return EqualsMethod(kMethod_Head); } + bool IsPut() { return EqualsMethod(kMethod_Put); } + bool IsTrace() { return EqualsMethod(kMethod_Trace); } + void ParseHeaderSet(char *buffer); private: // All members must be copy-constructable and assignable nsHttpHeaderArray mHeaders; @@ -112,6 +113,8 @@ private: nsCString mOrigin; ParsedMethodType mParsedMethod; bool mHTTPS; + + Mutex mLock; }; } // namespace net diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 00d79daed9..2b4d7dc0b8 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -183,27 +183,32 @@ nsHttpTransaction::Classify() if (!(mCaps & NS_HTTP_ALLOW_PIPELINING)) return (mClassification = CLASS_SOLO); - if (mRequestHead->PeekHeader(nsHttp::If_Modified_Since) || - mRequestHead->PeekHeader(nsHttp::If_None_Match)) + if (mRequestHead->HasHeader(nsHttp::If_Modified_Since) || + mRequestHead->HasHeader(nsHttp::If_None_Match)) return (mClassification = CLASS_REVALIDATION); - const char *accept = mRequestHead->PeekHeader(nsHttp::Accept); - if (accept && !PL_strncmp(accept, "image/", 6)) + nsAutoCString accept; + bool hasAccept = NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Accept, accept)); + if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("image/"))) { return (mClassification = CLASS_IMAGE); + } - if (accept && !PL_strncmp(accept, "text/css", 8)) + if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("text/css"))) { return (mClassification = CLASS_SCRIPT); + } mClassification = CLASS_GENERAL; - int32_t queryPos = mRequestHead->RequestURI().FindChar('?'); + nsAutoCString requestURI; + mRequestHead->RequestURI(requestURI); + int32_t queryPos = requestURI.FindChar('?'); if (queryPos == kNotFound) { - if (StringEndsWith(mRequestHead->RequestURI(), + if (StringEndsWith(requestURI, NS_LITERAL_CSTRING(".js"))) mClassification = CLASS_SCRIPT; } else if (queryPos >= 3 && - Substring(mRequestHead->RequestURI(), queryPos - 3, 3). + Substring(requestURI, queryPos - 3, 3). EqualsLiteral(".js")) { mClassification = CLASS_SCRIPT; } @@ -301,7 +306,7 @@ nsHttpTransaction::Init(uint32_t caps, // containing a message-body MUST include a valid Content-Length header // field unless the server is known to be HTTP/1.1 compliant. if ((requestHead->IsPost() || requestHead->IsPut()) && - !requestBody && !requestHead->PeekHeader(nsHttp::Transfer_Encoding)) { + !requestBody && !requestHead->HasHeader(nsHttp::Transfer_Encoding)) { requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0")); } diff --git a/netwerk/protocol/http/nsIHttpChannel.idl b/netwerk/protocol/http/nsIHttpChannel.idl index a2b4735f02..b73e51deab 100644 --- a/netwerk/protocol/http/nsIHttpChannel.idl +++ b/netwerk/protocol/http/nsIHttpChannel.idl @@ -59,6 +59,11 @@ interface nsIHttpChannel : nsIChannel * Referrer policies. See ReferrerPolicy.h for more details. */ + + /* The undefined state, usually used to check the need of + overruling document-wide referrer policy. */ + const unsigned long REFERRER_POLICY_UNSET = 4294967295; // UINT32_MAX + /* default state, a shorter name for no-referrer-when-downgrade */ const unsigned long REFERRER_POLICY_DEFAULT = 0; /* default state, doesn't send referrer from https->http */ diff --git a/netwerk/test/unit/test_protocolproxyservice.js b/netwerk/test/unit/test_protocolproxyservice.js index b8fd682e02..2629bd5c03 100644 --- a/netwerk/test/unit/test_protocolproxyservice.js +++ b/netwerk/test/unit/test_protocolproxyservice.js @@ -604,6 +604,36 @@ function run_pac4_test() { prefs.setIntPref("network.proxy.type", 2); prefs.setCharPref("network.proxy.autoconfig_url", pac); + var req = pps.asyncResolve(channel, 0, new TestResolveCallback("http", run_pac5_test)); +} + +function run_pac5_test() { + // Bug 1251332 + let wRange = [ + ["SUN", "MON", "SAT", "MON"], // for Sun + ["SUN", "TUE", "SAT", "TUE"], // for Mon + ["MON", "WED", "SAT", "WED"], // for Tue + ["TUE", "THU", "SAT", "THU"], // for Wed + ["WED", "FRI", "WED", "SUN"], // for Thu + ["THU", "SAT", "THU", "SUN"], // for Fri + ["FRI", "SAT", "FRI", "SUN"], // for Sat + ]; + let today = (new Date()).getDay(); + var pac = 'data:text/plain,' + + 'function FindProxyForURL(url, host) {' + + ' if (weekdayRange("' + wRange[today][0] + '", "' + wRange[today][1] + '") &&' + + ' weekdayRange("' + wRange[today][2] + '", "' + wRange[today][3] + '")) {' + + ' return "PROXY foopy:8080; DIRECT";' + + ' }' + + '}'; + var channel = NetUtil.newChannel({ + uri: "http://www.mozilla.org/", + loadUsingSystemPrincipal: true + }); + // Configure PAC + + prefs.setIntPref("network.proxy.type", 2); + prefs.setCharPref("network.proxy.autoconfig_url", pac); var req = pps.asyncResolve(channel, 0, new TestResolveCallback("http", finish_pac_test)); } diff --git a/python/mozboot/mozboot/centosfedora.py b/python/mozboot/mozboot/centosfedora.py index 153309991b..3e1ada9b74 100644 --- a/python/mozboot/mozboot/centosfedora.py +++ b/python/mozboot/mozboot/centosfedora.py @@ -132,6 +132,5 @@ class CentOSFedoraBootstrapper(BaseBootstrapper): def suggest_mobile_android_artifact_mode_mozconfig(self): self.suggest_mobile_android_mozconfig(artifact_mode=True) - def upgrade_mercurial(self): + def upgrade_mercurial(self, current): self.dnf_update('mercurial') - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..839f856fd2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,43 @@ +// You might think topsrcdir is '.', but that's not true when the Gradle build +// is launched from within IntelliJ. +def topsrcdir = rootProject.projectDir.absolutePath + +def commandLine = ["${topsrcdir}/mach", "environment", "--format", "json", "--verbose"] +def proc = commandLine.execute(null, new File(topsrcdir)) +def standardOutput = new ByteArrayOutputStream() +proc.consumeProcessOutput(standardOutput, standardOutput) +proc.waitFor() + +// Only show the output if something went wrong. +if (proc.exitValue() != 0) { + throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${proc.exitValue()}:\n\n${standardOutput.toString()}") +} + +import groovy.json.JsonSlurper +def slurper = new JsonSlurper() +def json = slurper.parseText(standardOutput.toString()) + +include ':app' +include ':base' +include ':omnijar' +include ':thirdparty' + +def gradleRoot = new File("${json.topobjdir}/mobile/android/gradle") +project(':app').projectDir = new File(gradleRoot, 'app') +project(':base').projectDir = new File(gradleRoot, 'base') +project(':omnijar').projectDir = new File(gradleRoot, 'omnijar') +project(':thirdparty').projectDir = new File(gradleRoot, 'thirdparty') + +if (json.substs.MOZ_INSTALL_TRACKING) { + include ':thirdparty_adjust_sdk' + project(':thirdparty_adjust_sdk').projectDir = new File(gradleRoot, 'thirdparty_adjust_sdk') +} + +// The Gradle instance is shared between settings.gradle and all the +// other build.gradle files (see +// http://forums.gradle.org/gradle/topics/define_extension_properties_from_settings_xml). +// We use this ext property to pass the per-object-directory mozconfig +// between scripts. This lets us execute set-up code before we gradle +// tries to configure the project even once, and as a side benefit +// saves invoking |mach environment| multiple times. +gradle.ext.mozconfig = json diff --git a/toolkit/components/telemetry/Telemetry.h b/toolkit/components/telemetry/Telemetry.h index 1b16d0ed74..cfdb445905 100644 --- a/toolkit/components/telemetry/Telemetry.h +++ b/toolkit/components/telemetry/Telemetry.h @@ -273,7 +273,7 @@ class ProcessedStack; * @param aFirefoxUptime - Firefox uptime at the time of the hang, in minutes * @param aAnnotations - Any annotations to be added to the report */ -#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(MOZILLA_XPCOMRT_API) +#if defined(MOZ_ENABLE_PROFILER_SPS) void RecordChromeHang(uint32_t aDuration, ProcessedStack &aStack, int32_t aSystemUptime, diff --git a/toolkit/components/telemetry/TelemetryController.jsm b/toolkit/components/telemetry/TelemetryController.jsm index eb848f623b..5b4cae6900 100644 --- a/toolkit/components/telemetry/TelemetryController.jsm +++ b/toolkit/components/telemetry/TelemetryController.jsm @@ -49,7 +49,7 @@ const PING_FORMAT_VERSION = 4; // Delay before intializing telemetry (ms) const TELEMETRY_DELAY = Preferences.get("toolkit.telemetry.initDelay", 60) * 1000; // Delay before initializing telemetry if we're testing (ms) -const TELEMETRY_TEST_DELAY = 100; +const TELEMETRY_TEST_DELAY = 1; // Ping types. const PING_TYPE_MAIN = "main"; diff --git a/toolkit/components/telemetry/TelemetrySend.jsm b/toolkit/components/telemetry/TelemetrySend.jsm index 1820c72c13..64e0cbf644 100644 --- a/toolkit/components/telemetry/TelemetrySend.jsm +++ b/toolkit/components/telemetry/TelemetrySend.jsm @@ -911,6 +911,7 @@ var TelemetrySendImpl = { request.open("POST", url, true); request.overrideMimeType("text/plain"); request.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); + request.setRequestHeader("Date", Policy.now().toUTCString()); this._pendingPingRequests.set(id, request); diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index 53238d779e..877bcc8c29 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -78,7 +78,7 @@ const TELEMETRY_INTERVAL = 60 * 1000; // Delay before intializing telemetry (ms) const TELEMETRY_DELAY = Preferences.get("toolkit.telemetry.initDelay", 60) * 1000; // Delay before initializing telemetry if we're testing (ms) -const TELEMETRY_TEST_DELAY = 100; +const TELEMETRY_TEST_DELAY = 1; // Execute a scheduler tick every 5 minutes. const SCHEDULER_TICK_INTERVAL_MS = Preferences.get("toolkit.telemetry.scheduler.tickInterval", 5 * 60) * 1000; // When user is idle, execute a scheduler tick every 60 minutes. @@ -311,13 +311,41 @@ var TelemetryScheduler = { this._log.trace("init"); this._shuttingDown = false; this._isUserIdle = false; + // Initialize the last daily ping and aborted session last due times to the current time. // Otherwise, we might end up sending daily pings even if the subsession is not long enough. let now = Policy.now(); this._lastDailyPingTime = now.getTime(); this._lastSessionCheckpointTime = now.getTime(); this._rescheduleTimeout(); + idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS); + Services.obs.addObserver(this, "wake_notification", false); + }, + + /** + * Stops the scheduler. + */ + shutdown: function() { + if (this._shuttingDown) { + if (this._log) { + this._log.error("shutdown - Already shut down"); + } else { + Cu.reportError("TelemetryScheduler.shutdown - Already shut down"); + } + return; + } + + this._log.trace("shutdown"); + if (this._schedulerTimer) { + Policy.clearSchedulerTickTimeout(this._schedulerTimer); + this._schedulerTimer = null; + } + + idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); + Services.obs.removeObserver(this, "wake_notification"); + + this._shuttingDown = true; }, _clearTimeout: function() { @@ -415,6 +443,13 @@ var TelemetryScheduler = { this._isUserIdle = false; return this._onSchedulerTick(); break; + case "wake_notification": + // The machine woke up from sleep, trigger a tick to avoid sessions + // spanning more than a day. + // This is needed because sleep time does not count towards timeouts + // on Mac & Linux - see bug 1262386, bug 1204823 et al. + return this._onSchedulerTick(); + break; } return undefined; }, @@ -517,30 +552,6 @@ var TelemetryScheduler = { this._rescheduleTimeout(); }, - - /** - * Stops the scheduler. - */ - shutdown: function() { - if (this._shuttingDown) { - if (this._log) { - this._log.error("shutdown - Already shut down"); - } else { - Cu.reportError("TelemetryScheduler.shutdown - Already shut down"); - } - return; - } - - this._log.trace("shutdown"); - if (this._schedulerTimer) { - Policy.clearSchedulerTickTimeout(this._schedulerTimer); - this._schedulerTimer = null; - } - - idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); - - this._shuttingDown = true; - } }; this.EXPORTED_SYMBOLS = ["TelemetrySession"]; diff --git a/toolkit/components/telemetry/TelemetryStopwatch.jsm b/toolkit/components/telemetry/TelemetryStopwatch.jsm index 19c19c6b66..ee3d899480 100644 --- a/toolkit/components/telemetry/TelemetryStopwatch.jsm +++ b/toolkit/components/telemetry/TelemetryStopwatch.jsm @@ -8,14 +8,110 @@ const Cu = Components.utils; this.EXPORTED_SYMBOLS = ["TelemetryStopwatch"]; +Cu.import("resource://gre/modules/Log.jsm", this); var Telemetry = Cc["@mozilla.org/base/telemetry;1"] .getService(Ci.nsITelemetry); -// simpleTimers are directly associated with a histogram -// name. objectTimers are associated with an object _and_ -// a histogram name. -var simpleTimers = {}; -var objectTimers = new WeakMap(); +// Weak map does not allow using null objects as keys. These objects are used +// as 'null' placeholders. +const NULL_OBJECT = {}; +const NULL_KEY = {}; + +/** + * Timers is a variation of a Map used for storing information about running + * Stopwatches. Timers has the following data structure: + * + * { + * "HISTOGRAM_NAME": WeakMap { + * Object || NULL_OBJECT: Map { + * "KEY" || NULL_KEY: startTime + * ... + * } + * ... + * } + * ... + * } + * + * + * @example + * // Stores current time for a keyed histogram "PLAYING_WITH_CUTE_ANIMALS". + * Timers.put("PLAYING_WITH_CUTE_ANIMALS", null, "CATS", Date.now()); + * + * @example + * // Returns information about a simple Stopwatch. + * let startTime = Timers.get("PLAYING_WITH_CUTE_ANIMALS", null, "CATS"); + */ +let Timers = { + _timers: new Map(), + + _validTypes: function(histogram, obj, key) { + let nonEmptyString = value => { + return typeof value === "string" && value !== "" && value.length > 0; + }; + return nonEmptyString(histogram) && + typeof obj == "object" && + (key === NULL_KEY || nonEmptyString(key)); + }, + + get: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if (!this.has(histogram, obj, key)) { + return null; + } + + return this._timers.get(histogram).get(obj).get(key); + }, + + put: function(histogram, obj, key, startTime) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if (!this._validTypes(histogram, obj, key)) { + return false; + } + + let objectMap = this._timers.get(histogram) || new WeakMap(); + let keyedInfo = objectMap.get(obj) || new Map(); + keyedInfo.set(key, startTime); + objectMap.set(obj, keyedInfo); + this._timers.set(histogram, objectMap); + return true; + }, + + has: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + return this._timers.has(histogram) && + this._timers.get(histogram).has(obj) && + this._timers.get(histogram).get(obj).has(key); + }, + + delete: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if(!this.has(histogram, obj, key)) { + return false; + } + let objectMap = this._timers.get(histogram); + let keyedInfo = objectMap.get(obj); + if (keyedInfo.size > 1) { + keyedInfo.delete(key); + return true; + } + objectMap.delete(obj); + // NOTE: + // We never delete empty objecMaps from this._timers because there is no + // nice solution for tracking the number of objects in a WeakMap. + // WeakMap is not enumerable, so we can't deterministically say when it's + // empty. We accept that trade-off here, given that entries for short-lived + // objects will go away when they are no longer referenced + return true; + } +}; this.TelemetryStopwatch = { /** @@ -23,39 +119,21 @@ this.TelemetryStopwatch = { * directly associated with a histogram, or with a pair of a histogram and * an object. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.h + * @param {String} aHistogram - a string which must be a valid histogram name. * - * @param aObj Optional parameter. If specified, the timer is associated - * with this object, meaning that multiple timers for a same - * histogram may be run concurrently, as long as they are - * associated with different objects. + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers for the same histogram may be run + * concurrently, as long as they are associated with + * different objects. * - * @return true if the timer was successfully started, false otherwise. If a - * timer already exists, it can't be started again, and the existing - * one will be cleared in order to avoid measurements errors. + * @returns {Boolean} True if the timer was successfully started, false + * otherwise. If a timer already exists, it can't be + * started again, and the existing one will be cleared in + * order to avoid measurements errors. */ start: function(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) - return false; - - let timers; - if (aObj) { - timers = objectTimers.get(aObj) || {}; - objectTimers.set(aObj, timers); - } else { - timers = simpleTimers; - } - - if (timers.hasOwnProperty(aHistogram)) { - delete timers[aHistogram]; - Cu.reportError("TelemetryStopwatch: key \"" + - aHistogram + "\" was already initialized"); - return false; - } - - timers[aHistogram] = Components.utils.now(); - return true; + return TelemetryStopwatchImpl.start(aHistogram, aObj, null); }, /** @@ -64,57 +142,37 @@ this.TelemetryStopwatch = { * an object. Important: Only use this method when a legitimate cancellation * should be done. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.json + * @param {String} aHistogram - a string which must be a valid histogram name. * - * @param aObj Optional parameter. If specified, the timer is associated - * with this object, meaning that multiple timers for a same - * histogram may be run concurrently, as long as they are - * associated with different objects. + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers or a same histogram may be run concurrently, + * as long as they are associated with different + * objects. * - * @return true if the timer exist and it was cleared, false otherwise. + * @returns {Boolean} True if the timer exist and it was cleared, False + * otherwise. */ - cancel: function ts_cancel(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) - return false; - - let timers = aObj - ? objectTimers.get(aObj) || {} - : simpleTimers; - - if (timers.hasOwnProperty(aHistogram)) { - delete timers[aHistogram]; - return true; - } - - return false; + cancel: function(aHistogram, aObj) { + return TelemetryStopwatchImpl.cancel(aHistogram, aObj, null); }, /** * Returns the elapsed time for a particular stopwatch. Primarily for * debugging purposes. Must be called prior to finish. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.h. If an invalid name - * is given, the function will throw. + * @param {String} aHistogram - a string which must be a valid histogram name. + * If an invalid name is given, the function will + * throw. * - * @param aObj Optional parameter which associates the histogram timer with - * the given object. + * @param (Object) aObj - Optional parameter which associates the histogram + * timer with the given object. * - * @return time in milliseconds or -1 if the stopwatch was not found. + * @returns {Integer} time in milliseconds or -1 if the stopwatch was not + * found. */ timeElapsed: function(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) - return -1; - let timers = aObj - ? objectTimers.get(aObj) || {} - : simpleTimers; - let start = timers[aHistogram]; - if (start) { - let delta = Components.utils.now() - start; - return Math.round(delta); - } - return -1; + return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, null); }, /** @@ -122,40 +180,156 @@ this.TelemetryStopwatch = { * calculates the time delta between start and finish, and adds the value * to the histogram. * - * @param aHistogram a string which must be a valid histogram name - * from TelemetryHistograms.h. If an invalid name - * is given, the function will throw. + * @param {String} aHistogram - a string which must be a valid histogram name. * - * @param aObj Optional parameter which associates the histogram timer with - * the given object. + * @param {Object} aObj - Optional parameter which associates the histogram + * timer with the given object. * - * @return true if the timer was succesfully stopped and the data was - * added to the histogram, false otherwise. + * @returns {Boolean} True if the timer was succesfully stopped and the data + * was added to the histogram, False otherwise. */ finish: function(aHistogram, aObj) { - if (!validTypes(aHistogram, aObj)) + return TelemetryStopwatchImpl.finish(aHistogram, aObj, null); + }, + + /** + * Starts a timer associated with a keyed telemetry histogram. The timer can + * be directly associated with a histogram and its key. Similarly to + * @see{TelemetryStopwatch.stat} the histogram and its key can be associated + * with an object. Each key may have multiple associated objects and each + * object can be associated with multiple keys. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers for the same histogram may be run + * concurrently,as long as they are associated with + * different objects. + * + * @returns {Boolean} True if the timer was successfully started, false + * otherwise. If a timer already exists, it can't be + * started again, and the existing one will be cleared in + * order to avoid measurements errors. + */ + startKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey); + }, + + /** + * Deletes the timer associated with a keyed histogram. Important: Only use + * this method when a legitimate cancellation should be done. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer + * associated with this object is deleted. + * + * @return {Boolean} True if the timer exist and it was cleared, False + * otherwise. + */ + cancelKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.cancel(aHistogram, aObj, aKey); + }, + + /** + * Returns the elapsed time for a particular stopwatch. Primarily for + * debugging purposes. Must be called prior to finish. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer + * associated with this object is used to calculate + * the elapsed time. + * + * @return {Integer} time in milliseconds or -1 if the stopwatch was not + * found. + */ + timeElapsedKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, aKey); + }, + + /** + * Stops the timer associated with the given keyed histogram (and object), + * calculates the time delta between start and finish, and adds the value + * to the keyed histogram. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - optional parameter which associates the histogram + * timer with the given object. + * + * @returns {Boolean} True if the timer was succesfully stopped and the data + * was added to the histogram, False otherwise. + */ + finishKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.finish(aHistogram, aObj, aKey); + } +}; + +this.TelemetryStopwatchImpl = { + start: function(histogram, object, key) { + if (Timers.has(histogram, object, key)) { + Timers.delete(histogram, object, key); + Cu.reportError(`TelemetryStopwatch: key "${histogram}" was already ` + + "initialized"); return false; - - let timers = aObj - ? objectTimers.get(aObj) || {} - : simpleTimers; - - let start = timers[aHistogram]; - delete timers[aHistogram]; - - if (start) { - let delta = Components.utils.now() - start; - delta = Math.round(delta); - let histogram = Telemetry.getHistogramById(aHistogram); - histogram.add(delta); - return true; } - return false; + return Timers.put(histogram, object, key, Components.utils.now()); + }, + + cancel: function (histogram, object, key) { + return Timers.delete(histogram, object, key); + }, + + timeElapsed: function(histogram, object, key) { + let startTime = Timers.get(histogram, object, key); + if (startTime === null) { + Cu.reportError("TelemetryStopwatch: requesting elapsed time for " + + `nonexisting stopwatch. Histogram: "${histogram}", ` + + `key: "${key}"`); + return -1; + } + + try { + let delta = Components.utils.now() - startTime + return Math.round(delta); + } catch (e) { + Cu.reportError("TelemetryStopwatch: failed to calculate elapsed time " + + `for Histogram: "${histogram}", key: "${key}", ` + + `exception: ${Log.exceptionStr(e)}`); + return -1; + } + }, + + finish: function(histogram, object, key) { + let delta = this.timeElapsed(histogram, object, key); + if (delta == -1) { + return false; + } + + try { + if (key) { + Telemetry.getKeyedHistogramById(histogram).add(key, delta); + } else { + Telemetry.getHistogramById(histogram).add(delta); + } + } catch (e) { + Cu.reportError("TelemetryStopwatch: failed to update the Histogram " + + `"${histogram}", using key: "${key}", ` + + `exception: ${Log.exceptionStr(e)}`); + return false; + } + + return Timers.delete(histogram, object, key); } } - -function validTypes(aKey, aObj) { - return (typeof aKey == "string") && - (aObj === undefined || typeof aObj == "object"); -} diff --git a/toolkit/components/telemetry/TelemetryStorage.jsm b/toolkit/components/telemetry/TelemetryStorage.jsm index 8d0422c78c..27cacbe466 100644 --- a/toolkit/components/telemetry/TelemetryStorage.jsm +++ b/toolkit/components/telemetry/TelemetryStorage.jsm @@ -1579,10 +1579,6 @@ var TelemetryStorageImpl = { let ping; try { ping = JSON.parse(string); - // The ping's payload used to be stringified JSON. Deal with that. - if (typeof(ping.payload) == "string") { - ping.payload = JSON.parse(ping.payload); - } } catch (e) { this._log.trace("loadPingfile - unparseable ping " + aFilePath, e); yield OS.File.remove(aFilePath).catch((ex) => { diff --git a/toolkit/components/telemetry/docs/core-ping.rst b/toolkit/components/telemetry/docs/core-ping.rst new file mode 100644 index 0000000000..a2101749ac --- /dev/null +++ b/toolkit/components/telemetry/docs/core-ping.rst @@ -0,0 +1,105 @@ + +"core" ping +============ + +This mobile-specific ping is intended to provide the most critical +data in a concise format, allowing for frequent uploads. + +Since this ping is used to measure retention, it should be sent +each time the browser is opened. + +Submission will be per the Edge server specification:: + + /submit/telemetry/docId/docType/appName/appVersion/appUpdateChannel/appBuildID + +* ``docId`` is a UUID for deduping +* ``docType`` is “core” +* ``appName`` is “Fennec” +* ``appVersion`` is the version of the application (e.g. "46.0a1") +* ``appUpdateChannel`` is “release”, “beta”, etc. +* ``appBuildID`` is the build number + +Note: Counts below (e.g. search & usage times) are “since the last +ping”, not total for the whole application lifetime. + +Structure:: + + { + "v": 3, // ping format version + "clientId": , // client id, e.g. + // "c641eacf-c30c-4171-b403-f077724e848a" + "seq": , // running ping counter, e.g. 3 + "locale": , // application locale, e.g. "en-US" + "os": , // OS name. + "osversion": , // OS version. + "device": , // Build.MANUFACTURER + " - " + Build.MODEL + // where manufacturer is truncated to 12 characters + // & model is truncated to 19 characters + "arch": , // e.g. "arm", "x86" + "profileDate": , // Profile creation date in days since + // UNIX epoch. + "defaultSearch": , // Identifier of the default search engine, + // e.g. "yahoo". + + "experiments": [, …], // Optional, array of identifiers + // for the active experiments + } + +Field details +------------- + +device +~~~~~~ +The ``device`` field is filled in with information specified by the hardware +manufacturer. As such, it could be excessively long and use excessive amounts +of limited user data. To avoid this, we limit the length of the field. We're +more likely have collisions for models within a manufacturer (e.g. "Galaxy S5" +vs. "Galaxy Note") than we are for shortened manufacturer names so we provide +more characters for the model than the manufacturer. + +defaultSearch +~~~~~~~~~~~~~ +On Android, this field may be ``null``. To get the engine, we rely on +``SearchEngineManager#getDefaultEngine``, which searches in several places in +order to find the search engine identifier: + +* Shared Preferences +* The distribution (if it exists) +* The localized default engine + +If the identifier could not be retrieved, this field is ``null``. If the +identifier is retrieved, we attempt to create an instance of the search +engine from the search plugins (in order): + +* In the distribution +* From the localized plugins shipped with the browser +* The third-party plugins that are installed in the profile directory + +If the plugins fail to create a search engine instance, this field is also +``null``. + +This field can also be ``null`` when a custom search engine is set as the +default. + +profileDate +~~~~~~~~~~~ +On Android, this value is created at profile creation time and retrieved or, +for legacy profiles, taken from the package install time (note: this is not the +same exact metric as profile creation time but we compromised in favor of ease +of implementation). + +Additionally on Android, this field may be ``null`` in the unlikely event that +all of the following events occur: + +#. The times.json file does not exist +#. The package install date could not be persisted to disk + +The reason we don't just return the package install time even if the date could +not be persisted to disk is to ensure the value doesn't change once we start +sending it: we only want to send consistent values. + +Version history +--------------- +* v3: ``profileDate`` will return package install time when times.json is not available +* v2: added ``defaultSearch`` +* v1: initial version diff --git a/toolkit/components/telemetry/docs/deletion-ping.rst b/toolkit/components/telemetry/docs/deletion-ping.rst new file mode 100644 index 0000000000..a28f47cb97 --- /dev/null +++ b/toolkit/components/telemetry/docs/deletion-ping.rst @@ -0,0 +1,17 @@ + +"deletion" ping +=============== + +This ping is generated when a user turns off FHR upload from the Preferences panel, changing the related ``datareporting.healthreport.uploadEnabled`` preference. This requests that all associated data from that user be deleted. + +This ping contains the client id and no environment data. + +Structure:: + + { + version: 4, + type: "deletion", + ... common ping data + clientId: , + payload: { } + } \ No newline at end of file diff --git a/toolkit/components/telemetry/docs/index.rst b/toolkit/components/telemetry/docs/index.rst index 2c49b3ff5c..3e7a47af59 100644 --- a/toolkit/components/telemetry/docs/index.rst +++ b/toolkit/components/telemetry/docs/index.rst @@ -19,4 +19,7 @@ Client-side, this consists of: common-ping environment main-ping + core-ping + deletion-ping + uitour-ping preferences diff --git a/toolkit/components/telemetry/docs/pings.rst b/toolkit/components/telemetry/docs/pings.rst index 00031c241c..3ba42a4b98 100644 --- a/toolkit/components/telemetry/docs/pings.rst +++ b/toolkit/components/telemetry/docs/pings.rst @@ -43,9 +43,10 @@ Ping types * :doc:`main ` - contains the information collected by Telemetry (Histograms, hang stacks, ...) * :doc:`saved-session ` - has the same format as a main ping, but it contains the *"classic"* Telemetry payload with measurements covering the whole browser session. This is only a separate type to make storage of saved-session easier server-side. This is temporary and will be removed soon. * :doc:`crash ` - a ping that is captured and sent after Firefox crashes. +* :doc:`uitour-ping` - a ping submitted via the UITour API * ``activation`` - *planned* - sent right after installation or profile creation * ``upgrade`` - *planned* - sent right after an upgrade -* ``deletion`` - *planned* - on opt-out we may have to tell the server to delete user data +* :doc:`deletion ` - sent when FHR upload is disabled, requesting deletion of the data associated with this user Archiving ========= diff --git a/toolkit/components/telemetry/docs/uitour-ping.rst b/toolkit/components/telemetry/docs/uitour-ping.rst new file mode 100644 index 0000000000..e56a605e23 --- /dev/null +++ b/toolkit/components/telemetry/docs/uitour-ping.rst @@ -0,0 +1,24 @@ + +"uitour-tag" ping +================= + +This ping is submitted via the UITour setTreatmentTag API. It may be used by +the tour to record what settings were made by a user or to track the result of +A/B experiments. + +The client ID is submitted with this ping. + +Structure:: + + { + version: 1, + type: "uitour-tag", + clientId: , + payload: { + tagName: , + tagValue: + } + } + +See also: :doc:`common ping fields ` + diff --git a/toolkit/components/telemetry/tests/unit/.eslintrc b/toolkit/components/telemetry/tests/unit/.eslintrc new file mode 100644 index 0000000000..8a895f93bd --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc" + ] +} diff --git a/toolkit/components/telemetry/tests/unit/head.js b/toolkit/components/telemetry/tests/unit/head.js index 145b8aca58..a5dc9023c9 100644 --- a/toolkit/components/telemetry/tests/unit/head.js +++ b/toolkit/components/telemetry/tests/unit/head.js @@ -133,7 +133,7 @@ function decodeRequestPayload(request) { unicodeConverter.charset = "UTF-8"; let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer); utf8string += unicodeConverter.Finish(); - payload = decoder.decode(utf8string); + payload = JSON.parse(utf8string); } else { payload = decoder.decodeFromStream(s, s.available()); } @@ -172,58 +172,6 @@ function loadAddonManager(ID, name, version, platformVersion) { startupManager(); } -/** - * Decode the payload of an HTTP request into a ping. - * @param {Object} request The data representing an HTTP request (nsIHttpRequest). - * @return {Object} The decoded ping payload. - */ -function decodeRequestPayload(request) { - let s = request.bodyInputStream; - let payload = null; - let decoder = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON) - - if (request.getHeader("content-encoding") == "gzip") { - let observer = { - buffer: "", - onStreamComplete: function(loader, context, status, length, result) { - this.buffer = String.fromCharCode.apply(this, result); - } - }; - - let scs = Cc["@mozilla.org/streamConverters;1"] - .getService(Ci.nsIStreamConverterService); - let listener = Cc["@mozilla.org/network/stream-loader;1"] - .createInstance(Ci.nsIStreamLoader); - listener.init(observer); - let converter = scs.asyncConvertData("gzip", "uncompressed", - listener, null); - converter.onStartRequest(null, null); - converter.onDataAvailable(null, null, s, 0, s.available()); - converter.onStopRequest(null, null, null); - let unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] - .createInstance(Ci.nsIScriptableUnicodeConverter); - unicodeConverter.charset = "UTF-8"; - let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer); - utf8string += unicodeConverter.Finish(); - payload = decoder.decode(utf8string); - } else { - payload = decoder.decodeFromStream(s, s.available()); - } - - return payload; -} - -function loadAddonManager(id, name, version, platformVersion) { - let ns = {}; - Cu.import("resource://gre/modules/Services.jsm", ns); - let head = "../../../../mozapps/extensions/test/xpcshell/head_addons.js"; - let file = do_get_file(head); - let uri = ns.Services.io.newFileURI(file); - ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); - createAppInfo(id, name, version, platformVersion); - startupManager(); -} - var gAppInfo = null; function createAppInfo(ID, name, version, platformVersion) { @@ -312,6 +260,17 @@ function promiseRejects(promise) { return promise.then(() => false, () => true); } +// Generates a random string of at least a specific length. +function generateRandomString(length) { + let string = ""; + + while (string.length < length) { + string += Math.random().toString(36); + } + + return string.substring(0, length); +} + // Short-hand for retrieving the histogram with that id. function getHistogram(histogramId) { return Telemetry.getHistogramById(histogramId); @@ -329,6 +288,8 @@ if (runningInParent) { Services.prefs.setBoolPref("toolkit.telemetry.archive.enabled", true); // Telemetry xpcshell tests cannot show the infobar. Services.prefs.setBoolPref("datareporting.policy.dataSubmissionPolicyBypassNotification", true); + // FHR uploads should be enabled. + Services.prefs.setBoolPref("datareporting.healthreport.uploadEnabled", true); fakePingSendTimer((callback, timeout) => { Services.tm.mainThread.dispatch(() => callback(), Ci.nsIThread.DISPATCH_NORMAL); @@ -338,6 +299,8 @@ if (runningInParent) { do_register_cleanup(() => TelemetrySend.shutdown()); } +TelemetryController.initLogging(); + // Avoid timers interrupting test behavior. fakeSchedulerTimer(() => {}, () => {}); // Make pind sending predictable. diff --git a/toolkit/components/telemetry/tests/unit/test_PingAPI.js b/toolkit/components/telemetry/tests/unit/test_PingAPI.js index 6ba160fb7a..2ed67a0661 100644 --- a/toolkit/components/telemetry/tests/unit/test_PingAPI.js +++ b/toolkit/components/telemetry/tests/unit/test_PingAPI.js @@ -195,6 +195,23 @@ add_task(function* test_archiveCleanup() { // Empty the archive. yield OS.File.removeDir(gPingsArchivePath); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_DIRECTORIES_COUNT").clear(); + // Also reset these histograms to make sure normal sized pings don't get counted. + Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED").clear(); + Telemetry.getHistogramById("TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB").clear(); + + // Build the cache. Nothing should be evicted as there's no ping directory. + yield TelemetryController.reset(); + yield TelemetryStorage.testCleanupTaskPromise(); + yield TelemetryArchive.promiseArchivedPingList(); + + let h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report 0 pings scanned if no archive dir exists."); + // One directory out of four was removed as well. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report 0 evicted dirs if no archive dir exists."); + let expectedPrunedInfo = []; let expectedNotPrunedInfo = []; @@ -215,15 +232,17 @@ add_task(function* test_archiveCleanup() { } }); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SESSION_PING_COUNT").clear(); + // Create a ping which should be pruned because it is past the retention period. let date = fakeNow(2010, 1, 1, 1, 0, 0); let pingId = yield TelemetryController.submitExternalPing(PING_TYPE, {}, {}); expectedPrunedInfo.push({ id: pingId, creationDate: date }); // Create a ping which should be kept because it is within the retention period. - date = fakeNow(2010, 2, 1, 1, 0, 0); + const oldestDirectoryDate = fakeNow(2010, 2, 1, 1, 0, 0); pingId = yield TelemetryController.submitExternalPing(PING_TYPE, {}, {}); - expectedNotPrunedInfo.push({ id: pingId, creationDate: date }); + expectedNotPrunedInfo.push({ id: pingId, creationDate: oldestDirectoryDate }); // Create 20 other pings which are within the retention period, but would be affected // by the disk quota. @@ -235,8 +254,15 @@ add_task(function* test_archiveCleanup() { } } + // We expect all the pings we archived to be in this histogram. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SESSION_PING_COUNT"); + Assert.equal(h.snapshot().sum, 22, "All the pings must be live-accumulated in the histogram."); + // Reset the histogram that will be populated by the archive scan. + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_OLDEST_DIRECTORY_AGE").clear(); + // Move the current date 180 days ahead of the first ping. - fakeNow(2010, 7, 1, 1, 0, 0); + let now = fakeNow(2010, 7, 1, 1, 0, 0); // Reset TelemetryArchive and TelemetryController to start the startup cleanup. yield TelemetryController.reset(); // Wait for the cleanup to finish. @@ -247,6 +273,27 @@ add_task(function* test_archiveCleanup() { // Check that the archive is in the correct state. yield checkArchive(); + // Make sure the ping count is correct after the scan (one ping was removed). + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT").snapshot(); + Assert.equal(h.sum, 21, "The histogram must count all the pings in the archive."); + // One directory out of four was removed as well. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must correctly report removed archive directories."); + // Check that the remaining directories are correctly counted. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_DIRECTORIES_COUNT").snapshot(); + Assert.equal(h.sum, 3, "Telemetry must correctly report the remaining archive directories."); + // Check that the remaining directories are correctly counted. + const oldestAgeInMonths = 5; + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_OLDEST_DIRECTORY_AGE").snapshot(); + Assert.equal(h.sum, oldestAgeInMonths, + "Telemetry must correctly report age of the oldest directory in the archive."); + + // We need to test the archive size before we hit the quota, otherwise a special + // value is recorded. + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").clear(); + Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTING_OVER_QUOTA_MS").clear(); + // Move the current date 180 days ahead of the second ping. fakeNow(2010, 8, 1, 1, 0, 0); // Reset TelemetryController and TelemetryArchive. @@ -262,9 +309,19 @@ add_task(function* test_archiveCleanup() { yield checkArchive(); // Find how much disk space the archive takes. - const archivedPings = yield getArchivedPingsInfo(); + const archivedPingsInfo = yield getArchivedPingsInfo(); let archiveSizeInBytes = - archivedPings.reduce((lastResult, element) => lastResult + element.size, 0); + archivedPingsInfo.reduce((lastResult, element) => lastResult + element.size, 0); + + // Check that the correct values for quota probes are reported when no quota is hit. + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").snapshot(); + Assert.equal(h.sum, Math.round(archiveSizeInBytes / 1024 / 1024), + "Telemetry must report the correct archive size."); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report 0 evictions if quota is not hit."); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTING_OVER_QUOTA_MS").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report a null elapsed time if quota is not hit."); + // Set the quota to 80% of the space. const testQuotaInBytes = archiveSizeInBytes * 0.8; fakeStorageQuota(testQuotaInBytes); @@ -272,7 +329,6 @@ add_task(function* test_archiveCleanup() { // The storage prunes archived pings until we reach 90% of the requested storage quota. // Based on that, find how many pings should be kept. const safeQuotaSize = testQuotaInBytes * 0.9; - const archivedPingsInfo = yield getArchivedPingsInfo(); let sizeInBytes = 0; let pingsWithinQuota = []; let pingsOutsideQuota = []; @@ -296,11 +352,50 @@ add_task(function* test_archiveCleanup() { // Check that the archive is in the correct state. yield checkArchive(); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").snapshot(); + Assert.equal(h.sum, pingsOutsideQuota.length, + "Telemetry must correctly report the over quota pings evicted from the archive."); + h = Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").snapshot(); + Assert.equal(h.sum, 300, "Archive quota was hit, a special size must be reported."); + // Trigger a cleanup again and make sure we're not removing anything. yield TelemetryController.reset(); yield TelemetryStorage.testCleanupTaskPromise(); yield TelemetryArchive.promiseArchivedPingList(); yield checkArchive(); + + const OVERSIZED_PING_ID = "9b21ec8f-f762-4d28-a2c1-44e1c4694f24"; + // Create and archive an oversized, uncompressed, ping. + const OVERSIZED_PING = { + id: OVERSIZED_PING_ID, + type: PING_TYPE, + creationDate: (new Date()).toISOString(), + // Generate a ~2MB string to use as the payload. + payload: generateRandomString(2 * 1024 * 1024) + }; + yield TelemetryArchive.promiseArchivePing(OVERSIZED_PING); + + // Get the size of the archived ping. + const oversizedPingPath = + TelemetryStorage._testGetArchivedPingPath(OVERSIZED_PING.id, new Date(OVERSIZED_PING.creationDate), PING_TYPE) + "lz4"; + const archivedPingSizeMB = Math.floor((yield OS.File.stat(oversizedPingPath)).size / 1024 / 1024); + + // We expect the oversized ping to be pruned when scanning the archive. + expectedPrunedInfo.push({ id: OVERSIZED_PING_ID, creationDate: new Date(OVERSIZED_PING.creationDate) }); + + // Scan the archive. + yield TelemetryController.reset(); + yield TelemetryStorage.testCleanupTaskPromise(); + yield TelemetryArchive.promiseArchivedPingList(); + // The following also checks that non oversized pings are not removed. + yield checkArchive(); + + // Make sure we're correctly updating the related histograms. + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping in the archive."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[archivedPingSizeMB], 1, + "Telemetry must report the correct size for the oversized ping."); }); add_task(function* test_clientId() { diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js index 1b5db0204a..4514ce7e9d 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js @@ -20,6 +20,7 @@ Cu.import("resource://gre/modules/Promise.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); const PING_FORMAT_VERSION = 4; +const DELETION_PING_TYPE = "deletion"; const TEST_PING_TYPE = "test-ping-type"; const PLATFORM_VERSION = "1.9.2"; @@ -140,6 +141,64 @@ add_task(function* test_simplePing() { checkPingFormat(ping, TEST_PING_TYPE, false, false); }); +add_task(function* test_disableDataUpload() { + const isUnified = Preferences.get(PREF_UNIFIED, false); + if (!isUnified) { + // Skipping the test if unified telemetry is off, as no deletion ping will + // be generated. + return; + } + + const PREF_TELEMETRY_SERVER = "toolkit.telemetry.server"; + + // Disable FHR upload: this should trigger a deletion ping. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, false); + + let ping = yield PingServer.promiseNextPing(); + checkPingFormat(ping, DELETION_PING_TYPE, true, false); + // Wait on ping activity to settle. + yield TelemetrySend.testWaitOnOutgoingPings(); + + // Restore FHR Upload. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, true); + + // Simulate a failure in sending the deletion ping by disabling the HTTP server. + yield PingServer.stop(); + + // Try to send a ping. It will be saved as pending and get deleted when disabling upload. + TelemetryController.submitExternalPing(TEST_PING_TYPE, {}); + + // Disable FHR upload to send a deletion ping again. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, false); + + // Wait on sending activity to settle, as |TelemetryController.reset()| doesn't do that. + yield TelemetrySend.testWaitOnOutgoingPings(); + // Wait for the pending pings to be deleted. Resetting TelemetryController doesn't + // trigger the shutdown, so we need to call it ourselves. + yield TelemetryStorage.shutdown(); + // Simulate a restart, and spin the send task. + yield TelemetryController.reset(); + + // Disabling Telemetry upload must clear out all the pending pings. + let pendingPings = yield TelemetryStorage.loadPendingPingList(); + Assert.equal(pendingPings.length, 1, + "All the pending pings but the deletion ping should have been deleted"); + + // Enable the ping server again. + PingServer.start(); + // We set the new server using the pref, otherwise it would get reset with + // |TelemetryController.reset|. + Preferences.set(PREF_TELEMETRY_SERVER, "http://localhost:" + PingServer.port); + + // Reset the controller to spin the ping sending task. + yield TelemetryController.reset(); + ping = yield PingServer.promiseNextPing(); + checkPingFormat(ping, DELETION_PING_TYPE, true, false); + + // Restore FHR Upload. + Preferences.set(PREF_FHR_UPLOAD_ENABLED, true); +}); + add_task(function* test_pingHasClientId() { // Send a ping with a clientId. yield sendPing(true, false); @@ -186,8 +245,15 @@ add_task(function* test_archivePings() { const uploadPref = isUnified ? PREF_FHR_UPLOAD_ENABLED : PREF_ENABLED; Preferences.set(uploadPref, false); + // If we're using unified telemetry, disabling ping upload will generate a "deletion" + // ping. Catch it. + if (isUnified) { + let ping = yield PingServer.promiseNextPing(); + checkPingFormat(ping, DELETION_PING_TYPE, true, false); + } + // Register a new Ping Handler that asserts if a ping is received, then send a ping. - registerPingHandler(() => Assert.ok(false, "Telemetry must not send pings if not allowed to.")); + PingServer.registerPingHandler(() => Assert.ok(false, "Telemetry must not send pings if not allowed to.")); let pingId = yield sendPing(true, true); // Check that the ping was archived, even with upload disabled. @@ -207,7 +273,7 @@ add_task(function* test_archivePings() { Preferences.set(uploadPref, true); Preferences.set(PREF_ARCHIVE_ENABLED, true); - now = new Date(2014, 06, 18, 22, 0, 0); + now = new Date(2014, 6, 18, 22, 0, 0); fakeNow(now); // Restore the non asserting ping handler. PingServer.resetPingHandler(); @@ -225,7 +291,7 @@ add_task(function* test_archivePings() { add_task(function* test_midnightPingSendFuzzing() { const fuzzingDelay = 60 * 60 * 1000; fakeMidnightPingFuzzingDelay(fuzzingDelay); - let now = new Date(2030, 5, 1, 11, 00, 0); + let now = new Date(2030, 5, 1, 11, 0, 0); fakeNow(now); let waitForTimer = () => new Promise(resolve => { @@ -237,21 +303,20 @@ add_task(function* test_midnightPingSendFuzzing() { PingServer.clearRequests(); yield TelemetryController.reset(); - // A ping just before the end of the fuzzing delay should not get sent. - now = new Date(2030, 5, 2, 0, 59, 59); + // A ping after midnight within the fuzzing delay should not get sent. + now = new Date(2030, 5, 2, 0, 40, 0); fakeNow(now); PingServer.registerPingHandler((req, res) => { Assert.ok(false, "No ping should be received yet."); }); let timerPromise = waitForTimer(); yield sendPing(true, true); - let [timerCallback, timerTimeout] = yield timerPromise; Assert.ok(!!timerCallback); Assert.deepEqual(futureDate(now, timerTimeout), new Date(2030, 5, 2, 1, 0, 0)); - // A ping after midnight within the fuzzing delay should also not get sent. - now = new Date(2030, 5, 2, 0, 40, 0); + // A ping just before the end of the fuzzing delay should not get sent. + now = new Date(2030, 5, 2, 0, 59, 59); fakeNow(now); timerPromise = waitForTimer(); yield sendPing(true, true); @@ -282,8 +347,7 @@ add_task(function* test_midnightPingSendFuzzing() { // Check that pings shortly before midnight are immediately sent. now = fakeNow(2030, 5, 3, 23, 59, 0); yield sendPing(true, true); - request = yield gRequestIterator.next(); - ping = decodeRequestPayload(request); + ping = yield PingServer.promiseNextPing(); checkPingFormat(ping, TEST_PING_TYPE, true, true); yield TelemetrySend.testWaitOnOutgoingPings(); @@ -292,6 +356,52 @@ add_task(function* test_midnightPingSendFuzzing() { fakePingSendTimer(() => {}, () => {}); }); +add_task(function* test_changePingAfterSubmission() { + // Submit a ping with a custom payload. + let payload = { canary: "test" }; + let pingPromise = TelemetryController.submitExternalPing(TEST_PING_TYPE, payload, options); + + // Change the payload with a predefined value. + payload.canary = "changed"; + + // Wait for the ping to be archived. + const pingId = yield pingPromise; + + // Make sure our changes didn't affect the submitted payload. + let archivedCopy = yield TelemetryArchive.promiseArchivedPingById(pingId); + Assert.equal(archivedCopy.payload.canary, "test", + "The payload must not be changed after being submitted."); +}); + +add_task(function* test_telemetryEnabledUnexpectedValue(){ + // Remove the default value for toolkit.telemetry.enabled from the default prefs. + // Otherwise, we wouldn't be able to set the pref to a string. + let defaultPrefBranch = Services.prefs.getDefaultBranch(null); + defaultPrefBranch.deleteBranch(PREF_ENABLED); + + // Set the preferences controlling the Telemetry status to a string. + Preferences.set(PREF_ENABLED, "false"); + // Check that Telemetry is not enabled. + yield TelemetryController.reset(); + Assert.equal(Telemetry.canRecordExtended, false, + "Invalid values must not enable Telemetry recording."); + + // Delete the pref again. + defaultPrefBranch.deleteBranch(PREF_ENABLED); + + // Make sure that flipping it to true works. + Preferences.set(PREF_ENABLED, true); + yield TelemetryController.reset(); + Assert.equal(Telemetry.canRecordExtended, true, + "True must enable Telemetry recording."); + + // Also check that the false works as well. + Preferences.set(PREF_ENABLED, false); + yield TelemetryController.reset(); + Assert.equal(Telemetry.canRecordExtended, false, + "False must disable Telemetry recording."); +}); + add_task(function* test_telemetryCleanFHRDatabase(){ const FHR_DBNAME_PREF = "datareporting.healthreport.dbName"; const CUSTOM_DB_NAME = "unlikely.to.be.used.sqlite"; diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js b/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js index 923a8640f7..533d460314 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryControllerShutdown.js @@ -50,11 +50,13 @@ add_task(function* test_sendTimeout() { yield TelemetryController.setup(); TelemetrySend.setServer("http://localhost:" + httpServer.identity.primaryPort); - yield TelemetryController.submitExternalPing("test-ping-type", {}); + let submissionPromise = TelemetryController.submitExternalPing("test-ping-type", {}); // Trigger the AsyncShutdown phase TelemetryController hangs off. AsyncShutdown.profileBeforeChange._trigger(); AsyncShutdown.sendTelemetry._trigger(); + // Now wait for the ping submission. + yield submissionPromise; // If we get here, we didn't time out in the shutdown routines. Assert.ok(true, "Didn't time out on shutdown."); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js index 1131f67eff..f2b2b3bba4 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLateWrites.js @@ -9,7 +9,7 @@ Cu.import("resource://gre/modules/Services.jsm", this); const PR_WRONLY = 0x2; const PR_CREATE_FILE = 0x8; const PR_TRUNCATE = 0x20; -const RW_OWNER = 0600; +const RW_OWNER = parseInt("0600", 8); const STACK_SUFFIX1 = "stack1.txt"; const STACK_SUFFIX2 = "stack2.txt"; diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js index e41b074987..808f2f3ec9 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLockCount.js @@ -12,7 +12,7 @@ const N_FAILED_LOCKS = 10; const PR_WRONLY = 0x2; const PR_CREATE_FILE = 0x8; const PR_TRUNCATE = 0x20; -const RW_OWNER = 0600; +const RW_OWNER = parseInt("0600", 8); function write_string_to_file(file, contents) { let ostream = Cc["@mozilla.org/network/safe-file-output-stream;1"] diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js index 44d73b296d..4f9d385485 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js @@ -24,8 +24,9 @@ function check_event(event, id, data) } } -function* run_test() +add_task(function* () { + do_get_profile(); yield TelemetrySession.setup(); TelemetryLog.log(TEST_PREFIX + "1", ["val", 123, undefined]); @@ -45,4 +46,4 @@ function* run_test() do_check_true(log[1][1] <= log[2][1]); yield TelemetrySession.shutdown(); -} +}); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js b/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js index b9e57bc7ae..0f9c1eff6d 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryReportingPolicy.js @@ -14,6 +14,7 @@ Cu.import("resource://gre/modules/TelemetryReportingPolicy.jsm", this); Cu.import("resource://gre/modules/TelemetryUtils.jsm", this); Cu.import("resource://gre/modules/Timer.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); +Cu.import("resource://gre/modules/UpdateUtils.jsm", this); const PREF_BRANCH = "toolkit.telemetry."; const PREF_SERVER = PREF_BRANCH + "server"; @@ -40,6 +41,21 @@ function fakeResetAcceptedPolicy() { Preferences.reset(PREF_ACCEPTED_POLICY_VERSION); } +function setMinimumPolicyVersion(aNewPolicyVersion) { + const CHANNEL_NAME = UpdateUtils.getUpdateChannel(false); + // We might have channel-dependent minimum policy versions. + const CHANNEL_DEPENDENT_PREF = PREF_MINIMUM_POLICY_VERSION + ".channel-" + CHANNEL_NAME; + + // Does the channel-dependent pref exist? If so, set its value. + if (Preferences.get(CHANNEL_DEPENDENT_PREF, undefined)) { + Preferences.set(CHANNEL_DEPENDENT_PREF, aNewPolicyVersion); + return; + } + + // We don't have a channel specific minimu, so set the common one. + Preferences.set(PREF_MINIMUM_POLICY_VERSION, aNewPolicyVersion); +} + function run_test() { // Addon manager needs a profile directory do_get_profile(true); @@ -109,7 +125,7 @@ add_task(function* test_prefs() { // Set a new minimum policy version and check that user is no longer notified. let newMinimum = Preferences.get(PREF_CURRENT_POLICY_VERSION, 1) + 1; - Preferences.set(PREF_MINIMUM_POLICY_VERSION, newMinimum); + setMinimumPolicyVersion(newMinimum); Assert.ok(!TelemetryReportingPolicy.testIsUserNotified(), "A greater minimum policy version must invalidate the policy and disable upload."); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js index 410926cc51..2e7bbdde97 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js @@ -170,6 +170,21 @@ add_task(function* test_sendPendingPings() { countByType = countPingTypes(pings); Assert.equal(countByType.get(TEST_TYPE_A), 5, "Should have received the correct amount of type A pings"); + + yield TelemetrySend.testWaitOnOutgoingPings(); + PingServer.resetPingHandler(); +}); + +add_task(function* test_sendDateHeader() { + let now = fakeNow(new Date(Date.UTC(2011, 1, 1, 11, 0, 0))); + yield TelemetrySend.reset(); + + let pingId = yield TelemetryController.submitExternalPing("test-send-date-header", {}); + let req = yield PingServer.promiseNextRequest(); + let ping = decodeRequestPayload(req); + Assert.equal(req.getHeader("Date"), "Tue, 01 Feb 2011 11:00:00 GMT", + "Telemetry should send the correct Date header with requests."); + Assert.equal(ping.id, pingId, "Should have received the correct ping id."); }); // Test the backoff timeout behavior after send failures. @@ -235,6 +250,69 @@ add_task(function* test_backoffTimeout() { Assert.equal(TelemetrySend.pendingPingCount, 0, "Should have no pending pings left"); }); +add_task(function* test_discardBigPings() { + const TEST_PING_TYPE = "test-ping-type"; + + // Generate a 2MB string and create an oversized payload. + const OVERSIZED_PAYLOAD = generateRandomString(2 * 1024 * 1024); + + // Reset the histograms. + Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").clear(); + Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB").clear(); + + // Submit a ping of a normal size and check that we don't count it in the histogram. + yield TelemetryController.submitExternalPing(TEST_PING_TYPE, { test: "test" }); + yield TelemetrySend.testWaitOnOutgoingPings(); + let h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report no oversized ping submitted."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.sum, 0, "Telemetry must report no oversized pings."); + + // Submit an oversized ping and check that it gets discarded. + yield TelemetryController.submitExternalPing(TEST_PING_TYPE, OVERSIZED_PAYLOAD); + yield TelemetrySend.testWaitOnOutgoingPings(); + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping submitted."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[2], 1, "Telemetry must report a 2MB, oversized, ping submitted."); +}); + +add_task(function* test_evictedOnServerErrors() { + const TEST_TYPE = "test-evicted"; + + yield TelemetrySend.reset(); + + // Write a custom ping handler which will return 403. This will trigger ping eviction + // on client side. + PingServer.registerPingHandler((req, res) => { + res.setStatusLine(null, 403, "Forbidden"); + res.processAsync(); + res.finish(); + }); + + // Clear the histogram and submit a ping. + Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").clear(); + let pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {}); + yield TelemetrySend.testWaitOnOutgoingPings(); + + let h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors"); + + // The ping should not be persisted. + yield Assert.rejects(TelemetryStorage.loadPendingPing(pingId), "The ping must not be persisted."); + + // Reset the ping handler and submit a new ping. + PingServer.resetPingHandler(); + pingId = yield TelemetryController.submitExternalPing(TEST_TYPE, {}); + + let ping = yield PingServer.promiseNextPings(1); + Assert.equal(ping[0].id, pingId, "The correct ping must be received"); + + // We should not have updated the error histogram. + h = Telemetry.getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report a ping evicted due to server errors"); +}); + // Test that the current, non-persisted pending pings are properly saved on shutdown. add_task(function* test_persistCurrentPingsOnShutdown() { const TEST_TYPE = "test-persistCurrentPingsOnShutdown"; diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js index 6d85245219..52d6c3511f 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js @@ -17,7 +17,7 @@ Cu.import("resource://gre/modules/TelemetryController.jsm", this); Cu.import("resource://gre/modules/TelemetrySend.jsm", this); Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -let {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {}); +var {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {}); // We increment TelemetryStorage's MAX_PING_FILE_AGE and // OVERDUE_PING_FILE_AGE by 1 minute so that our test pings exceed @@ -33,8 +33,10 @@ const RECENT_PINGS = 4; const TOTAL_EXPECTED_PINGS = OVERDUE_PINGS + RECENT_PINGS + OLD_FORMAT_PINGS; -let gCreatedPings = 0; -let gSeenPings = 0; +const PREF_FHR_UPLOAD = "datareporting.healthreport.uploadEnabled"; + +var gCreatedPings = 0; +var gSeenPings = 0; /** * Creates some Telemetry pings for the and saves them to disk. Each ping gets a @@ -48,13 +50,13 @@ let gSeenPings = 0; * @returns Promise * @resolve an Array with the created pings ids. */ -let createSavedPings = Task.async(function* (aPingInfos) { +var createSavedPings = Task.async(function* (aPingInfos) { let pingIds = []; let now = Date.now(); for (let type in aPingInfos) { let num = aPingInfos[type].num; - let age = now - aPingInfos[type].age; + let age = now - (aPingInfos[type].age || 0); for (let i = 0; i < num; ++i) { let pingId = yield TelemetryController.addPendingPing("test-ping", {}, { overwrite: true }); if (aPingInfos[type].age) { @@ -77,7 +79,7 @@ let createSavedPings = Task.async(function* (aPingInfos) { * @param aPingIds an Array of ping ids to delete. * @returns Promise */ -let clearPings = Task.async(function* (aPingIds) { +var clearPings = Task.async(function* (aPingIds) { for (let pingId of aPingIds) { yield TelemetryStorage.removePendingPing(pingId); } @@ -118,7 +120,7 @@ function assertReceivedPings(aExpectedNum) { * @param aPingIds an Array of pings ids to check. * @returns Promise */ -let assertNotSaved = Task.async(function* (aPingIds) { +var assertNotSaved = Task.async(function* (aPingIds) { let saved = 0; for (let id of aPingIds) { let filePath = getSavePathForPingId(id); @@ -145,7 +147,7 @@ function pingHandler(aRequest) { /** * Clear out all pending pings. */ -let clearPendingPings = Task.async(function*() { +var clearPendingPings = Task.async(function*() { const pending = yield TelemetryStorage.loadPendingPingList(); for (let p of pending) { yield TelemetryStorage.removePendingPing(p.id); @@ -156,6 +158,7 @@ function run_test() { PingServer.start(); PingServer.registerPingHandler(pingHandler); do_get_profile(); + loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true); Services.prefs.setCharPref(TelemetryController.Constants.PREF_SERVER, @@ -168,6 +171,9 @@ function run_test() { * |TelemetryController.testSaveDirectoryToFile| could fail. */ add_task(function* setupEnvironment() { + // The following tests assume this pref to be true by default. + Services.prefs.setBoolPref(PREF_FHR_UPLOAD, true); + yield TelemetryController.setup(); let directory = TelemetryStorage.pingDirectoryPath; @@ -389,7 +395,6 @@ add_task(function* test_overdue_old_format() { add_task(function* test_pendingPingsQuota() { const PING_TYPE = "foo"; - const PREF_FHR_UPLOAD = "datareporting.healthreport.uploadEnabled"; // Disable upload so pings don't get sent and removed from the pending pings directory. Services.prefs.setBoolPref(PREF_FHR_UPLOAD, false); @@ -501,6 +506,48 @@ add_task(function* test_pendingPingsQuota() { yield TelemetryStorage.testPendingQuotaTaskPromise(); yield checkPendingPings(); + const OVERSIZED_PING_ID = "9b21ec8f-f762-4d28-a2c1-44e1c4694f24"; + // Create a pending oversized ping. + const OVERSIZED_PING = { + id: OVERSIZED_PING_ID, + type: PING_TYPE, + creationDate: (new Date()).toISOString(), + // Generate a 2MB string to use as the ping payload. + payload: generateRandomString(2 * 1024 * 1024), + }; + yield TelemetryStorage.savePendingPing(OVERSIZED_PING); + + // Reset the histograms. + Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").clear(); + Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").clear(); + + // Try to manually load the oversized ping. + yield Assert.rejects(TelemetryStorage.loadPendingPing(OVERSIZED_PING_ID), + "The oversized ping should have been pruned."); + Assert.ok(!(yield OS.File.exists(getSavePathForPingId(OVERSIZED_PING_ID))), + "The ping should not be on the disk anymore."); + + // Make sure we're correctly updating the related histograms. + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").snapshot(); + Assert.equal(h.sum, 1, "Telemetry must report 1 oversized ping in the pending pings directory."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[2], 1, "Telemetry must report a 2MB, oversized, ping."); + + // Save the ping again to check if it gets pruned when scanning the pings directory. + yield TelemetryStorage.savePendingPing(OVERSIZED_PING); + expectedPrunedPings.push(OVERSIZED_PING_ID); + + // Scan the pending pings directory. + yield TelemetryController.reset(); + yield TelemetryStorage.testPendingQuotaTaskPromise(); + yield checkPendingPings(); + + // Make sure we're correctly updating the related histograms. + h = Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").snapshot(); + Assert.equal(h.sum, 2, "Telemetry must report 1 oversized ping in the pending pings directory."); + h = Telemetry.getHistogramById("TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB").snapshot(); + Assert.equal(h.counts[2], 2, "Telemetry must report two 2MB, oversized, pings."); + Services.prefs.setBoolPref(PREF_FHR_UPLOAD, true); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js index a7c75af65d..b996fa0a90 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js @@ -901,6 +901,8 @@ add_task(function* test_dailyDuplication() { return; } + yield TelemetrySend.reset(); + yield clearPendingPings(); PingServer.clearRequests(); let schedulerTickCallback = null; @@ -943,8 +945,8 @@ add_task(function* test_dailyDuplication() { yield schedulerTickCallback(); // Shutdown to cleanup the aborted-session if it gets created. - yield TelemetrySession.shutdown(); PingServer.resetPingHandler(); + yield TelemetrySession.shutdown(); }); add_task(function* test_dailyOverdue() { @@ -979,7 +981,7 @@ add_task(function* test_dailyOverdue() { // Simulate an overdue ping: we're not close to midnight, but the last daily ping // time is too long ago. - let dailyOverdue = new Date(2030, 1, 2, 13, 00, 0); + let dailyOverdue = new Date(2030, 1, 2, 13, 0, 0); fakeNow(dailyOverdue); // Run a scheduler tick: it should trigger the daily ping. @@ -1007,7 +1009,7 @@ add_task(function* test_environmentChange() { let timerCallback = null; let timerDelay = null; - clearPendingPings(); + yield clearPendingPings(); yield TelemetrySend.reset(); PingServer.clearRequests(); @@ -1083,6 +1085,8 @@ add_task(function* test_savedPingsOnShutdown() { const dir = TelemetryStorage.pingDirectoryPath; yield OS.File.removeDir(dir, {ignoreAbsent: true}); yield OS.File.makeDir(dir); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); PingServer.clearRequests(); @@ -1157,6 +1161,9 @@ add_task(function* test_savedSessionData() { Assert.equal(payload.info.profileSubsessionCounter, expectedSubsessions); yield TelemetrySession.shutdown(); + // Restore the UUID generator so we don't mess with other tests. + fakeGenerateUUID(generateUUID, generateUUID); + // Load back the serialised session data. let data = yield CommonUtils.readJSON(dataFilePath); Assert.equal(data.profileSubsessionCounter, expectedSubsessions); @@ -1255,6 +1262,9 @@ add_task(function* test_invalidSessionData() { yield TelemetrySession.shutdown(); + // Restore the UUID generator so we don't mess with other tests. + fakeGenerateUUID(generateUUID, generateUUID); + // Load back the serialised session data. let data = yield CommonUtils.readJSON(dataFilePath); Assert.equal(data.profileSubsessionCounter, expectedSubsessions); @@ -1262,61 +1272,6 @@ add_task(function* test_invalidSessionData() { Assert.equal(data.subsessionId, expectedSubsessionUUID); }); -add_task(function* test_pingExtendedStats() { - const EXTENDED_PAYLOAD_FIELDS = [ - "chromeHangs", "threadHangStats", "log", "slowSQL", "fileIOReports", "lateWrites", - "addonHistograms", "addonDetails", "UIMeasurements", - ]; - - // Disable sending extended statistics. - Telemetry.canRecordExtended = false; - - gRequestIterator = Iterator(new Request()); - yield TelemetrySession.reset(); - yield sendPing(); - - let request = yield gRequestIterator.next(); - let ping = decodeRequestPayload(request); - checkPingFormat(ping, PING_TYPE_MAIN, true, true); - - // Check that the payload does not contain extended statistics fields. - for (let f in EXTENDED_PAYLOAD_FIELDS) { - Assert.ok(!(EXTENDED_PAYLOAD_FIELDS[f] in ping.payload), - EXTENDED_PAYLOAD_FIELDS[f] + " must not be in the payload if the extended set is off."); - } - - // We check this one separately so that we can reuse EXTENDED_PAYLOAD_FIELDS below, since - // slowSQLStartup might not be there. - Assert.ok(!("slowSQLStartup" in ping.payload), - "slowSQLStartup must not be sent if the extended set is off"); - - Assert.ok(!("addonManager" in ping.payload.simpleMeasurements), - "addonManager must not be sent if the extended set is off."); - Assert.ok(!("UITelemetry" in ping.payload.simpleMeasurements), - "UITelemetry must not be sent if the extended set is off."); - - // Restore the preference. - Telemetry.canRecordExtended = true; - - // Send a new ping that should contain the extended data. - yield TelemetrySession.reset(); - yield sendPing(); - request = yield gRequestIterator.next(); - ping = decodeRequestPayload(request); - checkPingFormat(ping, PING_TYPE_MAIN, true, true); - - // Check that the payload now contains extended statistics fields. - for (let f in EXTENDED_PAYLOAD_FIELDS) { - Assert.ok(EXTENDED_PAYLOAD_FIELDS[f] in ping.payload, - EXTENDED_PAYLOAD_FIELDS[f] + " must be in the payload if the extended set is on."); - } - - Assert.ok("addonManager" in ping.payload.simpleMeasurements, - "addonManager must be sent if the extended set is on."); - Assert.ok("UITelemetry" in ping.payload.simpleMeasurements, - "UITelemetry must be sent if the extended set is on."); -}); - add_task(function* test_abortedSession() { if (gIsAndroid || gIsGonk) { // We don't have the aborted session ping here. @@ -1370,6 +1325,8 @@ add_task(function* test_abortedSession() { Assert.notEqual(abortedSessionPing.id, updatedAbortedSessionPing.id); Assert.notEqual(abortedSessionPing.creationDate, updatedAbortedSessionPing.creationDate); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); Assert.ok(!(yield OS.File.exists(ABORTED_FILE)), "No aborted session ping must be available after a shutdown."); @@ -1380,6 +1337,8 @@ add_task(function* test_abortedSession() { yield clearPendingPings(); PingServer.clearRequests(); + // TODO: Remove the TelemetrySend manual setup when bug 1145188 lands. + yield TelemetrySend.setup(true); yield TelemetrySession.reset(); yield TelemetryController.reset(); @@ -1391,6 +1350,8 @@ add_task(function* test_abortedSession() { Assert.equal(receivedPing.type, PING_TYPE_MAIN, "Should have the correct type"); Assert.equal(receivedPing.payload.info.reason, REASON_ABORTED_SESSION, "Ping should have the correct reason"); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1406,6 +1367,9 @@ add_task(function* test_abortedSession_Shutdown() { let now = fakeNow(2040, 1, 1, 0, 0, 0); // Fake scheduler functions to control aborted-session flow in tests. fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); yield TelemetrySession.reset(); Assert.ok((yield OS.File.exists(DATAREPORTING_PATH)), @@ -1424,6 +1388,8 @@ add_task(function* test_abortedSession_Shutdown() { // not found) do not compromise the shutdown. yield OS.File.remove(ABORTED_FILE); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1441,11 +1407,14 @@ add_task(function* test_abortedDailyCoalescing() { let schedulerTickCallback = null; PingServer.clearRequests(); - let nowDate = new Date(2009, 10, 18, 00, 00, 0); + let nowDate = new Date(2009, 10, 18, 0, 0, 0); fakeNow(nowDate); // Fake scheduler functions to control aborted-session flow in tests. fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); yield TelemetrySession.reset(); Assert.ok((yield OS.File.exists(DATAREPORTING_PATH)), @@ -1475,6 +1444,8 @@ add_task(function* test_abortedDailyCoalescing() { Assert.equal(abortedSessionPing.payload.info.sessionId, dailyPing.payload.info.sessionId); Assert.equal(abortedSessionPing.payload.info.subsessionId, dailyPing.payload.info.subsessionId); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1486,31 +1457,56 @@ add_task(function* test_schedulerComputerSleep() { const ABORTED_FILE = OS.Path.join(DATAREPORTING_PATH, ABORTED_PING_FILE_NAME); + clearPendingPings(); + // TODO: Remove the TelemetrySend manual setup when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); PingServer.clearRequests(); // Remove any aborted-session ping from the previous tests. yield OS.File.removeDir(DATAREPORTING_PATH, { ignoreAbsent: true }); // Set a fake current date and start Telemetry. - let nowDate = new Date(2060, 10, 18, 0, 00, 0); - fakeNow(nowDate); + let nowDate = fakeNow(2009, 10, 18, 0, 0, 0); let schedulerTickCallback = null; fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); yield TelemetrySession.reset(); // Set the current time 3 days in the future at midnight, before running the callback. - let future = futureDate(nowDate, MS_IN_ONE_DAY * 3); - fakeNow(future); + nowDate = fakeNow(futureDate(nowDate, 3 * MS_IN_ONE_DAY)); Assert.ok(!!schedulerTickCallback); // Execute one scheduler tick. yield schedulerTickCallback(); let dailyPing = yield PingServer.promiseNextPing(); - Assert.equal(dailyPing.payload.info.reason, REASON_DAILY); + Assert.equal(dailyPing.payload.info.reason, REASON_DAILY, + "The wake notification should have triggered a daily ping."); + Assert.equal(dailyPing.creationDate, nowDate.toISOString(), + "The daily ping date should be correct."); Assert.ok((yield OS.File.exists(ABORTED_FILE)), "There must be an aborted session ping."); + // Now also test if we are sending a daily ping if we wake up on the next + // day even when the timer doesn't trigger. + // This can happen due to timeouts not running out during sleep times, + // see bug 1262386, bug 1204823 et al. + // Note that we don't get wake notifications on Linux due to bug 758848. + nowDate = fakeNow(futureDate(nowDate, 1 * MS_IN_ONE_DAY)); + + // We emulate the mentioned timeout behavior by sending the wake notification + // instead of triggering the timeout callback. + // This should trigger a daily ping, because we passed midnight. + Services.obs.notifyObservers(null, "wake_notification", null); + + dailyPing = yield PingServer.promiseNextPing(); + Assert.equal(dailyPing.payload.info.reason, REASON_DAILY, + "The wake notification should have triggered a daily ping."); + Assert.equal(dailyPing.creationDate, nowDate.toISOString(), + "The daily ping date should be correct."); + + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1529,9 +1525,12 @@ add_task(function* test_schedulerEnvironmentReschedules() { yield clearPendingPings(); PingServer.clearRequests(); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); // Set a fake current date and start Telemetry. - let nowDate = new Date(2009, 10, 18, 0, 00, 0); + let nowDate = new Date(2060, 10, 18, 0, 0, 0); fakeNow(nowDate); let schedulerTickCallback = null; fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); @@ -1557,6 +1556,8 @@ add_task(function* test_schedulerEnvironmentReschedules() { Assert.ok(!!schedulerTickCallback); yield schedulerTickCallback(); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); @@ -1571,6 +1572,9 @@ add_task(function* test_schedulerNothingDue() { // Remove any aborted-session ping from the previous tests. yield OS.File.removeDir(DATAREPORTING_PATH, { ignoreAbsent: true }); yield clearPendingPings(); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); // We don't expect to receive any ping in this test, so assert if we do. PingServer.registerPingHandler((req, res) => { @@ -1595,12 +1599,8 @@ add_task(function* test_schedulerNothingDue() { // Check that no aborted session ping was written to disk. Assert.ok(!(yield OS.File.exists(ABORTED_FILE))); - // We should not miss midnight when going to idle. - now.setHours(23); - now.setMinutes(50); - fakeIdleNotification("idle"); - Assert.equal(schedulerTimeout, 10 * 60 * 1000); - + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); PingServer.resetPingHandler(); }); @@ -1616,6 +1616,9 @@ add_task(function* test_pingExtendedStats() { yield clearPendingPings(); PingServer.clearRequests(); + // TODO: Remove the TelemetrySend manual setup/reset when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); yield TelemetrySession.reset(); yield sendPing(); @@ -1697,9 +1700,122 @@ add_task(function* test_schedulerUserIdle() { // We should not miss midnight when going to idle. now.setHours(23); now.setMinutes(50); + fakeNow(now); fakeIdleNotification("idle"); Assert.equal(schedulerTimeout, 10 * 60 * 1000); + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); + yield TelemetrySession.shutdown(); +}); + +add_task(function* test_DailyDueAndIdle() { + if (gIsAndroid || gIsGonk) { + // We don't have the aborted session or the daily ping here. + return; + } + + yield TelemetrySession.reset(); + yield clearPendingPings(); + PingServer.clearRequests(); + // TODO: Remove the TelemetrySend setup when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); + + let receivedPingRequest = null; + // Register a ping handler that will assert when receiving multiple daily pings. + PingServer.registerPingHandler(req => { + Assert.ok(!receivedPingRequest, "Telemetry must only send one daily ping."); + receivedPingRequest = req; + }); + + let schedulerTickCallback = null; + let now = new Date(2030, 1, 1, 0, 0, 0); + fakeNow(now); + // Fake scheduler functions to control daily collection flow in tests. + fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + yield TelemetrySession.setup(); + + // Trigger the daily ping. + let firstDailyDue = new Date(2030, 1, 2, 0, 0, 0); + fakeNow(firstDailyDue); + + // Run a scheduler tick: it should trigger the daily ping. + Assert.ok(!!schedulerTickCallback); + let tickPromise = schedulerTickCallback(); + + // Send an idle and then an active user notification. + fakeIdleNotification("idle"); + fakeIdleNotification("active"); + + // Wait on the tick promise. + yield tickPromise; + + yield TelemetrySend.testWaitOnOutgoingPings(); + + // Decode the ping contained in the request and check that's a daily ping. + Assert.ok(receivedPingRequest, "Telemetry must send one daily ping."); + const receivedPing = decodeRequestPayload(receivedPingRequest); + checkPingFormat(receivedPing, PING_TYPE_MAIN, true, true); + Assert.equal(receivedPing.payload.info.reason, REASON_DAILY); + + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); + yield TelemetrySession.shutdown(); +}); + +add_task(function* test_userIdleAndSchedlerTick() { + if (gIsAndroid || gIsGonk) { + // We don't have the aborted session or the daily ping here. + return; + } + + yield TelemetrySession.reset(); + yield clearPendingPings(); + PingServer.clearRequests(); + // TODO: Remove the TelemetrySend setup when bug 1145188 lands. + yield TelemetrySend.setup(true); + yield TelemetrySend.reset(); + + let receivedPingRequest = null; + // Register a ping handler that will assert when receiving multiple daily pings. + PingServer.registerPingHandler(req => { + Assert.ok(!receivedPingRequest, "Telemetry must only send one daily ping."); + receivedPingRequest = req; + }); + + let schedulerTickCallback = null; + let now = new Date(2030, 1, 1, 0, 0, 0); + fakeNow(now); + // Fake scheduler functions to control daily collection flow in tests. + fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {}); + yield TelemetrySession.setup(); + + // Move the current date/time to midnight. + let firstDailyDue = new Date(2030, 1, 2, 0, 0, 0); + fakeNow(firstDailyDue); + + // The active notification should trigger a scheduler tick. The latter will send the + // due daily ping. + fakeIdleNotification("active"); + + // Immediately running another tick should not send a daily ping again. + Assert.ok(!!schedulerTickCallback); + yield schedulerTickCallback(); + + // A new "idle" notification should not send a new daily ping. + fakeIdleNotification("idle"); + + yield TelemetrySend.testWaitOnOutgoingPings(); + + // Decode the ping contained in the request and check that's a daily ping. + Assert.ok(receivedPingRequest, "Telemetry must send one daily ping."); + const receivedPing = decodeRequestPayload(receivedPingRequest); + checkPingFormat(receivedPing, PING_TYPE_MAIN, true, true); + Assert.equal(receivedPing.payload.info.reason, REASON_DAILY); + + // TODO: Remove the TelemetrySend manual shutdown when bug 1145188 lands. + yield TelemetrySend.shutdown(); yield TelemetrySession.shutdown(); }); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js b/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js index 07265fecf6..69c8efa0f1 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js @@ -1,18 +1,19 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -let tmpScope = {}; +var tmpScope = {}; Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", tmpScope); -let TelemetryStopwatch = tmpScope.TelemetryStopwatch; +var TelemetryStopwatch = tmpScope.TelemetryStopwatch; // We can't create a histogram here since the ones created with // newHistogram are not seen by getHistogramById that the module uses. const HIST_NAME = "TELEMETRY_PING"; const HIST_NAME2 = "RANGE_CHECKSUM_ERRORS"; +const KEYED_HIST = { id: "TELEMETRY_INVALID_PING_TYPE_SUBMITTED", key: "TEST" }; -let refObj = {}, refObj2 = {}; +var refObj = {}, refObj2 = {}; -let originalCount1, originalCount2; +var originalCount1, originalCount2; function run_test() { let histogram = Telemetry.getHistogramById(HIST_NAME); @@ -23,6 +24,10 @@ function run_test() { snapshot = histogram.snapshot(); originalCount2 = snapshot.counts.reduce((a,b) => a += b); + histogram = Telemetry.getKeyedHistogramById(KEYED_HIST.id); + snapshot = histogram.snapshot(KEYED_HIST.key); + originalCount3 = snapshot.counts.reduce((a,b) => a += b); + do_check_false(TelemetryStopwatch.start(3)); do_check_false(TelemetryStopwatch.start({})); do_check_false(TelemetryStopwatch.start("", 3)); @@ -44,16 +49,10 @@ function run_test() { do_check_false(TelemetryStopwatch.finish("mark1", refObj)); do_check_true(TelemetryStopwatch.start("NON-EXISTENT_HISTOGRAM")); - try { - TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM"); - do_throw("Non-existent histogram name should throw an error."); - } catch (e) {} + do_check_false(TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM")); do_check_true(TelemetryStopwatch.start("NON-EXISTENT_HISTOGRAM", refObj)); - try { - TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM", refObj); - do_throw("Non-existent histogram name should throw an error."); - } catch (e) {} + do_check_false(TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM", refObj)); do_check_true(TelemetryStopwatch.start(HIST_NAME)); do_check_true(TelemetryStopwatch.start(HIST_NAME2)); @@ -97,6 +96,44 @@ function run_test() { do_check_false(TelemetryStopwatch.finish(HIST_NAME)); do_check_false(TelemetryStopwatch.finish(HIST_NAME, refObj)); + // Verify that keyed stopwatch reject invalid keys. + for (let key of [3, {}, ""]) { + do_check_false(TelemetryStopwatch.startKeyed(KEYED_HIST.id, key)); + } + + // Verify that keyed histograms can be started. + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1")); + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY2")); + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1", refObj)); + do_check_true(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY2", refObj)); + + // Restarting keyed histograms should fail. + do_check_false(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1")); + do_check_false(TelemetryStopwatch.startKeyed("HISTOGRAM", "KEY1", refObj)); + + // Finishing a stopwatch of a non existing histogram should return false. + do_check_false(TelemetryStopwatch.finishKeyed("HISTOGRAM", "KEY2")); + do_check_false(TelemetryStopwatch.finishKeyed("HISTOGRAM", "KEY2", refObj)); + + // Starting & finishing a keyed stopwatch for an existing histogram should work. + do_check_true(TelemetryStopwatch.startKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_true(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, KEYED_HIST.key)); + // Verify that TS.finish deleted the timers + do_check_false(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, KEYED_HIST.key)); + + // Verify that they can be used again + do_check_true(TelemetryStopwatch.startKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_true(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, KEYED_HIST.key)); + + do_check_false(TelemetryStopwatch.finishKeyed("unknown-mark", "unknown-key")); + do_check_false(TelemetryStopwatch.finishKeyed(KEYED_HIST.id, "unknown-key")); + + // Verify that keyed histograms can only be canceled through "keyed" API. + do_check_true(TelemetryStopwatch.startKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_false(TelemetryStopwatch.cancel(KEYED_HIST.id, KEYED_HIST.key)); + do_check_true(TelemetryStopwatch.cancelKeyed(KEYED_HIST.id, KEYED_HIST.key)); + do_check_false(TelemetryStopwatch.cancelKeyed(KEYED_HIST.id, KEYED_HIST.key)); + finishTest(); } @@ -112,4 +149,10 @@ function finishTest() { newCount = snapshot.counts.reduce((a,b) => a += b); do_check_eq(newCount - originalCount2, 3, "The correct number of histograms were added for histogram 2."); + + histogram = Telemetry.getKeyedHistogramById(KEYED_HIST.id); + snapshot = histogram.snapshot(KEYED_HIST.key); + newCount = snapshot.counts.reduce((a,b) => a += b); + + do_check_eq(newCount - originalCount3, 2, "The correct number of histograms were added for histogram 3."); } diff --git a/toolkit/components/telemetry/tests/unit/test_ThirdPartyCookieProbe.js b/toolkit/components/telemetry/tests/unit/test_ThirdPartyCookieProbe.js deleted file mode 100644 index 4b02e612c1..0000000000 --- a/toolkit/components/telemetry/tests/unit/test_ThirdPartyCookieProbe.js +++ /dev/null @@ -1,166 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -Cu.import("resource://gre/modules/Services.jsm", this); -Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); -Cu.import("resource://gre/modules/ThirdPartyCookieProbe.jsm", this); -Cu.import("resource://gre/modules/Promise.jsm", this); -Cu.import("resource://gre/modules/TelemetryPing.jsm", this); - -let TOPIC_ACCEPTED = "third-party-cookie-accepted"; -let TOPIC_REJECTED = "third-party-cookie-rejected"; - -let FLUSH_MILLISECONDS = 1000 * 60 * 60 * 24 / 2; /*Half a day, for testing purposes*/ - -const NUMBER_OF_REJECTS = 30; -const NUMBER_OF_ACCEPTS = 17; -const NUMBER_OF_REPEATS = 5; - -let gCookieService; -let gThirdPartyCookieProbe; - -let gHistograms = { - clear: function() { - this.sitesAccepted.clear(); - this.requestsAccepted.clear(); - this.sitesRejected.clear(); - this.requestsRejected.clear(); - } -}; - -function run_test() { - do_print("Initializing environment"); - do_get_profile(); - createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); - gCookieService = Cc["@mozilla.org/cookieService;1"].getService(Ci.nsICookieService); - - do_print("Initializing ThirdPartyCookieProbe.jsm"); - gThirdPartyCookieProbe = new ThirdPartyCookieProbe(); - gThirdPartyCookieProbe.init(); - - do_print("Acquiring histograms"); - gHistograms.sitesAccepted = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_ACCEPTED"); - gHistograms.sitesRejected = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_SITES_BLOCKED"), - gHistograms.requestsAccepted = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_ACCEPTED"); - gHistograms.requestsRejected = - Services.telemetry.getHistogramById("COOKIES_3RDPARTY_NUM_ATTEMPTS_BLOCKED"), - - - run_next_test(); -} - -/** - * Utility function: try to set a cookie with the given document uri and referrer uri. - * - * @param obj An object with the following fields - * - {string} request The uri of the request setting the cookie. - * - {string} referrer The uri of the referrer for this request. - */ -function tryToSetCookie(obj) { - let requestURI = Services.io.newURI(obj.request, null, null); - let referrerURI = Services.io.newURI(obj.referrer, null, null); - let requestChannel = Services.io.newChannelFromURI2(requestURI, - null, // aLoadingNode - Services.scriptSecurityManager.getSystemPrincipal(), - null, // aTriggeringPrincipal - Ci.nsILoadInfo.SEC_NORMAL, - Ci.nsIContentPolicy.TYPE_OTHER); - gCookieService.setCookieString(referrerURI, null, "Is there a cookie in my jar?", requestChannel); -} - -function wait(ms) { - let deferred = Promise.defer(); - do_timeout(ms, () => deferred.resolve()); - return deferred.promise; -} - -function oneTest(tld, flushUptime, check) { - gHistograms.clear(); - do_print("Testing with tld " + tld); - - do_print("Adding rejected entries"); - Services.prefs.setIntPref("network.cookie.cookieBehavior", - 1 /*reject third-party cookies*/); - - for (let i = 0; i < NUMBER_OF_REJECTS; ++i) { - for (let j = 0; j < NUMBER_OF_REPEATS; ++j) { - for (let prefix of ["http://", "https://"]) { - // Histogram sitesRejected should only count - // NUMBER_OF_REJECTS entries. - // Histogram requestsRejected should count - // NUMBER_OF_REJECTS * NUMBER_OF_REPEATS * 2 - tryToSetCookie({ - request: prefix + "echelon" + tld, - referrer: prefix + "domain" + i + tld - }); - } - } - } - - do_print("Adding accepted entries"); - Services.prefs.setIntPref("network.cookie.cookieBehavior", - 0 /*accept third-party cookies*/); - - for (let i = 0; i < NUMBER_OF_ACCEPTS; ++i) { - for (let j = 0; j < NUMBER_OF_REPEATS; ++j) { - for (let prefix of ["http://", "https://"]) { - // Histogram sitesAccepted should only count - // NUMBER_OF_ACCEPTS entries. - // Histogram requestsAccepted should count - // NUMBER_OF_ACCEPTS * NUMBER_OF_REPEATS * 2 - tryToSetCookie({ - request: prefix + "prism" + tld, - referrer: prefix + "domain" + i + tld - }); - } - } - } - - do_print("Checking that the histograms have not changed before ping()"); - do_check_eq(gHistograms.sitesAccepted.snapshot().sum, 0); - do_check_eq(gHistograms.sitesRejected.snapshot().sum, 0); - do_check_eq(gHistograms.requestsAccepted.snapshot().sum, 0); - do_check_eq(gHistograms.requestsRejected.snapshot().sum, 0); - - do_print("Checking that the resulting histograms are correct"); - if (flushUptime != null) { - let now = Date.now(); - let before = now - flushUptime; - gThirdPartyCookieProbe._latestFlush = before; - gThirdPartyCookieProbe.flush(now); - } else { - gThirdPartyCookieProbe.flush(); - } - check(); -} - -add_task(function() { - // To ensure that we work correctly with eTLD, test with several suffixes - for (let tld of [".com", ".com.ar", ".co.uk", ".gouv.fr"]) { - oneTest(tld, FLUSH_MILLISECONDS, function() { - do_check_eq(gHistograms.sitesAccepted.snapshot().sum, NUMBER_OF_ACCEPTS * 2); - do_check_eq(gHistograms.sitesRejected.snapshot().sum, NUMBER_OF_REJECTS * 2); - do_check_eq(gHistograms.requestsAccepted.snapshot().sum, NUMBER_OF_ACCEPTS * NUMBER_OF_REPEATS * 2 * 2); - do_check_eq(gHistograms.requestsRejected.snapshot().sum, NUMBER_OF_REJECTS * NUMBER_OF_REPEATS * 2 * 2); - }); - } - - // Check that things still work with default uptime management - for (let tld of [".com", ".com.ar", ".co.uk", ".gouv.fr"]) { - yield wait(1000); // Ensure that uptime is at least one second - oneTest(tld, null, function() { - do_check_true(gHistograms.sitesAccepted.snapshot().sum > 0); - do_check_true(gHistograms.sitesRejected.snapshot().sum > 0); - do_check_true(gHistograms.requestsAccepted.snapshot().sum > 0); - do_check_true(gHistograms.requestsRejected.snapshot().sum > 0); - }); - } -}); - -add_task(function() { - gThirdPartyCookieProbe.dispose(); -}); - diff --git a/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js b/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js index 891b119aaa..2826abbf29 100644 --- a/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js +++ b/toolkit/components/telemetry/tests/unit/test_ThreadHangStats.js @@ -23,24 +23,33 @@ function run_test() { // We use the rt_tgsigqueueinfo syscall on Linux which requires a // certain kernel version. It's not an error if the system running // the test is older than that. - let kernel = Services.sysinfo.kernel_version || Services.sysinfo.version; + let kernel = Services.sysinfo.get('kernel_version') || + Services.sysinfo.get('version'); if (Services.vc.compare(kernel, '2.6.31') < 0) { ok("Hang reporting not supported for old kernel."); return; } } - // Run two events in the event loop: - // the first event causes a hang; - // the second event checks results from the first event. + // Run three events in the event loop: + // the first event causes a transient hang; + // the second event causes a permanent hang; + // the third event checks results from previous events. do_execute_soon(() => { - // Cause a hang lasting 1 second. + // Cause a hang lasting 1 second (transient hang). let startTime = Date.now(); while ((Date.now() - startTime) < 1000) { } }); + do_execute_soon(() => { + // Cause a hang lasting 10 seconds (permanent hang). + let startTime = Date.now(); + while ((Date.now() - startTime) < 10000) { + } + }); + do_execute_soon(() => { do_test_pending(); @@ -77,6 +86,14 @@ function run_test() { notEqual(endHangs.hangs[0].stack.length, 0); equal(typeof endHangs.hangs[0].stack[0], "string"); + // Make sure one of the hangs is a permanent + // hang containing a native stack. + ok(endHangs.hangs.some((hang) => ( + Array.isArray(hang.nativeStack) && + hang.nativeStack.length !== 0 && + typeof hang.nativeStack[0] === "string" + ))); + check_histogram(endHangs.hangs[0].histogram); do_test_finished(); diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index 5328e779f4..fde3ec7ea5 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -39,25 +39,16 @@ skip-if = os == "android" [test_TelemetryLockCount.js] [test_TelemetryLog.js] [test_TelemetryController.js] -# Bug 676989: test fails consistently on Android -# fail-if = os == "android" -# Bug 1144395: crash on Android 4.3 -skip-if = android_version == "18" tags = addons [test_TelemetryController_idle.js] [test_TelemetryControllerShutdown.js] tags = addons [test_TelemetryStopwatch.js] [test_TelemetryControllerBuildID.js] -# Bug 1144395: crash on Android 4.3 -skip-if = android_version == "18" -[test_ThirdPartyCookieProbe.js] [test_TelemetrySendOldPings.js] skip-if = os == "android" # Disabled due to intermittent orange on Android tags = addons [test_TelemetrySession.js] -# Bug 1144395: crash on Android 4.3 -skip-if = android_version == "18" tags = addons [test_ThreadHangStats.js] run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high diff --git a/toolkit/content/license.html b/toolkit/content/license.html index ddf9bbec57..9e577be170 100644 --- a/toolkit/content/license.html +++ b/toolkit/content/license.html @@ -101,6 +101,9 @@
  • k_exp License
  • Khronos group License
  • Kiss FFT License
  • +#ifdef MOZ_USE_LIBCXX +
  • libc++ License
  • +#endif
  • libcubeb License
  • libevent License
  • libffi License
  • @@ -3038,6 +3041,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +#ifdef MOZ_USE_LIBCXX +

    libc++ License

    + +

    This license applies to the copy of libc++ obtained + from the Android NDK.

    + +
    +Copyright (c) 2009-2014 by the contributors listed in the libc++ CREDITS.TXT
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    +
    + +
    + +#endif +

    libcubeb License

    This license applies to files in the directory diff --git a/toolkit/content/moz.build b/toolkit/content/moz.build index 1b670a1b89..ed1be90af3 100644 --- a/toolkit/content/moz.build +++ b/toolkit/content/moz.build @@ -16,6 +16,8 @@ if CONFIG['OS_TARGET'] == 'Android': if CONFIG['MOZ_ANDROID_CXX_STL'] == 'mozstlport': DEFINES['USE_STLPORT'] = True +if CONFIG['MOZ_ANDROID_CXX_STL'] == 'libc++': + DEFINES['MOZ_USE_LIBCXX'] = True if CONFIG['MOZ_BUILD_APP'] == 'mobile/android': DEFINES['MOZ_FENNEC'] = True diff --git a/toolkit/content/widgets/findbar.xml b/toolkit/content/widgets/findbar.xml index e9e245774c..aebd6d68dc 100644 --- a/toolkit/content/widgets/findbar.xml +++ b/toolkit/content/widgets/findbar.xml @@ -672,7 +672,6 @@ this.browser.finder.focusContent(); this.browser.finder.enableSelection(); - this._findField.blur(); this._cancelTimers(); this.toggleHighlight(false); diff --git a/toolkit/modules/GMPInstallManager.jsm b/toolkit/modules/GMPInstallManager.jsm index 00a5cf4cc1..31333243b4 100644 --- a/toolkit/modules/GMPInstallManager.jsm +++ b/toolkit/modules/GMPInstallManager.jsm @@ -382,21 +382,29 @@ GMPExtractor.prototype = { let entries = this._getZipEntries(zipReader); let extractedPaths = []; + let destDir = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsILocalFile); + destDir.initWithPath(this.installToDirPath); + // Make sure the destination exists + if(!destDir.exists()) { + destDir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); + } + // Extract each of the entries entries.forEach(entry => { // We don't need these types of files - if (entry.includes("__MACOSX")) { + if (entry.includes("__MACOSX") || + entry == "_metadata/verified_contents.json" || + entry == "imgs/icon-128x128.png") { return; } - let outFile = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - outFile.initWithPath(this.installToDirPath); - outFile.appendRelativePath(entry); + let outFile = destDir.clone(); + // Do not extract into directories. Extract all files to the same + // directory. DO NOT use |OS.Path.basename()| here, as in Windows it + // does not work properly with forward slashes (which we must use here). + let outBaseName = entry.slice(entry.lastIndexOf("/") + 1); + outFile.appendRelativePath(outBaseName); - // Make sure the directory hierarchy exists - if(!outFile.parent.exists()) { - outFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); - } zipReader.extract(entry, outFile); extractedPaths.push(outFile.path); log.info(entry + " was successfully extracted to: " + diff --git a/toolkit/modules/InlineSpellChecker.jsm b/toolkit/modules/InlineSpellChecker.jsm index 3bfec4f704..154534dbfa 100644 --- a/toolkit/modules/InlineSpellChecker.jsm +++ b/toolkit/modules/InlineSpellChecker.jsm @@ -248,8 +248,7 @@ InlineSpellChecker.prototype = { menu.ownerDocument.dispatchEvent(spellcheckChangeEvent); } }; - item.addEventListener - ("command", callback(this, i, sortedList[i].id), true); + item.addEventListener("command", callback(this, i, sortedList[i].id), true); } if (insertBefore) menu.insertBefore(item, insertBefore); diff --git a/toolkit/modules/RemoteFinder.jsm b/toolkit/modules/RemoteFinder.jsm index 86e4d9db87..0d74cc2e8d 100644 --- a/toolkit/modules/RemoteFinder.jsm +++ b/toolkit/modules/RemoteFinder.jsm @@ -119,16 +119,18 @@ RemoteFinder.prototype = { this._browser.messageManager.sendAsyncMessage("Finder:GetInitialSelection", {}); }, - fastFind: function (aSearchString, aLinksOnly) { + fastFind: function (aSearchString, aLinksOnly, aDrawOutline) { this._browser.messageManager.sendAsyncMessage("Finder:FastFind", { searchString: aSearchString, - linksOnly: aLinksOnly }); + linksOnly: aLinksOnly, + drawOutline: aDrawOutline }); }, - findAgain: function (aFindBackwards, aLinksOnly) { + findAgain: function (aFindBackwards, aLinksOnly, aDrawOutline) { this._browser.messageManager.sendAsyncMessage("Finder:FindAgain", { findBackwards: aFindBackwards, - linksOnly: aLinksOnly }); + linksOnly: aLinksOnly, + drawOutline: aDrawOutline }); }, highlight: function (aHighlight, aWord) { @@ -157,12 +159,16 @@ RemoteFinder.prototype = { } } + this._browser.focus(); this._browser.messageManager.sendAsyncMessage("Finder:FocusContent"); }, keyPress: function (aEvent) { this._browser.messageManager.sendAsyncMessage("Finder:KeyPress", { keyCode: aEvent.keyCode, + ctrlKey: aEvent.ctrlKey, + metaKey: aEvent.metaKey, + altKey: aEvent.altKey, shiftKey: aEvent.shiftKey }); }, @@ -239,17 +245,21 @@ RemoteFinderListener.prototype = { } case "Finder:FastFind": - this._finder.fastFind(data.searchString, data.linksOnly); + this._finder.fastFind(data.searchString, data.linksOnly, data.drawOutline); break; case "Finder:FindAgain": - this._finder.findAgain(data.findBackwards, data.linksOnly); + this._finder.findAgain(data.findBackwards, data.linksOnly, data.drawOutline); break; case "Finder:Highlight": this._finder.highlight(data.highlight, data.word); break; + case "Finder:EnableSelection": + this._finder.enableSelection(); + break; + case "Finder:RemoveSelection": this._finder.removeSelection(); break; diff --git a/toolkit/mozapps/extensions/AddonPathService.cpp b/toolkit/mozapps/extensions/AddonPathService.cpp index d171f0aebd..8a405c0ea4 100644 --- a/toolkit/mozapps/extensions/AddonPathService.cpp +++ b/toolkit/mozapps/extensions/AddonPathService.cpp @@ -129,6 +129,16 @@ AddonPathService::InsertPath(const nsAString& path, const nsAString& addonIdStri return NS_OK; } +NS_IMETHODIMP +AddonPathService::MapURIToAddonId(nsIURI* aURI, nsAString& addonIdString) +{ + if (JSAddonId* id = MapURIToAddonID(aURI)) { + JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(id)); + AssignJSFlatString(addonIdString, flat); + } + return NS_OK; +} + static nsresult ResolveURI(nsIURI* aURI, nsAString& out) { diff --git a/toolkit/mozapps/extensions/amIAddonPathService.idl b/toolkit/mozapps/extensions/amIAddonPathService.idl index 8636898585..9c9197a61c 100644 --- a/toolkit/mozapps/extensions/amIAddonPathService.idl +++ b/toolkit/mozapps/extensions/amIAddonPathService.idl @@ -5,6 +5,8 @@ #include "nsISupports.idl" +interface nsIURI; + /** * This service maps file system paths where add-ons reside to the ID * of the add-on. Paths are added by the add-on manager. They can @@ -26,4 +28,10 @@ interface amIAddonPathService : nsISupports * associated with the given add-on ID. */ void insertPath(in AString path, in AString addonId); + + /** + * Given a URI to a file, return the ID of the add-on that the file belongs + * to. Returns an empty string if there is no add-on there. + */ + AString mapURIToAddonId(in nsIURI aURI); }; diff --git a/toolkit/mozapps/extensions/amWebAPI.js b/toolkit/mozapps/extensions/amWebAPI.js index 2d48c99769..b415a9e4d6 100644 --- a/toolkit/mozapps/extensions/amWebAPI.js +++ b/toolkit/mozapps/extensions/amWebAPI.js @@ -102,9 +102,9 @@ function AddonInstall(window, properties) { * to make sure of that. It also automatically wraps objects when necessary. */ function WebAPITask(generator) { - let task = Task.async(generator); - return function(...args) { + let task = Task.async(generator.bind(this)); + let win = this.window; let wrapForContent = (obj) => { diff --git a/toolkit/profile/test/.eslintrc b/toolkit/profile/test/.eslintrc new file mode 100644 index 0000000000..e617dcd2f8 --- /dev/null +++ b/toolkit/profile/test/.eslintrc @@ -0,0 +1,5 @@ +{ + "extends": [ + "../../../testing/mochitest/chrome.eslintrc" + ] +} diff --git a/tools/leak-gauge/leak-gauge.html b/tools/leak-gauge/leak-gauge.html index 63090f2600..f8c74baf5d 100644 --- a/tools/leak-gauge/leak-gauge.html +++ b/tools/leak-gauge/leak-gauge.html @@ -8,6 +8,7 @@ --> + Leak Gauge