From d457251529c741caa2eb939efd2b3b746bcb9eba Mon Sep 17 00:00:00 2001 From: roytam1 Date: Thu, 18 Apr 2024 11:14:32 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1120715 - Part 6: Remove the dom.requestcache.enabled pref; r=bkelly (800c996a96) - Bug 1143222 - Put the DOM Cache tests in sequential mode again until we fix the rest of the intermittent failures; a=RyanVM (2ebdd659a1) - Bug 1255636 - Give a better error message when the Request constructor fails because of a cross-origin referrer URL; r=bkelly (d81a21c0bb) - Bug 1265056 - don't needlessly construct nsAutoCString temporaries in dom/; r=baku (3be49ca3fc) - Bug 1243849 - Restore support for accessing the Cache API from app:// URLs and also for storing requests/responses with app:// URLs within it; r=bkelly (eb56fa564c) - Bug 1263235, part 1 - Move PBrowser::AsyncMessage's data argument last. r=smaug (6852b87c22) - Bug 1263235, part 2 - Make PContent::AsyncMessage and PContentBridge::AsyncMessage's data argument last. r=smaug (9e8cd94461) - Bug 1263028 - send HTTP data to the content process in smaller chunks, r=michal (c0da548157) - Bug 1263235, part 3 - Move PHttpChannel::OnTransportAndData's data argument last. r=mayhemer (e1bf4f430f) - Bug 1263235, part 4 - Make PBrowserStream::Write's data argument last. r=jimm (8bcec4d541) - Bug 1260876 - Remove process switch code for signed package code (added by Bug 1186290). r=valentin. (cef270b44c) - Bug 1234575 - Empty fragment is ignored in URI of location header r=mcmanus (db68f102d8) - Bug 1262506 - Unused variable in a runnable in BackgroundParentImpl, r=ehsan (9288f0a111) - bug 1239166 - platform work to support Microsoft Family Safety functionality r=froydnj,mgoodwin,mhowell,rbarnes,vladan (adc357f3b3) - Bug 842818 - Make Crypto::GetRandomValues() work off the main thread r=baku,keeler,mt (533f8942c4) - Bug 1247089 - Add a mode to ReportToConsoleNonLocalized that ignores the calling location. r=bkelly (4be23e0869) - Bug 1258883 - Add a way to replace the entire Push service in tests. r=wchen (06a5f27016) - Bug 1243856 - Remove alarms from the Push H2 backend. r=dragana (60d146dc73) - Bug 1246066 - Clear PushService timeout tasks on uninitialization. r=itcambridge (461276a972) - Bug 1214338 - Implement Android GCM-based PushService protocol. r=rnewman r=kitcambridge (f2bb78994a) - Bug 1257821 - Support the new aesgcm content encoding scheme. r=mt (1da653c14a) - Bug 1243856 - Remove alarms from the Push WebSocket backend. r=dragana (43f74c4999) - Bug 1258145 - Remove waitForPromise from the xpcshell tests. r=wchen (cdd1aff2f6) - Bug 1253831 - Don't check actual intervals in the Push backoff test. r=wchen (859fa0bba3) - Bug 1246341 - Include status codes in "ack" and "unregister" requests. r=dragana (a62d0daf9b) - Bug 1246341 - Add a test for push event error reporting. r=dragana (013bc814e4) - Bug 1247089 - Log Web Push decryption errors. r=bkelly (ffc093dc2f) - Bug 1258221 - patch 2 - Port FileSystem API and DeviceStorage API to PBackground, r=smaug (c1c0e08bc0) - Bug 1258221 - patch 3 - Rename FileSystemTaskBase to FileSystemTaskChildBase, r=smaug (867a0e65fd) - Bug 1251032 - Don't return layersId or textureFactoryIdentifier as outparams in RenderFrameParent constructor. r=kanru (198ddff7fc) - Bug 1251032 - Send RenderFrame info down to child in CreateWindow message. r=kanru (87e9001088) - Bug 1251032 - Make it possible to assign a frameloader to RenderFrameParent after construction. r=kanru (96483d1282) - Bug 1254865 - Send disableglobalhistory state down to TabChild after construction asynchronously. r=smaug (3949285b62) - Bug 1238707 Release the window immediately in TabParent::Destroy() to avoid leaks. r=smaug (fc612485d7) - Bug 1256589 part.1 Move the implementation of StopPropagation() from dom::Event to WidgetEvent r=smaug (181721b64c) - Bug 1256589 part.2 Move the implementation of StopImmediatePropagation() from dom::Event to WidgetEvent r=smaug (554a0dc5b5) - Bug 1203059 part.1 nsXBLWincowKeyHandler mark WidgetEvent::mFlags if it's reserved by chrome before the event is dispatched into the content r=smaug (9162dd68cb) - Bug 1203059 part.2 When an event is reserved by chrome, it should be fired only on chrome r=smaug (35f082ca5f) - Bug 1203059 part.3 Installing and removing keyboard event listeners of nsXBLWindowKeyHandler should be done by the class itself r=smaug (1e06c2d0bc) - Bug 1203059 part.4 Update test_keycodes.xul for the new behavior r=smaug (3014d21c75) - Bug 1256589 part.3 Move the implementation of StopCrossProcessForwarding() from dom::Event to WidgetEvent r=smaug (96db915b51) - Bug 1257180 - patch 1 - Directory clonable to workers, r=smaug (5634acb08d) - Bug 1257180 - patch 2 - Directory can be sent via postMessage(), r=smaug (82ada39ae3) - Bug 1263311: Part 3 - s/nsCancelableRunnable/CancelableRunnable/g. r=froydnj (79d0a6c81f) - Bug 1253198: add WebRtcIce prefix to all ICE unit tests. r=bwc (9c18f5fd56) - Bug 1244926: added TCP socket filter to only allow outgoing STUN. r=jesup (75debfdc84) - Bug 1257405 - Increase auth secret length to 16 octets. r=mt (b1e000c331) - Bug 1257401 - Remove the worker descriptor for PushSubscription. r=khuey (5791fb69ef) - Bug 1257401 - Remove the worker descriptor for PushManager. r=khuey (e2c75903a9) - Bug 1257821 - Remove the authenticated aesgcm128 content coding scheme. r=mt,marco (64a2917910) - Bug 1256488 - Add a Base64 URL-decoder for C++ and chrome JS callers. r=mt,baku (0bbb250298) - Bug 1247685 - Validate and store app server keys in the Push service. r=mt (c3c026ccb1) - Bug 1252650 - Support loading PushService immediately on Android; r=kitcambridge (d59a37fec4) - Bug 1258595 - Shut down the Push service if errors occur at startup. r=wchen (eef1805652) - Bug 1258595 - Wait for the Push service to shut down between tests. r=wchen (c30cf92ce6) - Bug 1262618 - Fix an unchained promise and a couple of non-promise returns in the push service. r=wchen (8eadab5706) - Bug 1263747 - Log error messages when stringifying errors. r=bgrins (edffd0074e) - Bug 1265705 - Silence startup JavaScript strict warning in resource://gre/modules/PushService.jsm. r=kitcambridge (e7e210fb61) - Bug 1264062 - Don't bother checking which accelerated layer types are available if they're all disabled by pref r=milan (0d3208ad59) - Bug 1263346. Remove wrong Ivy Bridge device id. r=Bas (7e39e7f370) - fix misspatch (a67a111b2c) - Bug 1251334 - Create a disposable pref to force-disable e10s in an emergency. r=jimm (ef892d4474) - Bug 1254774 - error: member access into incomplete type 'nsIUUIDGenerator' after bug 1237847. r=aklotz (c1f334609f) - Bug 1257242 - Split the ::BrowserTabsRemoteAutostart() function into two parts, to allow for the blocking policies to be checked independently from the prefs checks. r=jimm (1babda578f) - Bug 1260190 - Disable e10s for accessibility users on OS X. r=jimm (219e5b1f19) - Bug 1237769 - Disable e10s on Windows XP if layers acceleration is requested r=milan (60f2434e9f) - Bug 1232911 - [3.2] Block VPX support in ADM on unsupported devices. r=snorp (6924aa073a) - Bug 1263249 - Bubble up unique failureId in GetFeatureStatus. r=mconley,milan (cd56eeab3c) - Bug 1219296 - Split fields not needed for repaints out from FrameMetrics. r=kats (9003ca634a) - Bug 1219296 - Factor out scroll snap information into a form that's usable by the compositor. r=kats (23d3e619a1) - Bug 1219296 - Make ScrollMetadata::sNullMetadata a StaticAutoPtr so that ScrollMetadata can admit nsTArray members. r=kats (1729ff7d93) - Bug 1257641 - Replace the mUpdateScrollOffset bool with an enum, needed in the next patch. r=botond (f9d546f8e8) - Bug 1257641 - Use empty transactions to carry scroll offset updates to APZ that don't require a repaint. r=mattwoodrow,mstange,botond (ba4a8a8c29) - Bug 1246290 - Add a bit to FrameMetrics to indicate if APZ-scrolling should be disabled on that APZC. r=botond (af2067137f) - Bug 1256589 part.4 Move the implementation of PreventDefault() and add PreventDefaultBeforeDispatch() from dom::Event to WidgetEvent r=smaug (e7828f2d8f) - Bug 1256589 part.5 Add DefaultPrevented() and DefaultPreventedByContent() to WidgetEvent r=smaug (e65cdd9127) - Bug 1249915 - Fix missing MOZ_COUNT_CTOR and some misc cleanup. r=karlt (d2f26cf971) - Bug 1154183 part.1 Move shortcut/access key candidate list creators from nsContentUtils to WidgetKeyboardEvent r=smaug (40b0b11a5a) - Bug 1154183 part.2 eKeyDown event should have charCode value of following keypress event r=smaug (28c1443ba3) - Bug 1154183 part.3 Clean up some variable names in nsXBLWindowKeyHandler::WalkHandlersAndExecute() r=smaug (81e25023d8) - Bug 1154183 part.4 Implement nsXBLWindowKeyHandler::GetElementForHandler() r=smaug (b5605d5c83) - Bug 1154183 part.5 Make nsXBLWindowKeyHandler::GetElementForHandler() use early return style r=smaug (017467204f) - Bug 1154183 part.6 Add nsXBLWindowKeyHandler::IsExecuteableElement() r=smaug (1fda349113) - Bug 1154183 part.7 Don't dispatch preceding keydown events of reserved keypress events on content in the default event group r=smaug (15b9e8c9d2) - Bug 1256589 part.6 Move the implementation of IsTrusted() from dom::Event to WidgetEvent r=smaug (ec79520fd3) - Bug 1253044. Fix fall through of observer topics when other conditions aren't met in PresShell::Observe. r=dholbert (dcc36884aa) - Bug 1157546 - Replace the image visibility API with a more general API that tracks visibility for any kind of frame. r=mstange (d6ea061614) - Bug 1219296 - Factor out the algorithm that computes a scroll snap destination into a reusable form. r=kats (296cbe9e49) - Bug 1254275 - Inspect the event queue to find out whether momentum events are following. r=kats (b2bb8a26b9) - Bug 1219296 - Scroll snap directly in APZ instead of going through the main thread. r=kats (0a30b550f9) - Bug 1219296 - Fix an include-what-you-use error. r=kats (4a128ae98e) - Bug 1260588 - C++ APZ should only allow handoff to ancestor APZC r=botond (9856ab5160) - Bug 1257269 - Panning up in a scrollable element should not hide the toolbar r=kats,jchen (1036ffc9e3) - Bug 1219296 - Ship scroll snap information to the compositor. r=kats (0e920f02a1) - Bug 1219296 - Move the layout.css.scroll-snap.proximity-threshold pref to gfxPrefs, so it can be queried on the compositor thread. r=kats (2e3e1ec16e) - Bug 1219296 - Remove StartSmoothScroll()'s argument, which is no longer used. r=kats (12efcd9c79) - Bug 1219296 - Light refactoring to how a smooth scroll is launched inside APZC. r=kats (ba6a9ed9a2) - Bug 1219296 - Followup to fix stale code comments. r=me and DONTBUILD (fec5f65988) - Bug 1257641 - Remove now-unused code for the lightweight scroll offset update message. r=botond (d449e45d6d) - Bug 1236680 Part 1: Add new WinUtils function to Resolve moved Users folder. r=jimm (51d12f856c) - Bug 1236680 Part 2: Resolve GMP path for moved Users folder. r=cpearce (e568217b78) - Bug 1236680 Part 3: Add #ifs to include to fix bustage. r=me (40c38680ea) - Bug 1240315: Add startup crash report annotation for AppInit_DLLs; r=jimm (aa2040baae) - Bug 1253446 - patch 2 - Return the proper scaling factor when querying the primary screen on Windows. r=emk (9765e4f7ca) - Bug 1251624 - patch 1 - The desktop to device scaling in WinUtils::MonitorFromRect should not depend on custom CSS pixel scaling (devPixelsPerPx setting). r=emk (788b4ad5db) - Bug 1251624 - patch 2 - Check for scaling override (devPixelsPerPx setting) in nsScreenWin::GetDefaultCSSScaleFactor, for proper window positioning when a custom scale factor is used. r=emk (2843a3fe70) - Bug 1222149 - delete unused fields from AsyncEncodeAndWriteIcon; r=roc clang-cl says these are unused, so let's delete them. (62cf7f8f47) - Bug 1204809 - Notify (don't hang) third party windows when adding shortcut icon. r=jmathies (7e4058a0f8) - Bug 1253566 - Deal with char16_t/wchar_t mismatch. r=aklotz (1c6cf160c7) - Bug 1211941 - Let nsICacheStorage.openTruncate impl return an HTTP cache entry write handle, r=michal (6a5796fb93) - Bug 1050613 - Make sure force-valid for HTTP cache entries is removed when entries are removed, r=michal (9efb91eefc) - Bug 1248389 - Cache index causing CPU loops, r=honzab (175b5b27f2) - Bug 1066970 - Show 'calculating' during HTTP cache cleaning process in preferences window, r=michal (db722000d8) - Bug 1248958 - CacheIndex mRWBuf ownership too fragile, read-after-free, r=honzab (66ee3d1d0d) - Bug 1248003 - Purge from HTTP cache memory pool only in reasonable intervals, r=michal (1cd6cb5983) - Bug 1068674 - Don't turn off e10s if hardware acceleration is disabled. r=jimm (dfbef44278) --- b2g/app/b2g.js | 4 +- dom/apps/Webapps.jsm | 10 +- dom/base/ChromeUtils.cpp | 56 ++ dom/base/ChromeUtils.h | 14 + dom/base/Crypto.cpp | 58 +- dom/base/Crypto.h | 3 - dom/base/Element.cpp | 4 +- dom/base/FileList.cpp | 3 +- dom/base/ImageEncoder.cpp | 2 +- dom/base/StructuredCloneHolder.cpp | 89 ++- dom/base/StructuredCloneTags.h | 3 +- dom/base/TextInputProcessor.cpp | 3 +- dom/base/WebSocket.cpp | 4 +- dom/base/nsContentUtils.cpp | 195 +---- dom/base/nsContentUtils.h | 42 +- dom/base/nsFrameLoader.cpp | 4 +- dom/base/nsFrameMessageManager.cpp | 4 +- dom/base/nsGkAtomList.h | 2 + dom/base/nsGlobalWindow.cpp | 14 +- dom/base/nsIImageLoadingContent.idl | 33 +- dom/base/nsImageLoadingContent.cpp | 167 ++-- dom/base/nsImageLoadingContent.h | 25 +- dom/base/nsPerformance.cpp | 2 +- dom/base/test/test_postMessages.html | 69 +- dom/base/test/unit/test_chromeutils_base64.js | 103 +++ dom/base/test/unit/xpcshell.ini | 1 + dom/bindings/Bindings.conf | 20 +- dom/bindings/Errors.msg | 1 + dom/cache/CachePushStreamChild.cpp | 4 +- dom/cache/CacheStorage.cpp | 1 + dom/cache/DBSchema.cpp | 2 +- dom/cache/ReadStream.cpp | 4 +- dom/cache/TypeUtils.cpp | 3 +- dom/cache/test/mochitest/driver.js | 3 +- dom/cache/test/xpcshell/head.js | 3 - dom/canvas/WebGLContext.cpp | 12 +- dom/canvas/WebGLContextLossHandler.cpp | 2 +- dom/crypto/WebCryptoTask.h | 2 +- dom/events/AsyncEventDispatcher.cpp | 2 +- dom/events/AsyncEventDispatcher.h | 6 +- dom/events/Event.cpp | 29 +- dom/events/Event.h | 4 +- dom/events/EventDispatcher.cpp | 71 +- dom/events/EventListenerManager.cpp | 12 +- dom/events/EventStateManager.cpp | 72 +- dom/events/IMEContentObserver.cpp | 8 +- dom/events/IMEStateManager.cpp | 6 +- dom/events/TextComposition.cpp | 8 +- dom/fetch/Request.cpp | 27 +- dom/fetch/Request.h | 2 - dom/filesystem/CreateDirectoryTask.cpp | 197 ++--- dom/filesystem/CreateDirectoryTask.h | 61 +- dom/filesystem/CreateFileTask.cpp | 322 ++++---- dom/filesystem/CreateFileTask.h | 82 +- dom/filesystem/DeviceStorageFileSystem.cpp | 57 +- dom/filesystem/DeviceStorageFileSystem.h | 6 + dom/filesystem/Directory.cpp | 73 +- dom/filesystem/Directory.h | 6 + dom/filesystem/FileSystemBase.cpp | 24 +- dom/filesystem/FileSystemBase.h | 56 +- .../FileSystemPermissionRequest.cpp | 128 ++- dom/filesystem/FileSystemPermissionRequest.h | 19 +- dom/filesystem/FileSystemRequestParent.cpp | 53 +- dom/filesystem/FileSystemRequestParent.h | 40 +- dom/filesystem/FileSystemTaskBase.cpp | 395 ++++++---- dom/filesystem/FileSystemTaskBase.h | 328 ++++---- dom/filesystem/GetDirectoryListingTask.cpp | 347 +++++---- dom/filesystem/GetDirectoryListingTask.h | 62 +- dom/filesystem/GetFileOrDirectoryTask.cpp | 236 +++--- dom/filesystem/GetFileOrDirectoryTask.h | 63 +- dom/filesystem/OSFileSystem.cpp | 31 +- dom/filesystem/OSFileSystem.h | 69 +- dom/filesystem/PFileSystemParams.ipdlh | 72 ++ dom/filesystem/PFileSystemRequest.ipdl | 6 +- dom/filesystem/RemoveTask.cpp | 235 +++--- dom/filesystem/RemoveTask.h | 66 +- dom/filesystem/moz.build | 1 + dom/filesystem/tests/mochitest.ini | 2 + dom/filesystem/tests/test_worker_basic.html | 70 ++ dom/filesystem/tests/worker_basic.js | 58 ++ dom/html/HTMLButtonElement.cpp | 4 +- dom/html/HTMLCanvasElement.cpp | 4 +- dom/html/HTMLFormElement.cpp | 2 +- dom/html/HTMLInputElement.cpp | 25 +- dom/html/HTMLObjectElement.cpp | 2 +- dom/html/nsGenericHTMLElement.cpp | 2 +- dom/indexedDB/ActorsChild.cpp | 4 +- dom/indexedDB/IDBDatabase.cpp | 2 +- dom/interfaces/push/nsIPushNotifier.idl | 3 + dom/ipc/Blob.cpp | 4 +- dom/ipc/ContentBridgeChild.cpp | 6 +- dom/ipc/ContentBridgeChild.h | 4 +- dom/ipc/ContentBridgeParent.cpp | 8 +- dom/ipc/ContentBridgeParent.h | 4 +- dom/ipc/ContentChild.cpp | 61 +- dom/ipc/ContentChild.h | 14 +- dom/ipc/ContentParent.cpp | 86 +- dom/ipc/ContentParent.h | 24 +- dom/ipc/PBrowser.ipdl | 15 +- dom/ipc/PContent.ipdl | 87 +-- dom/ipc/PContentBridge.ipdl | 4 +- dom/ipc/TabChild.cpp | 22 +- dom/ipc/TabChild.h | 6 +- dom/ipc/TabParent.cpp | 195 ++--- dom/ipc/TabParent.h | 20 +- dom/ipc/nsIContentChild.cpp | 4 +- dom/ipc/nsIContentChild.h | 4 +- dom/ipc/nsIContentParent.cpp | 4 +- dom/ipc/nsIContentParent.h | 4 +- dom/locales/en-US/chrome/dom/dom.properties | 2 + dom/media/android/AndroidMediaPluginHost.cpp | 3 +- dom/media/bridge/MediaModule.cpp | 6 +- dom/media/gmp/GMPProcessParent.cpp | 19 +- .../android/AndroidDecoderModule.cpp | 25 +- dom/messagechannel/MessagePort.cpp | 2 +- dom/network/PTCPSocket.ipdl | 2 +- dom/network/TCPSocketChild.cpp | 14 +- dom/network/TCPSocketChild.h | 2 + dom/network/TCPSocketParent.cpp | 67 +- dom/network/TCPSocketParent.h | 7 +- dom/network/UDPSocketParent.cpp | 8 +- dom/network/UDPSocketParent.h | 4 +- dom/plugins/base/nsPluginInstanceOwner.cpp | 4 +- dom/plugins/ipc/BrowserStreamChild.cpp | 4 +- dom/plugins/ipc/BrowserStreamChild.h | 4 +- dom/plugins/ipc/BrowserStreamParent.cpp | 4 +- dom/plugins/ipc/PBrowserStream.ipdl | 4 +- dom/promise/PromiseDebugging.cpp | 2 +- dom/push/Push.js | 4 +- dom/push/Push.manifest | 3 + dom/push/PushComponents.js | 109 ++- dom/push/PushCrypto.jsm | 175 +++-- dom/push/PushManager.cpp | 734 ++++-------------- dom/push/PushManager.h | 212 +---- dom/push/PushNotifier.cpp | 89 ++- dom/push/PushNotifier.h | 7 +- dom/push/PushRecord.jsm | 18 + dom/push/PushService.jsm | 514 ++++++------ dom/push/PushServiceAndroidGCM.jsm | 247 ++++++ dom/push/PushServiceHttp2.jsm | 107 +-- dom/push/PushServiceWebSocket.jsm | 491 +++++++----- dom/push/PushSubscription.cpp | 388 +++++++++ dom/push/PushSubscription.h | 95 +++ dom/push/moz.build | 16 +- dom/push/test/error_worker.js | 10 + dom/push/test/mochitest.ini | 5 +- dom/push/test/mockpushserviceparent.js | 120 ++- dom/push/test/test_data.html | 6 +- dom/push/test/test_error_reporting.html | 129 +++ dom/push/test/test_multiple_register.html | 2 +- ...est_multiple_register_different_scope.html | 2 +- ...le_register_during_service_activation.html | 12 +- dom/push/test/test_register.html | 2 +- .../test/test_serviceworker_lifetime.html | 2 +- dom/push/test/test_subscription_change.html | 2 +- ...test_try_registering_offline_disabled.html | 2 +- dom/push/test/test_unregister.html | 2 +- dom/push/test/test_utils.js | 88 ++- dom/push/test/xpcshell/head.js | 27 - .../test/xpcshell/test_clear_origin_data.js | 4 +- dom/push/test/xpcshell/test_crypto.js | 245 ++++++ dom/push/test/xpcshell/test_drop_expired.js | 9 +- .../test/xpcshell/test_notification_ack.js | 21 +- .../test/xpcshell/test_notification_data.js | 90 ++- .../xpcshell/test_notification_duplicate.js | 6 +- .../test/xpcshell/test_notification_error.js | 9 +- .../test/xpcshell/test_notification_http2.js | 38 +- .../xpcshell/test_notification_incomplete.js | 3 +- .../test_notification_version_string.js | 9 +- dom/push/test/xpcshell/test_permissions.js | 23 +- dom/push/test/xpcshell/test_quota_exceeded.js | 7 +- dom/push/test/xpcshell/test_quota_observer.js | 10 +- .../xpcshell/test_quota_with_notification.js | 6 +- .../test/xpcshell/test_reconnect_retry.js | 2 +- dom/push/test/xpcshell/test_register_case.js | 14 +- dom/push/test/xpcshell/test_register_flush.js | 6 +- .../xpcshell/test_register_invalid_json.js | 3 +- dom/push/test/xpcshell/test_register_no_id.js | 3 +- .../xpcshell/test_register_request_queue.js | 7 +- .../test/xpcshell/test_register_rollback.js | 4 +- .../test/xpcshell/test_register_timeout.js | 6 +- .../test/xpcshell/test_register_wrong_id.js | 3 +- .../test/xpcshell/test_register_wrong_type.js | 3 +- .../xpcshell/test_registration_success.js | 6 +- dom/push/test/xpcshell/test_retry_ws.js | 29 +- dom/push/test/xpcshell/test_startup_error.js | 73 ++ .../test/xpcshell/test_unregister_error.js | 3 +- .../xpcshell/test_unregister_invalid_json.js | 3 +- .../test/xpcshell/test_unregister_success.js | 4 +- ...test_updateRecordNoEncryptionKeys_http2.js | 3 +- dom/push/test/xpcshell/xpcshell.ini | 2 + .../mochitest/fetch/fetch_test_framework.js | 3 +- dom/tests/mochitest/fetch/mochitest.ini | 2 - dom/tests/mochitest/fetch/sw_reroute.js | 3 +- .../mochitest/fetch/test_request_cache.html | 19 - dom/webidl/Directory.webidl | 14 +- dom/webidl/PushManager.webidl | 27 +- dom/webidl/PushSubscription.webidl | 5 +- dom/webidl/Request.webidl | 1 - dom/webidl/ThreadSafeChromeUtils.webidl | 50 ++ dom/workers/ServiceWorkerManager.cpp | 9 +- dom/workers/ServiceWorkerRegistration.cpp | 32 +- dom/workers/ServiceWorkerRegistration.h | 22 +- dom/workers/WorkerPrefs.h | 1 - dom/workers/test/gtest/TestReadWrite.cpp | 6 +- .../test_serviceworker_interfaces.js | 2 + dom/workers/test/test_worker_interfaces.js | 2 + dom/xbl/nsXBLEventHandler.cpp | 17 +- dom/xbl/nsXBLPrototypeHandler.h | 19 +- dom/xbl/nsXBLService.cpp | 32 +- dom/xbl/nsXBLWindowKeyHandler.cpp | 372 ++++++--- dom/xbl/nsXBLWindowKeyHandler.h | 37 +- dom/xul/nsXULElement.cpp | 2 +- editor/libeditor/nsEditor.cpp | 2 +- gfx/gl/GLLibraryEGL.cpp | 2 + gfx/ipc/GfxMessageUtils.h | 68 +- gfx/layers/FrameMetrics.cpp | 5 +- gfx/layers/FrameMetrics.h | 227 ++++-- gfx/layers/LayerMetricsWrapper.h | 43 +- gfx/layers/Layers.cpp | 69 +- gfx/layers/Layers.h | 53 +- gfx/layers/LayersLogging.cpp | 17 +- gfx/layers/LayersLogging.h | 4 + .../apz/public/GeckoContentController.h | 3 +- gfx/layers/apz/src/APZCTreeManager.cpp | 2 +- gfx/layers/apz/src/APZUtils.h | 2 +- gfx/layers/apz/src/AsyncPanZoomController.cpp | 307 ++++---- gfx/layers/apz/src/AsyncPanZoomController.h | 35 +- gfx/layers/apz/src/InputBlockState.cpp | 12 +- gfx/layers/apz/src/InputBlockState.h | 3 +- .../apz/test/gtest/APZCTreeManagerTester.h | 21 +- gfx/layers/apz/test/gtest/TestBasic.cpp | 14 +- gfx/layers/apz/util/APZEventState.cpp | 3 +- .../composite/AsyncCompositionManager.cpp | 19 +- .../composite/ContainerLayerComposite.cpp | 7 +- gfx/layers/ipc/LayerTransactionParent.cpp | 23 +- gfx/layers/ipc/LayerTransactionParent.h | 3 - gfx/layers/ipc/LayersMessages.ipdlh | 4 +- gfx/layers/ipc/PLayerTransaction.ipdl | 5 - gfx/layers/ipc/ShadowLayers.cpp | 2 +- gfx/src/DriverCrashGuard.cpp | 3 +- gfx/src/gfxCrashReporterUtils.cpp | 2 +- gfx/tests/gtest/TestLayers.cpp | 64 +- gfx/thebes/gfxPlatform.cpp | 28 +- gfx/thebes/gfxPrefs.h | 1 + gfx/thebes/gfxUtils.cpp | 14 +- gfx/thebes/gfxUtils.h | 1 + gfx/thebes/gfxWindowsPlatform.cpp | 6 +- ipc/glue/BackgroundChildImpl.cpp | 19 + ipc/glue/BackgroundChildImpl.h | 7 + ipc/glue/BackgroundImpl.cpp | 2 +- ipc/glue/BackgroundParentImpl.cpp | 195 ++++- ipc/glue/BackgroundParentImpl.h | 11 + ipc/glue/MessagePump.cpp | 4 +- ipc/glue/PBackground.ipdl | 5 + layout/base/FrameLayerBuilder.cpp | 36 +- layout/base/Units.h | 5 + layout/base/nsDisplayList.cpp | 32 +- layout/base/nsDisplayList.h | 17 +- layout/base/nsDocumentViewer.cpp | 3 +- layout/base/nsIPresShell.h | 47 +- layout/base/nsLayoutUtils.cpp | 120 +-- layout/base/nsLayoutUtils.h | 29 +- layout/base/nsPresShell.cpp | 459 ++++++----- layout/base/nsPresShell.h | 80 +- layout/base/nsRefreshDriver.cpp | 6 +- layout/base/nsRefreshDriver.h | 14 +- layout/forms/nsListControlFrame.cpp | 2 +- layout/generic/ScrollSnap.cpp | 311 ++++++++ layout/generic/ScrollSnap.h | 41 + layout/generic/TextOverflow.cpp | 5 +- layout/generic/Visibility.h | 46 ++ layout/generic/moz.build | 4 +- layout/generic/nsFrame.cpp | 222 +++++- layout/generic/nsFrameStateBits.h | 4 + layout/generic/nsGfxScrollFrame.cpp | 449 +++-------- layout/generic/nsGfxScrollFrame.h | 57 +- layout/generic/nsIFrame.h | 94 +++ layout/generic/nsIScrollableFrame.h | 22 +- layout/generic/nsImageFrame.cpp | 36 +- layout/generic/nsImageFrame.h | 7 + layout/generic/nsPluginFrame.cpp | 2 +- layout/generic/nsRubyTextContainerFrame.cpp | 1 + layout/generic/nsSubDocumentFrame.cpp | 4 +- layout/generic/nsVideoFrame.cpp | 25 +- layout/generic/nsVideoFrame.h | 11 +- layout/ipc/RenderFrameParent.cpp | 48 +- layout/ipc/RenderFrameParent.h | 7 +- layout/svg/SVGFEImageFrame.cpp | 39 +- layout/svg/nsSVGImageFrame.cpp | 38 +- layout/xul/nsButtonBoxFrame.cpp | 2 +- layout/xul/nsMenuBarFrame.cpp | 5 +- layout/xul/nsMenuBarListener.cpp | 2 +- layout/xul/nsMenuFrame.cpp | 2 +- layout/xul/nsResizerFrame.cpp | 3 +- layout/xul/nsScrollBoxFrame.cpp | 4 +- layout/xul/nsTitleBarFrame.cpp | 3 +- media/mtransport/build/moz.build | 2 +- media/mtransport/common.build | 2 +- media/mtransport/nr_socket_prsock.cpp | 5 +- ...cket_filter.cpp => stun_socket_filter.cpp} | 171 +++- media/mtransport/stun_socket_filter.h | 36 + media/mtransport/stun_udp_socket_filter.h | 24 - media/mtransport/test/ice_unittest.cpp | 396 ++++++---- .../peerconnection/MediaPipelineFactory.cpp | 6 +- modules/libpref/init/all.js | 15 +- netwerk/base/moz.build | 3 +- .../base/nsIPackagedAppChannelListener.idl | 25 - ...DPSocketFilter.idl => nsISocketFilter.idl} | 20 +- netwerk/base/nsSocketTransportService2.cpp | 1 + netwerk/cache2/CacheEntry.cpp | 33 +- netwerk/cache2/CacheEntry.h | 9 +- netwerk/cache2/CacheFileContextEvictor.cpp | 4 + netwerk/cache2/CacheIndex.cpp | 59 +- netwerk/cache2/CacheIndex.h | 11 + netwerk/cache2/CacheStorage.cpp | 13 +- netwerk/cache2/CacheStorageService.cpp | 114 ++- netwerk/cache2/CacheStorageService.h | 25 +- netwerk/protocol/http/HttpChannelChild.cpp | 10 +- netwerk/protocol/http/HttpChannelChild.h | 8 +- netwerk/protocol/http/HttpChannelParent.cpp | 56 +- netwerk/protocol/http/HttpChannelParent.h | 3 - .../http/HttpChannelParentListener.cpp | 18 - .../protocol/http/HttpChannelParentListener.h | 3 - netwerk/protocol/http/PHttpChannel.ipdl | 4 +- netwerk/protocol/http/PackagedAppService.cpp | 70 +- netwerk/protocol/http/PackagedAppService.h | 7 +- netwerk/protocol/http/nsHttpChannel.cpp | 28 +- netwerk/protocol/http/nsHttpChannel.h | 3 - .../protocol/websocket/WebSocketChannel.cpp | 2 +- netwerk/test/mochitests/redirect.sjs | 8 + ...bout_blank_to_signed_web_packaged_app.html | 64 -- .../test/mochitests/test_redirect_ref.html | 30 + .../test_signed_web_packaged_app.html | 70 -- .../test_signed_web_packaged_app_origin.html | 94 --- .../test/unit/test_packaged_app_service.js | 80 -- security/manager/ssl/nsNSSCertificate.cpp | 17 +- security/manager/ssl/nsNSSCertificate.h | 4 + security/manager/ssl/nsNSSCertificateDB.cpp | 39 +- security/manager/ssl/nsNSSCertificateDB.h | 5 + security/manager/ssl/nsNSSComponent.cpp | 470 ++++++++++- security/manager/ssl/nsNSSModule.cpp | 2 +- services/mobileid/MobileIdentityManager.jsm | 10 +- testing/xpcshell/moz-http2/moz-http2.js | 28 +- toolkit/components/telemetry/Histograms.json | 8 + toolkit/content/aboutSupport.js | 2 + toolkit/devtools/Console.jsm | 23 +- toolkit/identity/IdentityCryptoService.cpp | 34 +- toolkit/identity/nsIIdentityCryptoService.idl | 2 +- .../chrome/global/aboutSupport.properties | 4 +- .../test/xpcshell/data/test_gfxBlacklist.xml | 4 +- toolkit/xre/nsAppRunner.cpp | 175 +++-- toolkit/xre/nsAppRunner.h | 7 + widget/BasicEvents.h | 78 +- widget/EventForwards.h | 6 + widget/GfxDriverInfo.cpp | 7 +- widget/GfxDriverInfo.h | 22 +- widget/GfxInfoBase.cpp | 68 +- widget/GfxInfoBase.h | 4 +- widget/GfxInfoX11.cpp | 21 +- widget/GfxInfoX11.h | 9 +- widget/InputData.h | 5 + widget/TextEventDispatcher.cpp | 49 +- widget/TextEvents.h | 61 ++ widget/TouchEvents.h | 9 +- widget/WidgetEventImpl.cpp | 166 ++++ widget/cocoa/GfxInfo.h | 7 +- widget/cocoa/GfxInfo.mm | 26 +- widget/cocoa/TextInputHandler.mm | 30 +- widget/cocoa/nsChildView.mm | 14 + widget/gtk/nsGtkKeyUtils.cpp | 38 +- widget/nsBaseWidget.cpp | 18 +- widget/nsBaseWidget.h | 2 +- widget/nsGUIEventIPC.h | 2 + widget/nsIGfxInfo.idl | 6 +- widget/tests/test_keycodes.xul | 541 +++++++++---- widget/windows/GfxInfo.cpp | 170 ++-- widget/windows/GfxInfo.h | 9 +- widget/windows/KeyboardLayout.cpp | 33 +- widget/windows/WinUtils.cpp | 317 +++++++- widget/windows/WinUtils.h | 31 +- widget/windows/nsScreenWin.cpp | 7 +- widget/windows/nsWindow.cpp | 4 +- xpcom/base/DebuggerOnGCRunnable.h | 2 +- xpcom/base/nsCycleCollector.cpp | 2 +- xpcom/glue/nsThreadUtils.cpp | 4 +- xpcom/glue/nsThreadUtils.h | 6 +- xpcom/io/Base64.cpp | 154 +++- xpcom/io/Base64.h | 18 +- xpcom/io/nsStreamUtils.cpp | 12 +- xpcom/system/nsIXULRuntime.idl | 7 + xpcom/threads/TimerThread.cpp | 2 +- 392 files changed, 11612 insertions(+), 6523 deletions(-) create mode 100644 dom/base/test/unit/test_chromeutils_base64.js create mode 100644 dom/filesystem/PFileSystemParams.ipdlh create mode 100644 dom/filesystem/tests/test_worker_basic.html create mode 100644 dom/filesystem/tests/worker_basic.js create mode 100644 dom/push/PushServiceAndroidGCM.jsm create mode 100644 dom/push/PushSubscription.cpp create mode 100644 dom/push/PushSubscription.h create mode 100644 dom/push/test/error_worker.js create mode 100644 dom/push/test/test_error_reporting.html create mode 100644 dom/push/test/xpcshell/test_crypto.js create mode 100644 dom/push/test/xpcshell/test_startup_error.js delete mode 100644 dom/tests/mochitest/fetch/test_request_cache.html create mode 100644 layout/generic/ScrollSnap.cpp create mode 100644 layout/generic/ScrollSnap.h create mode 100644 layout/generic/Visibility.h rename media/mtransport/{stun_udp_socket_filter.cpp => stun_socket_filter.cpp} (56%) create mode 100644 media/mtransport/stun_socket_filter.h delete mode 100644 media/mtransport/stun_udp_socket_filter.h delete mode 100644 netwerk/base/nsIPackagedAppChannelListener.idl rename netwerk/base/{nsIUDPSocketFilter.idl => nsISocketFilter.idl} (60%) create mode 100644 netwerk/test/mochitests/redirect.sjs delete mode 100644 netwerk/test/mochitests/test_about_blank_to_signed_web_packaged_app.html create mode 100644 netwerk/test/mochitests/test_redirect_ref.html delete mode 100644 netwerk/test/mochitests/test_signed_web_packaged_app.html delete mode 100644 netwerk/test/mochitests/test_signed_web_packaged_app_origin.html diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index f84fa620d9..5acdfdd8db 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -855,8 +855,8 @@ pref("memory.system_memory_reporter", true); // Don't dump memory reports on OOM, by default. pref("memory.dump_reports_on_oom", false); -pref("layout.imagevisibility.numscrollportwidths", 1); -pref("layout.imagevisibility.numscrollportheights", 1); +pref("layout.framevisibility.numscrollportwidths", 1); +pref("layout.framevisibility.numscrollportheights", 1); // Enable native identity (persona/browserid) pref("dom.identity.enabled", true); diff --git a/dom/apps/Webapps.jsm b/dom/apps/Webapps.jsm index 0f5adeed8e..6bc82b328f 100755 --- a/dom/apps/Webapps.jsm +++ b/dom/apps/Webapps.jsm @@ -4370,10 +4370,12 @@ this.DOMApplicationRegistry = { return "INVALID_SEGMENTS_NUMBER"; } - // We need to translate the base64 alphabet used in JWT to our base64 alphabet - // before calling atob. - let decodedReceipt = JSON.parse(atob(segments[1].replace(/-/g, '+') - .replace(/_/g, '/'))); + let jwtBuffer = ChromeUtils.base64URLDecode(segments[1], { + // JWT/JWS prohibits padding per RFC 7515, section 2. + padding: "reject", + }); + let textDecoder = new TextDecoder("utf-8"); + let decodedReceipt = JSON.parse(textDecoder.decode(jwtBuffer)); if (!decodedReceipt) { return "INVALID_RECEIPT_ENCODING"; } diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index 34d0a1e373..f087a67c29 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -5,6 +5,7 @@ #include "ChromeUtils.h" +#include "mozilla/Base64.h" #include "mozilla/BasePrincipal.h" namespace mozilla { @@ -50,6 +51,61 @@ ThreadSafeChromeUtils::NondeterministicGetWeakSetKeys(GlobalObject& aGlobal, } } +/* static */ void +ThreadSafeChromeUtils::Base64URLEncode(GlobalObject& aGlobal, + const ArrayBufferViewOrArrayBuffer& aSource, + const Base64URLEncodeOptions& aOptions, + nsACString& aResult, + ErrorResult& aRv) +{ + size_t length = 0; + uint8_t* data = nullptr; + if (aSource.IsArrayBuffer()) { + const ArrayBuffer& buffer = aSource.GetAsArrayBuffer(); + buffer.ComputeLengthAndData(); + length = buffer.Length(); + data = buffer.Data(); + } else if (aSource.IsArrayBufferView()) { + const ArrayBufferView& view = aSource.GetAsArrayBufferView(); + view.ComputeLengthAndData(); + length = view.Length(); + data = view.Data(); + } else { + MOZ_CRASH("Uninitialized union: expected buffer or view"); + } + + nsresult rv = mozilla::Base64URLEncode(length, data, aOptions, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + aResult.Truncate(); + aRv.Throw(rv); + } +} + +/* static */ void +ThreadSafeChromeUtils::Base64URLDecode(GlobalObject& aGlobal, + const nsACString& aString, + const Base64URLDecodeOptions& aOptions, + JS::MutableHandle aRetval, + ErrorResult& aRv) +{ + FallibleTArray data; + nsresult rv = mozilla::Base64URLDecode(aString, aOptions, data); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + JS::Rooted buffer(aGlobal.Context(), + ArrayBuffer::Create(aGlobal.Context(), + data.Length(), + data.Elements())); + if (NS_WARN_IF(!buffer)) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + aRetval.set(buffer); +} + /* static */ void ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs, diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index cf1f30a402..9eace5c27f 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -20,6 +20,8 @@ class HeapSnapshot; namespace dom { +class ArrayBufferViewOrArrayBuffer; + class ThreadSafeChromeUtils { public: @@ -43,6 +45,18 @@ public: JS::Handle aSet, JS::MutableHandle aRetval, ErrorResult& aRv); + + static void Base64URLEncode(GlobalObject& aGlobal, + const ArrayBufferViewOrArrayBuffer& aSource, + const Base64URLEncodeOptions& aOptions, + nsACString& aResult, + ErrorResult& aRv); + + static void Base64URLDecode(GlobalObject& aGlobal, + const nsACString& aString, + const Base64URLDecodeOptions& aOptions, + JS::MutableHandle aRetval, + ErrorResult& aRv); }; class ChromeUtils : public ThreadSafeChromeUtils diff --git a/dom/base/Crypto.cpp b/dom/base/Crypto.cpp index 8dd9c26c66..34ff31f7fb 100644 --- a/dom/base/Crypto.cpp +++ b/dom/base/Crypto.cpp @@ -58,8 +58,6 @@ Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray, JS::MutableHandle aRetval, ErrorResult& aRv) { - MOZ_ASSERT(NS_IsMainThread(), "Called on the wrong thread"); - JS::Rooted view(aCx, aArray.Obj()); if (JS_IsTypedArrayObject(view) && JS_GetTypedArraySharedness(view)) { @@ -95,32 +93,24 @@ Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray, return; } - uint8_t* data = aArray.Data(); - - if (!XRE_IsParentProcess()) { - InfallibleTArray randomValues; - // Tell the parent process to generate random values via PContent - ContentChild* cc = ContentChild::GetSingleton(); - if (!cc->SendGetRandomValues(dataLen, &randomValues) || - randomValues.Length() == 0) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - NS_ASSERTION(dataLen == randomValues.Length(), - "Invalid length returned from parent process!"); - memcpy(data, randomValues.Elements(), dataLen); - } else { - uint8_t *buf = GetRandomValues(dataLen); - - if (!buf) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - - memcpy(data, buf, dataLen); - NS_Free(buf); + nsCOMPtr randomGenerator = + do_GetService("@mozilla.org/security/random-generator;1"); + if (!randomGenerator) { + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); + return; } + uint8_t* buf; + nsresult rv = randomGenerator->GenerateRandomBytes(dataLen, &buf); + if (NS_FAILED(rv) || !buf) { + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); + return; + } + + // Copy random bytes to ABV. + memcpy(aArray.Data(), buf, dataLen); + NS_Free(buf); + aRetval.set(view); } @@ -133,21 +123,5 @@ Crypto::Subtle() return mSubtle; } -/* static */ uint8_t* -Crypto::GetRandomValues(uint32_t aLength) -{ - nsCOMPtr randomGenerator; - nsresult rv; - randomGenerator = do_GetService("@mozilla.org/security/random-generator;1"); - NS_ENSURE_TRUE(randomGenerator, nullptr); - - uint8_t* buf; - rv = randomGenerator->GenerateRandomBytes(aLength, &buf); - - NS_ENSURE_SUCCESS(rv, nullptr); - - return buf; -} - } // namespace dom } // namespace mozilla diff --git a/dom/base/Crypto.h b/dom/base/Crypto.h index 3d4f0d76ff..e6f8969d75 100644 --- a/dom/base/Crypto.h +++ b/dom/base/Crypto.h @@ -54,9 +54,6 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - static uint8_t* - GetRandomValues(uint32_t aLength); - private: nsCOMPtr mParent; RefPtr mSubtle; diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index 34ebeb3cc0..8309951856 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -2116,7 +2116,7 @@ Element::DispatchClickEvent(nsPresContext* aPresContext, NS_PRECONDITION(aSourceEvent, "Must have source event"); NS_PRECONDITION(aStatus, "Null out param?"); - WidgetMouseEvent event(aSourceEvent->mFlags.mIsTrusted, eMouseClick, + WidgetMouseEvent event(aSourceEvent->IsTrusted(), eMouseClick, aSourceEvent->widget, WidgetMouseEvent::eReal); event.refPoint = aSourceEvent->refPoint; uint32_t clickCount = 1; @@ -2929,7 +2929,7 @@ Element::CheckHandleEventForLinksPrecondition(EventChainVisitor& aVisitor, nsIURI** aURI) const { if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault || - (!aVisitor.mEvent->mFlags.mIsTrusted && + (!aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->mMessage != eMouseClick) && (aVisitor.mEvent->mMessage != eKeyPress) && (aVisitor.mEvent->mMessage != eLegacyDOMActivate)) || diff --git a/dom/base/FileList.cpp b/dom/base/FileList.cpp index 4018dc1e53..41dd07a71d 100644 --- a/dom/base/FileList.cpp +++ b/dom/base/FileList.cpp @@ -115,7 +115,8 @@ bool FileList::ClonableToDifferentThreadOrProcess() const { for (uint32_t i = 0; i < mFilesOrDirectories.Length(); ++i) { - if (mFilesOrDirectories[i].IsDirectory()) { + if (mFilesOrDirectories[i].IsDirectory() && + !mFilesOrDirectories[i].GetAsDirectory()->ClonableToDifferentThreadOrProcess()) { return false; } } diff --git a/dom/base/ImageEncoder.cpp b/dom/base/ImageEncoder.cpp index 5bbad65d68..113dcf0623 100644 --- a/dom/base/ImageEncoder.cpp +++ b/dom/base/ImageEncoder.cpp @@ -72,7 +72,7 @@ GetBRGADataSourceSurfaceSync(already_AddRefed aImage) return helper->GetDataSurfaceSafe(); } -class EncodingCompleteEvent : public nsCancelableRunnable +class EncodingCompleteEvent : public CancelableRunnable { virtual ~EncodingCompleteEvent() {} diff --git a/dom/base/StructuredCloneHolder.cpp b/dom/base/StructuredCloneHolder.cpp index 5c917fde18..a1d823def6 100644 --- a/dom/base/StructuredCloneHolder.cpp +++ b/dom/base/StructuredCloneHolder.cpp @@ -11,6 +11,7 @@ #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/CryptoKey.h" #include "mozilla/dom/Directory.h" +#include "mozilla/dom/DirectoryBinding.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileList.h" #include "mozilla/dom/FileListBinding.h" @@ -663,7 +664,7 @@ ReadBlob(JSContext* aCx, MOZ_ASSERT(blobImpl); - // RefPtr needs to go out of scope before toObjectOrNull() is + // RefPtr needs to go out of scope before toObject() is // called because the static analysis thinks dereferencing XPCOM objects // can GC (because in some cases it can!), and a return statement with a // JSObject* type means that JSObject* is on the stack as a raw pointer @@ -710,6 +711,80 @@ WriteBlob(JSStructuredCloneWriter* aWriter, return false; } +// A directory is serialized as: +// - pair of ints: SCTAG_DOM_DIRECTORY, 0 +// - pair of ints: type (eDOMRootDirectory/eDOMNotRootDirectory) - path length +// - path as string +bool +WriteDirectory(JSStructuredCloneWriter* aWriter, + Directory* aDirectory) +{ + MOZ_ASSERT(aWriter); + MOZ_ASSERT(aDirectory); + + nsAutoString path; + aDirectory->GetFullRealPath(path); + + size_t charSize = sizeof(nsString::char_type); + return JS_WriteUint32Pair(aWriter, SCTAG_DOM_DIRECTORY, 0) && + JS_WriteUint32Pair(aWriter, (uint32_t)aDirectory->Type(), + path.Length()) && + JS_WriteBytes(aWriter, path.get(), path.Length() * charSize); +} + +JSObject* +ReadDirectory(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aZero, + StructuredCloneHolder* aHolder) +{ + MOZ_ASSERT(aCx); + MOZ_ASSERT(aReader); + MOZ_ASSERT(aHolder); + MOZ_ASSERT(aZero == 0); + + uint32_t directoryType, lengthOfString; + if (!JS_ReadUint32Pair(aReader, &directoryType, &lengthOfString)) { + return nullptr; + } + + MOZ_ASSERT(directoryType == Directory::eDOMRootDirectory || + directoryType == Directory::eNotDOMRootDirectory); + + nsAutoString path; + path.SetLength(lengthOfString); + size_t charSize = sizeof(nsString::char_type); + if (!JS_ReadBytes(aReader, (void*) path.BeginWriting(), + lengthOfString * charSize)) { + return nullptr; + } + + nsCOMPtr file; + nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, + getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + // RefPtr needs to go out of scope before toObject() is + // called because the static analysis thinks dereferencing XPCOM objects + // can GC (because in some cases it can!), and a return statement with a + // JSObject* type means that JSObject* is on the stack as a raw pointer + // while destructors are running. + JS::Rooted val(aCx); + { + RefPtr directory = + Directory::Create(aHolder->ParentDuringRead(), file, + (Directory::DirectoryType) directoryType); + + if (!ToJSValue(aCx, directory, &val)) { + return nullptr; + } + } + + return &val.toObject(); +} + // Read the WriteFileList for the format. JSObject* ReadFileList(JSContext* aCx, @@ -1007,6 +1082,10 @@ StructuredCloneHolder::CustomReadHandler(JSContext* aCx, return ReadBlob(aCx, aIndex, this); } + if (aTag == SCTAG_DOM_DIRECTORY) { + return ReadDirectory(aCx, aReader, aIndex, this); + } + if (aTag == SCTAG_DOM_FILELIST) { return ReadFileList(aCx, aReader, aIndex, this); } @@ -1047,6 +1126,14 @@ StructuredCloneHolder::CustomWriteHandler(JSContext* aCx, } } + // See if this is a Directory object. + { + Directory* directory = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Directory, aObj, directory))) { + return WriteDirectory(aWriter, directory); + } + } + // See if this is a FileList object. { FileList* fileList = nullptr; diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index 55be6f6ae6..abba5567e1 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -17,7 +17,6 @@ namespace dom { enum StructuredCloneTags { SCTAG_BASE = JS_SCTAG_USER_MIN, - // These tags are used only for main thread structured clone. SCTAG_DOM_BLOB, // This tag is obsolete and exists only for backwards compatibility with @@ -53,6 +52,8 @@ enum StructuredCloneTags { SCTAG_DOM_EXPANDED_PRINCIPAL, + SCTAG_DOM_DIRECTORY, + SCTAG_DOM_MAX }; diff --git a/dom/base/TextInputProcessor.cpp b/dom/base/TextInputProcessor.cpp index 6970add527..dd0c78317f 100644 --- a/dom/base/TextInputProcessor.cpp +++ b/dom/base/TextInputProcessor.cpp @@ -712,7 +712,8 @@ TextInputProcessor::WillDispatchKeyboardEvent( uint32_t aIndexOfKeypress, void* aData) { - // TextInputProcessor doesn't set alternative char code. + // TextInputProcessor doesn't set alternative char code nor modify charCode + // even when Ctrl key is pressed. } nsresult diff --git a/dom/base/WebSocket.cpp b/dom/base/WebSocket.cpp index cc64f1f738..2885280832 100644 --- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -254,7 +254,7 @@ NS_IMPL_ISUPPORTS(WebSocketImpl, nsIRequest, nsIEventTarget) -class CallDispatchConnectionCloseEvents final : public nsCancelableRunnable +class CallDispatchConnectionCloseEvents final : public CancelableRunnable { public: explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl) @@ -1251,7 +1251,7 @@ WebSocket::Constructor(const GlobalObject& aGlobal, RefPtr runnable = new InitRunnable(webSocket->mImpl, aUrl, protocolArray, - nsAutoCString(file.get()), lineno, column, aRv, + nsDependentCString(file.get()), lineno, column, aRv, &connectionFailed); runnable->Dispatch(aRv); } diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index e2cf05dbf4..4241c17188 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -3496,7 +3496,8 @@ nsContentUtils::ReportToConsoleNonLocalized(const nsAString& aErrorText, nsIURI* aURI, const nsAFlatString& aSourceLine, uint32_t aLineNumber, - uint32_t aColumnNumber) + uint32_t aColumnNumber, + MissingErrorLocationMode aLocationMode) { uint64_t innerWindowID = 0; if (aDocument) { @@ -3513,14 +3514,15 @@ nsContentUtils::ReportToConsoleNonLocalized(const nsAString& aErrorText, } nsAutoCString spec; - if (!aLineNumber) { + if (!aLineNumber && aLocationMode == eUSE_CALLING_LOCATION) { JSContext *cx = GetCurrentJSContext(); if (cx) { nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber); } } - if (spec.IsEmpty() && aURI) + if (spec.IsEmpty() && aURI) { aURI->GetSpec(spec); + } nsCOMPtr errorObject = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv); @@ -4922,188 +4924,6 @@ nsContentUtils::GetLocalizedEllipsis() return nsDependentString(sBuf); } -static bool -HasASCIIDigit(const nsTArray& aCandidates) -{ - for (uint32_t i = 0; i < aCandidates.Length(); ++i) { - uint32_t ch = aCandidates[i].mCharCode; - if (ch >= '0' && ch <= '9') - return true; - } - return false; -} - -static bool -CharsCaseInsensitiveEqual(uint32_t aChar1, uint32_t aChar2) -{ - return aChar1 == aChar2 || - (IS_IN_BMP(aChar1) && IS_IN_BMP(aChar2) && - ToLowerCase(char16_t(aChar1)) == ToLowerCase(char16_t(aChar2))); -} - -static bool -IsCaseChangeableChar(uint32_t aChar) -{ - return IS_IN_BMP(aChar) && - ToLowerCase(char16_t(aChar)) != ToUpperCase(char16_t(aChar)); -} - -/* static */ -void -nsContentUtils::GetAccelKeyCandidates(nsIDOMKeyEvent* aDOMKeyEvent, - nsTArray& aCandidates) -{ - NS_PRECONDITION(aCandidates.IsEmpty(), "aCandidates must be empty"); - - nsAutoString eventType; - aDOMKeyEvent->AsEvent()->GetType(eventType); - // Don't process if aDOMKeyEvent is not a keypress event. - if (!eventType.EqualsLiteral("keypress")) - return; - - WidgetKeyboardEvent* nativeKeyEvent = - aDOMKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); - if (nativeKeyEvent) { - NS_ASSERTION(nativeKeyEvent->mClass == eKeyboardEventClass, - "wrong type of native event"); - // nsShortcutCandidate::mCharCode is a candidate charCode. - // nsShoftcutCandidate::mIgnoreShift means the mCharCode should be tried to - // execute a command with/without shift key state. If this is TRUE, the - // shifted key state should be ignored. Otherwise, don't ignore the state. - // the priority of the charCodes are (shift key is not pressed): - // 0: charCode/false, - // 1: unshiftedCharCodes[0]/false, 2: unshiftedCharCodes[1]/false... - // the priority of the charCodes are (shift key is pressed): - // 0: charCode/false, - // 1: shiftedCharCodes[0]/false, 2: shiftedCharCodes[0]/true, - // 3: shiftedCharCodes[1]/false, 4: shiftedCharCodes[1]/true... - if (nativeKeyEvent->charCode) { - nsShortcutCandidate key(nativeKeyEvent->charCode, false); - aCandidates.AppendElement(key); - } - - uint32_t len = nativeKeyEvent->alternativeCharCodes.Length(); - if (!nativeKeyEvent->IsShift()) { - for (uint32_t i = 0; i < len; ++i) { - uint32_t ch = - nativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode; - if (!ch || ch == nativeKeyEvent->charCode) - continue; - - nsShortcutCandidate key(ch, false); - aCandidates.AppendElement(key); - } - // If unshiftedCharCodes doesn't have numeric but shiftedCharCode has it, - // this keyboard layout is AZERTY or similar layout, probably. - // In this case, Accel+[0-9] should be accessible without shift key. - // However, the priority should be lowest. - if (!HasASCIIDigit(aCandidates)) { - for (uint32_t i = 0; i < len; ++i) { - uint32_t ch = - nativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode; - if (ch >= '0' && ch <= '9') { - nsShortcutCandidate key(ch, false); - aCandidates.AppendElement(key); - break; - } - } - } - } else { - for (uint32_t i = 0; i < len; ++i) { - uint32_t ch = nativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode; - if (!ch) - continue; - - if (ch != nativeKeyEvent->charCode) { - nsShortcutCandidate key(ch, false); - aCandidates.AppendElement(key); - } - - // If the char is an alphabet, the shift key state should not be - // ignored. E.g., Ctrl+Shift+C should not execute Ctrl+C. - - // And checking the charCode is same as unshiftedCharCode too. - // E.g., for Ctrl+Shift+(Plus of Numpad) should not run Ctrl+Plus. - uint32_t unshiftCh = - nativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode; - if (CharsCaseInsensitiveEqual(ch, unshiftCh)) - continue; - - // On the Hebrew keyboard layout on Windows, the unshifted char is a - // localized character but the shifted char is a Latin alphabet, - // then, we should not execute without the shift state. See bug 433192. - if (IsCaseChangeableChar(ch)) - continue; - - // Setting the alternative charCode candidates for retry without shift - // key state only when the shift key is pressed. - nsShortcutCandidate key(ch, true); - aCandidates.AppendElement(key); - } - } - - // Special case for "Space" key. With some keyboard layouts, "Space" with - // or without Shift key causes non-ASCII space. For such keyboard layouts, - // we should guarantee that the key press works as an ASCII white space key - // press. - if (nativeKeyEvent->mCodeNameIndex == CODE_NAME_INDEX_Space && - nativeKeyEvent->charCode != static_cast(' ')) { - nsShortcutCandidate spaceKey(static_cast(' '), false); - aCandidates.AppendElement(spaceKey); - } - } else { - uint32_t charCode; - aDOMKeyEvent->GetCharCode(&charCode); - if (charCode) { - nsShortcutCandidate key(charCode, false); - aCandidates.AppendElement(key); - } - } -} - -/* static */ -void -nsContentUtils::GetAccessKeyCandidates(WidgetKeyboardEvent* aNativeKeyEvent, - nsTArray& aCandidates) -{ - NS_PRECONDITION(aCandidates.IsEmpty(), "aCandidates must be empty"); - - // return the lower cased charCode candidates for access keys. - // the priority of the charCodes are: - // 0: charCode, 1: unshiftedCharCodes[0], 2: shiftedCharCodes[0] - // 3: unshiftedCharCodes[1], 4: shiftedCharCodes[1],... - if (aNativeKeyEvent->charCode) { - uint32_t ch = aNativeKeyEvent->charCode; - if (IS_IN_BMP(ch)) - ch = ToLowerCase(char16_t(ch)); - aCandidates.AppendElement(ch); - } - for (uint32_t i = 0; - i < aNativeKeyEvent->alternativeCharCodes.Length(); ++i) { - uint32_t ch[2] = - { aNativeKeyEvent->alternativeCharCodes[i].mUnshiftedCharCode, - aNativeKeyEvent->alternativeCharCodes[i].mShiftedCharCode }; - for (uint32_t j = 0; j < 2; ++j) { - if (!ch[j]) - continue; - if (IS_IN_BMP(ch[j])) - ch[j] = ToLowerCase(char16_t(ch[j])); - // Don't append the charCode that was already appended. - if (aCandidates.IndexOf(ch[j]) == aCandidates.NoIndex) - aCandidates.AppendElement(ch[j]); - } - } - // Special case for "Space" key. With some keyboard layouts, "Space" with - // or without Shift key causes non-ASCII space. For such keyboard layouts, - // we should guarantee that the key press works as an ASCII white space key - // press. - if (aNativeKeyEvent->mCodeNameIndex == CODE_NAME_INDEX_Space && - aNativeKeyEvent->charCode != static_cast(' ')) { - aCandidates.AppendElement(static_cast(' ')); - } - return; -} - /* static */ void nsContentUtils::AddScriptBlocker() @@ -5426,8 +5246,9 @@ nsContentUtils::GetDragSession() nsresult nsContentUtils::SetDataTransferInEvent(WidgetDragEvent* aDragEvent) { - if (aDragEvent->dataTransfer || !aDragEvent->mFlags.mIsTrusted) + if (aDragEvent->dataTransfer || !aDragEvent->IsTrusted()) { return NS_OK; + } // For draggesture and dragstart events, the data transfer object is // created before the event fires, so it should already be set. For other @@ -7951,7 +7772,7 @@ nsContentUtils::SendKeyEvent(nsIWidget* aWidget, } if (aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_PREVENT_DEFAULT) { - event.mFlags.mDefaultPrevented = true; + event.PreventDefaultBeforeDispatch(); } nsEventStatus status; diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 5dec7373b3..f8d69e8ed5 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -171,15 +171,6 @@ struct EventNameMapping mozilla::EventClassID mEventClassID; }; -struct nsShortcutCandidate { - nsShortcutCandidate(uint32_t aCharCode, bool aIgnoreShift) : - mCharCode(aCharCode), mIgnoreShift(aIgnoreShift) - { - } - uint32_t mCharCode; - bool mIgnoreShift; -}; - typedef void (*CallOnRemoteChildFunction) (mozilla::dom::TabParent* aTabParent, void* aArg); @@ -817,7 +808,15 @@ public: * @param [aColumnNumber=0] (Optional) Column number within resource containing error. If aURI is null, then aDocument->GetDocumentURI() is used. + * @param [aLocationMode] (Optional) Specifies the behavior if + error location information is omitted. */ + enum MissingErrorLocationMode { + // Don't show location information in the error console. + eOMIT_LOCATION, + // Get location information from the currently executing script. + eUSE_CALLING_LOCATION + }; static nsresult ReportToConsoleNonLocalized(const nsAString& aErrorText, uint32_t aErrorFlags, const nsACString& aCategory, @@ -826,7 +825,9 @@ public: const nsAFlatString& aSourceLine = EmptyString(), uint32_t aLineNumber = 0, - uint32_t aColumnNumber = 0); + uint32_t aColumnNumber = 0, + MissingErrorLocationMode aLocationMode + = eUSE_CALLING_LOCATION); /** * Report a localized error message to the error console. @@ -1501,27 +1502,6 @@ public: */ static const nsDependentString GetLocalizedEllipsis(); - /** - * Get the candidates for accelkeys for aDOMKeyEvent. - * - * @param aDOMKeyEvent [in] the key event for accelkey handling. - * @param aCandidates [out] the candidate shortcut key combination list. - * the first item is most preferred. - */ - static void GetAccelKeyCandidates(nsIDOMKeyEvent* aDOMKeyEvent, - nsTArray& aCandidates); - - /** - * Get the candidates for accesskeys for aNativeKeyEvent. - * - * @param aNativeKeyEvent [in] the key event for accesskey handling. - * @param aCandidates [out] the candidate access key list. - * the first item is most preferred. - */ - static void GetAccessKeyCandidates( - mozilla::WidgetKeyboardEvent* aNativeKeyEvent, - nsTArray& aCandidates); - /** * Hide any XUL popups associated with aDocument, including any documents * displayed in child frames. Does nothing if aDocument is null. diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp index 5fc4478d07..652297d71e 100644 --- a/dom/base/nsFrameLoader.cpp +++ b/dom/base/nsFrameLoader.cpp @@ -2573,8 +2573,8 @@ nsFrameLoader::DoSendAsyncMessage(JSContext* aCx, if (aCpows && (!mgr || !mgr->Wrap(aCx, aCpows, &cpows))) { return NS_ERROR_UNEXPECTED; } - if (tabParent->SendAsyncMessage(nsString(aMessage), data, cpows, - IPC::Principal(aPrincipal))) { + if (tabParent->SendAsyncMessage(nsString(aMessage), cpows, + IPC::Principal(aPrincipal), data)) { return NS_OK; } else { return NS_ERROR_UNEXPECTED; diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp index e8a7796ff0..2d06322d42 100644 --- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -2033,8 +2033,8 @@ public: if (aCpows && !cc->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) { return NS_ERROR_UNEXPECTED; } - if (!cc->SendAsyncMessage(PromiseFlatString(aMessage), data, cpows, - IPC::Principal(aPrincipal))) { + if (!cc->SendAsyncMessage(PromiseFlatString(aMessage), cpows, + IPC::Principal(aPrincipal), data)) { return NS_ERROR_UNEXPECTED; } diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index c567c9b8a3..16776eb8fa 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -306,6 +306,7 @@ GK_ATOM(directionality, "directionality") GK_ATOM(directory, "directory") GK_ATOM(disableOutputEscaping, "disable-output-escaping") GK_ATOM(disabled, "disabled") +GK_ATOM(disableglobalhistory, "disableglobalhistory") GK_ATOM(disablehistory, "disablehistory") GK_ATOM(display, "display") GK_ATOM(displayMode, "display-mode") @@ -1039,6 +1040,7 @@ GK_ATOM(renderingobserverlist, "renderingobserverlist") GK_ATOM(repeat, "repeat") GK_ATOM(replace, "replace") GK_ATOM(required, "required") +GK_ATOM(reserved, "reserved") GK_ATOM(reset, "reset") GK_ATOM(resizeafter, "resizeafter") GK_ATOM(resizebefore, "resizebefore") diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 14c1fce641..505bff7801 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -3112,7 +3112,7 @@ nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor) gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->time), sizeof(uint32_t)); } - } else if (msg == eResize && aVisitor.mEvent->mFlags.mIsTrusted) { + } else if (msg == eResize && aVisitor.mEvent->IsTrusted()) { // QIing to window so that we can keep the old behavior also in case // a child window is handling resize. nsCOMPtr window = @@ -3120,10 +3120,10 @@ nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor) if (window) { mIsHandlingResizeEvent = true; } - } else if (msg == eMouseDown && aVisitor.mEvent->mFlags.mIsTrusted) { + } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) { gMouseDown = true; } else if ((msg == eMouseUp || msg == eDragEnd) && - aVisitor.mEvent->mFlags.mIsTrusted) { + aVisitor.mEvent->IsTrusted()) { gMouseDown = false; if (gDragServiceDisabled) { nsCOMPtr ds = @@ -3139,7 +3139,7 @@ nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor) // Handle 'active' event. if (!mIdleObservers.IsEmpty() && - aVisitor.mEvent->mFlags.mIsTrusted && + aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->HasMouseEventMessage() || aVisitor.mEvent->HasDragEventMessage())) { mAddActiveEventFuzzTime = false; @@ -3317,7 +3317,7 @@ nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor) if (aVisitor.mEvent->mMessage == eResize) { mIsHandlingResizeEvent = false; } else if (aVisitor.mEvent->mMessage == eUnload && - aVisitor.mEvent->mFlags.mIsTrusted) { + aVisitor.mEvent->IsTrusted()) { // Execute bindingdetached handlers before we tear ourselves // down. if (mDoc) { @@ -3325,7 +3325,7 @@ nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor) } mIsDocumentLoaded = false; } else if (aVisitor.mEvent->mMessage == eLoad && - aVisitor.mEvent->mFlags.mIsTrusted) { + aVisitor.mEvent->IsTrusted()) { // This is page load event since load events don't propagate to |window|. // @see nsDocument::PreHandleEvent. mIsDocumentLoaded = true; @@ -3338,7 +3338,7 @@ nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor) // onload event for the frame element. nsEventStatus status = nsEventStatus_eIgnore; - WidgetEvent event(aVisitor.mEvent->mFlags.mIsTrusted, eLoad); + WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad); event.mFlags.mBubbles = false; // Most of the time we could get a pres context to pass in here, diff --git a/dom/base/nsIImageLoadingContent.idl b/dom/base/nsIImageLoadingContent.idl index 0a469c165e..fea261a34c 100644 --- a/dom/base/nsIImageLoadingContent.idl +++ b/dom/base/nsIImageLoadingContent.idl @@ -5,6 +5,11 @@ #include "imgINotificationObserver.idl" +%{C++ +#include "mozilla/Maybe.h" +#include "Visibility.h" +%} + interface imgIRequest; interface nsIChannel; interface nsIStreamListener; @@ -12,6 +17,9 @@ interface nsIURI; interface nsIDocument; interface nsIFrame; +[ref] native MaybeOnNonvisible(const mozilla::Maybe); +native Visibility(mozilla::Visibility); + /** * This interface represents a content node that loads images. The interface * exists to allow getting information on the images that the content node @@ -32,7 +40,7 @@ interface nsIFrame; * interface to mirror this interface when changing it. */ -[scriptable, builtinclass, uuid(770f7d84-c917-42d7-bf8d-d1b70649e733)] +[scriptable, builtinclass, uuid(0357123d-9224-4d12-a47e-868c32689777)] interface nsIImageLoadingContent : imgINotificationObserver { /** @@ -169,18 +177,17 @@ interface nsIImageLoadingContent : imgINotificationObserver readonly attribute unsigned long naturalHeight; /** - * A visible count is stored, if it is non-zero then this image is considered - * visible. These methods increment, decrement, or return the visible count. + * Called by layout to announce when the frame associated with this content + * has changed its visibility state. * - * @param aNonvisibleAction What to do if the image's visibility count is now - * zero. If ON_NONVISIBLE_NO_ACTION, nothing will be - * done. If ON_NONVISIBLE_REQUEST_DISCARD, the image - * will be asked to discard its surfaces if possible. + * @param aNewVisibility The new visibility state. + * @param aNonvisibleAction A requested action if the frame has become + * nonvisible. If Nothing(), no action is + * requested. If DISCARD_IMAGES is specified, the + * frame is requested to ask any images it's + * associated with to discard their surfaces if + * possible. */ - [noscript, notxpcom] void IncrementVisibleCount(); - [noscript, notxpcom] void DecrementVisibleCount(in uint32_t aNonvisibleAction); - [noscript, notxpcom] uint32_t GetVisibleCount(); - - const long ON_NONVISIBLE_NO_ACTION = 0; - const long ON_NONVISIBLE_REQUEST_DISCARD = 1; + [noscript, notxpcom] void onVisibilityChange(in Visibility aNewVisibility, + in MaybeOnNonvisible aNonvisibleAction); }; diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp index 24d0d782d1..f7fec46ec6 100644 --- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -94,8 +94,7 @@ nsImageLoadingContent::nsImageLoadingContent() mStateChangerDepth(0), mCurrentRequestRegistered(false), mPendingRequestRegistered(false), - mFrameCreateCalled(false), - mVisibleCount(0) + mFrameCreateCalled(false) { if (!nsContentUtils::GetImgLoaderForChannel(nullptr, nullptr)) { mLoadingEnabled = false; @@ -110,8 +109,8 @@ nsImageLoadingContent::DestroyImageLoadingContent() { // Cancel our requests so they won't hold stale refs to us // NB: Don't ask to discard the images here. - ClearCurrentRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_NO_ACTION); - ClearPendingRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_NO_ACTION); + ClearCurrentRequest(NS_BINDING_ABORTED); + ClearPendingRequest(NS_BINDING_ABORTED); } nsImageLoadingContent::~nsImageLoadingContent() @@ -272,12 +271,7 @@ ImageIsAnimated(imgIRequest* aRequest) void nsImageLoadingContent::OnUnlockedDraw() { - if (mVisibleCount > 0) { - // We should already be marked as visible, there is nothing more we can do. - return; - } - - // It's OK for non-animated images to wait until the next image visibility + // It's OK for non-animated images to wait until the next frame visibility // update to become locked. (And that's preferable, since in the case of // scrolling it keeps memory usage minimal.) For animated images, though, we // want to mark them visible right away so we can call @@ -286,15 +280,27 @@ nsImageLoadingContent::OnUnlockedDraw() return; } - nsPresContext* presContext = GetFramePresContext(); - if (!presContext) + nsIFrame* frame = GetOurPrimaryFrame(); + if (!frame) { return; + } + + if (frame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE) { + // This frame is already marked visible; there's nothing to do. + return; + } + + nsPresContext* presContext = frame->PresContext(); + if (!presContext) { + return; + } nsIPresShell* presShell = presContext->PresShell(); - if (!presShell) + if (!presShell) { return; + } - presShell->EnsureImageInVisibleList(this); + presShell->EnsureFrameInApproximatelyVisibleList(frame); } nsresult @@ -478,11 +484,6 @@ nsImageLoadingContent::FrameCreated(nsIFrame* aFrame) mFrameCreateCalled = true; - if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { - // Assume all images in popups are visible. - IncrementVisibleCount(); - } - TrackImage(mCurrentRequest); TrackImage(mPendingRequest); @@ -526,13 +527,7 @@ nsImageLoadingContent::FrameDestroyed(nsIFrame* aFrame) nsIPresShell* presShell = presContext ? presContext->GetPresShell() : nullptr; if (presShell) { - presShell->RemoveImageFromVisibleList(this); - } - - if (aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP)) { - // We assume all images in popups are visible, so this decrement balances - // out the increment in FrameCreated above. - DecrementVisibleCount(ON_NONVISIBLE_NO_ACTION); + presShell->RemoveFrameFromApproximatelyVisibleList(aFrame); } } @@ -746,34 +741,6 @@ nsImageLoadingContent::UnblockOnload(imgIRequest* aRequest) return NS_OK; } -void -nsImageLoadingContent::IncrementVisibleCount() -{ - mVisibleCount++; - if (mVisibleCount == 1) { - TrackImage(mCurrentRequest); - TrackImage(mPendingRequest); - } -} - -void -nsImageLoadingContent::DecrementVisibleCount(uint32_t aNonvisibleAction) -{ - NS_ASSERTION(mVisibleCount > 0, "visible count should be positive here"); - mVisibleCount--; - - if (mVisibleCount == 0) { - UntrackImage(mCurrentRequest, aNonvisibleAction); - UntrackImage(mPendingRequest, aNonvisibleAction); - } -} - -uint32_t -nsImageLoadingContent::GetVisibleCount() -{ - return mVisibleCount; -} - /* * Non-interface methods */ @@ -1080,8 +1047,8 @@ void nsImageLoadingContent::CancelImageRequests(bool aNotify) { AutoStateChanger changer(this, aNotify); - ClearPendingRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD); - ClearCurrentRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD); + ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES)); + ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES)); } nsresult @@ -1093,8 +1060,8 @@ nsImageLoadingContent::UseAsPrimaryRequest(imgRequestProxy* aRequest, AutoStateChanger changer(this, aNotify); // Get rid if our existing images - ClearPendingRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD); - ClearCurrentRequest(NS_BINDING_ABORTED, ON_NONVISIBLE_REQUEST_DISCARD); + ClearPendingRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES)); + ClearCurrentRequest(NS_BINDING_ABORTED, Some(OnNonvisible::DISCARD_IMAGES)); // Clone the request we were given. RefPtr& req = PrepareNextRequest(aImageLoadType); @@ -1221,7 +1188,9 @@ nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision) // reason "image source changed". However, apparently there's some abuse // over in nsImageFrame where the displaying of the "broken" icon for the // next image depends on the cancel reason of the previous image. ugh. - ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED, ON_NONVISIBLE_REQUEST_DISCARD); + // XXX(seth): So shouldn't we fix nsImageFrame?! + ClearPendingRequest(NS_ERROR_IMAGE_BLOCKED, + Some(OnNonvisible::DISCARD_IMAGES)); // For the blocked case, we only want to cancel the existing current request // if size is not available. bz says the web depends on this behavior. @@ -1229,7 +1198,8 @@ nsImageLoadingContent::SetBlockedRequest(nsIURI* aURI, int16_t aContentDecision) mImageBlockingStatus = aContentDecision; uint32_t keepFlags = mCurrentRequestFlags & REQUEST_IS_IMAGESET; - ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED, ON_NONVISIBLE_REQUEST_DISCARD); + ClearCurrentRequest(NS_ERROR_IMAGE_BLOCKED, + Some(OnNonvisible::DISCARD_IMAGES)); // We still want to remember what URI we were and if it was an imageset, // despite not having an actual request. These are both cleared as part of @@ -1248,7 +1218,7 @@ nsImageLoadingContent::PrepareCurrentRequest(ImageLoadType aImageLoadType) // Get rid of anything that was there previously. ClearCurrentRequest(NS_ERROR_IMAGE_SRC_CHANGED, - ON_NONVISIBLE_REQUEST_DISCARD); + Some(OnNonvisible::DISCARD_IMAGES)); if (mNewRequestsWillNeedAnimationReset) { mCurrentRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET; @@ -1267,7 +1237,7 @@ nsImageLoadingContent::PreparePendingRequest(ImageLoadType aImageLoadType) { // Get rid of anything that was there previously. ClearPendingRequest(NS_ERROR_IMAGE_SRC_CHANGED, - ON_NONVISIBLE_REQUEST_DISCARD); + Some(OnNonvisible::DISCARD_IMAGES)); if (mNewRequestsWillNeedAnimationReset) { mPendingRequestFlags |= REQUEST_NEEDS_ANIMATION_RESET; @@ -1332,7 +1302,7 @@ nsImageLoadingContent::MakePendingRequestCurrent() void nsImageLoadingContent::ClearCurrentRequest(nsresult aReason, - uint32_t aNonvisibleAction) + const Maybe& aNonvisibleAction) { if (!mCurrentRequest) { // Even if we didn't have a current request, we might have been keeping @@ -1358,7 +1328,7 @@ nsImageLoadingContent::ClearCurrentRequest(nsresult aReason, void nsImageLoadingContent::ClearPendingRequest(nsresult aReason, - uint32_t aNonvisibleAction) + const Maybe& aNonvisibleAction) { if (!mPendingRequest) return; @@ -1444,6 +1414,27 @@ nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent) doc->UnblockOnload(false); } +void +nsImageLoadingContent::OnVisibilityChange(Visibility aNewVisibility, + const Maybe& aNonvisibleAction) +{ + switch (aNewVisibility) { + case Visibility::APPROXIMATELY_VISIBLE: + TrackImage(mCurrentRequest); + TrackImage(mPendingRequest); + break; + + case Visibility::APPROXIMATELY_NONVISIBLE: + UntrackImage(mCurrentRequest, aNonvisibleAction); + UntrackImage(mPendingRequest, aNonvisibleAction); + break; + + case Visibility::UNTRACKED: + MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility"); + break; + } +} + void nsImageLoadingContent::TrackImage(imgIRequest* aImage) { @@ -1454,32 +1445,34 @@ nsImageLoadingContent::TrackImage(imgIRequest* aImage) "Why haven't we heard of this request?"); nsIDocument* doc = GetOurCurrentDoc(); - if (doc && (mFrameCreateCalled || GetOurPrimaryFrame()) && - (mVisibleCount > 0)) { + if (!doc) { + return; + } - if (mVisibleCount == 1) { - // Since we're becoming visible, request a decode. - nsImageFrame* f = do_QueryFrame(GetOurPrimaryFrame()); - if (f) { - f->MaybeDecodeForPredictedSize(); - } - } + // We only want to track this request if we're visible. Ordinarily we check + // the visible count, but that requires a frame; in cases where + // GetOurPrimaryFrame() cannot obtain a frame (e.g. ), we assume + // we're visible if FrameCreated() was called. + nsIFrame* frame = GetOurPrimaryFrame(); + if ((frame && frame->GetVisibility() == Visibility::APPROXIMATELY_NONVISIBLE) || + (!frame && !mFrameCreateCalled)) { + return; + } - if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) { - mCurrentRequestFlags |= REQUEST_IS_TRACKED; - doc->AddImage(mCurrentRequest); - } - if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) { - mPendingRequestFlags |= REQUEST_IS_TRACKED; - doc->AddImage(mPendingRequest); - } + if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) { + mCurrentRequestFlags |= REQUEST_IS_TRACKED; + doc->AddImage(mCurrentRequest); + } + if (aImage == mPendingRequest && !(mPendingRequestFlags & REQUEST_IS_TRACKED)) { + mPendingRequestFlags |= REQUEST_IS_TRACKED; + doc->AddImage(mPendingRequest); } } void nsImageLoadingContent::UntrackImage(imgIRequest* aImage, - uint32_t aNonvisibleAction - /* = ON_NONVISIBLE_NO_ACTION */) + const Maybe& aNonvisibleAction + /* = Nothing() */) { if (!aImage) return; @@ -1496,10 +1489,10 @@ nsImageLoadingContent::UntrackImage(imgIRequest* aImage, if (doc && (mCurrentRequestFlags & REQUEST_IS_TRACKED)) { mCurrentRequestFlags &= ~REQUEST_IS_TRACKED; doc->RemoveImage(mCurrentRequest, - (aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD) + aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES) ? nsIDocument::REQUEST_DISCARD : 0); - } else if (aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD) { + } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) { // If we're not in the document we may still need to be discarded. aImage->RequestDiscard(); } @@ -1508,10 +1501,10 @@ nsImageLoadingContent::UntrackImage(imgIRequest* aImage, if (doc && (mPendingRequestFlags & REQUEST_IS_TRACKED)) { mPendingRequestFlags &= ~REQUEST_IS_TRACKED; doc->RemoveImage(mPendingRequest, - (aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD) + aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES) ? nsIDocument::REQUEST_DISCARD : 0); - } else if (aNonvisibleAction == ON_NONVISIBLE_REQUEST_DISCARD) { + } else if (aNonvisibleAction == Some(OnNonvisible::DISCARD_IMAGES)) { // If we're not in the document we may still need to be discarded. aImage->RequestDiscard(); } diff --git a/dom/base/nsImageLoadingContent.h b/dom/base/nsImageLoadingContent.h index d684d2ecc0..3ddcfc2bef 100644 --- a/dom/base/nsImageLoadingContent.h +++ b/dom/base/nsImageLoadingContent.h @@ -41,6 +41,11 @@ class imgRequestProxy; class nsImageLoadingContent : public nsIImageLoadingContent, public imgIOnloadBlocker { + template using Maybe = mozilla::Maybe; + using Nothing = mozilla::Nothing; + using OnNonvisible = mozilla::OnNonvisible; + using Visibility = mozilla::Visibility; + /* METHODS */ public: nsImageLoadingContent(); @@ -326,8 +331,10 @@ protected: * @param aNonvisibleAction An action to take if the image is no longer * visible as a result; see |UntrackImage|. */ - void ClearCurrentRequest(nsresult aReason, uint32_t aNonvisibleAction); - void ClearPendingRequest(nsresult aReason, uint32_t aNonvisibleAction); + void ClearCurrentRequest(nsresult aReason, + const Maybe& aNonvisibleAction = Nothing()); + void ClearPendingRequest(nsresult aReason, + const Maybe& aNonvisibleAction = Nothing()); /** * Retrieve a pointer to the 'registered with the refresh driver' flag for @@ -356,14 +363,16 @@ protected: * * No-op if aImage is null. * - * @param aNonvisibleAction What to do if the image's visibility count is now - * zero. If ON_NONVISIBLE_NO_ACTION, nothing will be - * done. If ON_NONVISIBLE_REQUEST_DISCARD, the image - * will be asked to discard its surfaces if possible. + * @param aNonvisibleAction A requested action if the frame has become + * nonvisible. If Nothing(), no action is + * requested. If DISCARD_IMAGES is specified, the + * frame is requested to ask any images it's + * associated with to discard their surfaces if + * possible. */ void TrackImage(imgIRequest* aImage); void UntrackImage(imgIRequest* aImage, - uint32_t aNonvisibleAction = ON_NONVISIBLE_NO_ACTION); + const Maybe& aNonvisibleAction = Nothing()); /* MEMBERS */ RefPtr mCurrentRequest; @@ -447,8 +456,6 @@ private: // True when FrameCreate has been called but FrameDestroy has not. bool mFrameCreateCalled; - - uint32_t mVisibleCount; }; #endif // nsImageLoadingContent_h__ diff --git a/dom/base/nsPerformance.cpp b/dom/base/nsPerformance.cpp index 36d9d82dc9..a564758c93 100644 --- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -1141,7 +1141,7 @@ PerformanceBase::CancelNotificationObservers() mPendingNotificationObserversTask = false; } -class NotifyObserversTask final : public nsCancelableRunnable +class NotifyObserversTask final : public CancelableRunnable { public: explicit NotifyObserversTask(PerformanceBase* aPerformance) diff --git a/dom/base/test/test_postMessages.html b/dom/base/test/test_postMessages.html index 4157ff0665..93e9511bf1 100644 --- a/dom/base/test/test_postMessages.html +++ b/dom/base/test/test_postMessages.html @@ -61,15 +61,15 @@ function compare(a, b) { } var clonableObjects = [ - { crossThreads: true, data: 'hello world' }, - { crossThreads: true, data: 123 }, - { crossThreads: true, data: null }, - { crossThreads: true, data: true }, - { crossThreads: true, data: new Date() }, - { crossThreads: true, data: [ 1, 'test', true, new Date() ] }, - { crossThreads: true, data: { a: true, b: null, c: new Date(), d: [ true, false, {} ] } }, - { crossThreads: true, data: new Blob([123], { type: 'plain/text' }) }, - { crossThreads: true, data: new ImageData(2, 2) }, + 'hello world', + 123, + null, + true, + new Date(), + [ 1, 'test', true, new Date() ], + { a: true, b: null, c: new Date(), d: [ true, false, {} ] }, + new Blob([123], { type: 'plain/text' }), + new ImageData(2, 2), ]; function create_fileList_forFile() { @@ -84,7 +84,7 @@ function create_fileList_forFile() { var domFile = fileList.files[0]; is(domFile.name, "prefs.js", "fileName should be prefs.js"); - clonableObjects.push({ crossThreads: true, data: fileList.files }); + clonableObjects.push(fileList.files); script.destroy(); next(); } @@ -105,7 +105,7 @@ function create_fileList_forDir() { is(fileList.files.length, 1, "Filelist has 1 element"); ok(fileList.files[0] instanceof Directory, "We have a directory."); - clonableObjects.push({ crossThreads: false, data: fileList.files }); + clonableObjects.push(fileList.files); script.destroy(); next(); } @@ -114,6 +114,34 @@ function create_fileList_forDir() { script.sendAsyncMessage("dir.open"); } +function create_directory() { + if (navigator.userAgent.toLowerCase().indexOf('Android') != -1) { + next(); + return; + } + + var url = SimpleTest.getTestFileURL("script_postmessages_fileList.js"); + var script = SpecialPowers.loadChromeScript(url); + + function onOpened(message) { + var fileList = document.getElementById('fileList'); + SpecialPowers.wrap(fileList).mozSetDirectory(message.dir); + + fileList.getFilesAndDirectories().then(function(list) { + // Just a simple test + is(list.length, 1, "This list has 1 element"); + ok(list[0] instanceof Directory, "We have a directory."); + + clonableObjects.push(list[0]); + script.destroy(); + next(); + }); + } + + script.addMessageListener("dir.opened", onOpened); + script.sendAsyncMessage("dir.open"); +} + function runTests(obj) { ok(('clonableObjects' in obj) && ('transferableObjects' in obj) && @@ -135,15 +163,8 @@ function runTests(obj) { var object = clonableObjects[clonableObjectsId++]; - // If this test requires a cross-thread structured clone algorithm, maybe - // we have to skip it. - if (!object.crossThread && obj.crossThread) { - runClonableTest(); - return; - } - - obj.send(object.data, []).then(function(received) { - compare(received.data, object.data); + obj.send(object, []).then(function(received) { + compare(received.data, object); runClonableTest(); }); } @@ -232,7 +253,6 @@ function test_windowToWindow() { runTests({ clonableObjects: true, transferableObjects: true, - crossThread: false, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -286,7 +306,6 @@ function test_windowToIframeURL(url) { runTests({ clonableObjects: true, transferableObjects: true, - crossThread: false, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -334,7 +353,6 @@ function test_workers() { runTests({ clonableObjects: true, transferableObjects: true, - crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -378,7 +396,6 @@ function test_broadcastChannel() { runTests({ clonableObjects: true, transferableObjects: false, - crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { if (ports.length) { @@ -424,7 +441,6 @@ function test_broadcastChannel_inWorkers() { runTests({ clonableObjects: true, transferableObjects: false, - crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { if (ports.length) { @@ -466,7 +482,6 @@ function test_messagePort() { runTests({ clonableObjects: true, transferableObjects: true, - crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -512,7 +527,6 @@ function test_messagePort_inWorkers() { runTests({ clonableObjects: true, transferableObjects: true, - crossThread: true, send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; @@ -536,6 +550,7 @@ function test_messagePort_inWorkers() { var tests = [ create_fileList_forFile, create_fileList_forDir, + create_directory, test_windowToWindow, test_windowToIframe, diff --git a/dom/base/test/unit/test_chromeutils_base64.js b/dom/base/test/unit/test_chromeutils_base64.js new file mode 100644 index 0000000000..cc8f9907ad --- /dev/null +++ b/dom/base/test/unit/test_chromeutils_base64.js @@ -0,0 +1,103 @@ +"use strict"; + +function run_test() { + test_base64URLEncode(); + test_base64URLDecode(); +} + +// Test vectors from RFC 4648, section 10. +let textTests = { + "": "", + "f": "Zg", + "fo": "Zm8", + "foo": "Zm9v", + "foob": "Zm9vYg", + "fooba": "Zm9vYmE", + "foobar": "Zm9vYmFy", +} + +// Examples from RFC 4648, section 9. +let binaryTests = [{ + decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]), + encoded: "FPucA9l-", +}, { + decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9]), + encoded: "FPucA9k", +}, { + decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03]), + encoded: "FPucAw", +}]; + +function padEncodedValue(value) { + switch (value.length % 4) { + case 0: + return value; + case 2: + return value + "=="; + case 3: + return value + "="; + default: + throw new TypeError("Invalid encoded value"); + } +} + +function testEncode(input, encoded) { + equal(ChromeUtils.base64URLEncode(input, { pad: false }), + encoded, encoded + " without padding"); + equal(ChromeUtils.base64URLEncode(input, { pad: true }), + padEncodedValue(encoded), encoded + " with padding"); +} + +function test_base64URLEncode() { + throws(_ => ChromeUtils.base64URLEncode(new Uint8Array(0)), /TypeError/, + "Should require encoding options"); + throws(_ => ChromeUtils.base64URLEncode(new Uint8Array(0), {}), /TypeError/, + "Encoding should require the padding option"); + + for (let {decoded, encoded} of binaryTests) { + testEncode(decoded, encoded); + } + + let textEncoder = new TextEncoder("utf-8"); + for (let decoded of Object.keys(textTests)) { + let input = textEncoder.encode(decoded); + testEncode(input, textTests[decoded]); + } +} + +function testDecode(input, decoded) { + let buffer = ChromeUtils.base64URLDecode(input, { padding: "reject" }); + deepEqual(new Uint8Array(buffer), decoded, input + " with padding rejected"); + + let paddedValue = padEncodedValue(input); + buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "ignore" }); + deepEqual(new Uint8Array(buffer), decoded, input + " with padding ignored"); + + if (paddedValue.length > input.length) { + throws(_ => ChromeUtils.base64URLDecode(paddedValue, { padding: "reject" }), + paddedValue + " with padding rejected should throw"); + + throws(_ => ChromeUtils.base64URLDecode(input, { padding: "require" }), + input + " with padding required should throw"); + + buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "require" }); + deepEqual(new Uint8Array(buffer), decoded, paddedValue + " with padding required"); + } +} + +function test_base64URLDecode() { + throws(_ => ChromeUtils.base64URLDecode(""), /TypeError/, + "Should require decoding options"); + throws(_ => ChromeUtils.base64URLEncode("", {}), /TypeError/, + "Decoding should require the padding option"); + + for (let {decoded, encoded} of binaryTests) { + testDecode(encoded, decoded); + } + + let textEncoder = new TextEncoder("utf-8"); + for (let decoded of Object.keys(textTests)) { + let expectedBuffer = textEncoder.encode(decoded); + testDecode(textTests[decoded], expectedBuffer); + } +} diff --git a/dom/base/test/unit/xpcshell.ini b/dom/base/test/unit/xpcshell.ini index 4edb51135d..782384a8d1 100644 --- a/dom/base/test/unit/xpcshell.ini +++ b/dom/base/test/unit/xpcshell.ini @@ -32,3 +32,4 @@ skip-if = os == 'mac' [test_xhr_standalone.js] [test_xmlserializer.js] [test_cancelPrefetch.js] +[test_chromeutils_base64.js] diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index c3f57fba59..f6a3a966c3 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -952,25 +952,6 @@ DOMInterfaces = { 'nativeType': 'mozilla::dom::workers::PushMessageData', }, -'PushManager': [{ - 'workers': False, - 'headerFile': 'mozilla/dom/PushManager.h', - 'nativeType': 'mozilla::dom::PushManager', -}, { - 'workers': True, - 'headerFile': 'mozilla/dom/PushManager.h', - 'nativeType': 'mozilla::dom::WorkerPushManager', -}], - -'PushSubscription': [{ - 'workers': False, - 'headerFile': 'mozilla/dom/PushManager.h', -}, { - 'workers': True, - 'headerFile': 'mozilla/dom/PushManager.h', - 'nativeType': 'mozilla::dom::WorkerPushSubscription', -}], - 'Range': { 'nativeType': 'nsRange', 'binaryNames': { @@ -1014,6 +995,7 @@ DOMInterfaces = { 'ServiceWorkerRegistration': [{ 'nativeType': 'mozilla::dom::ServiceWorkerRegistrationMainThread', 'headerFile': 'mozilla/dom/ServiceWorkerRegistration.h', + 'implicitJSContext': [ 'pushManager' ], }, { 'workers': True, 'nativeType': 'mozilla::dom::ServiceWorkerRegistrationWorkerThread', diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg index ab53058065..d82e57b8b7 100644 --- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -62,6 +62,7 @@ MSG_DEF(MSG_MISSING_REQUIRED_DICTIONARY_MEMBER, 1, JSEXN_TYPEERR, "Missing requi MSG_DEF(MSG_INVALID_REQUEST_METHOD, 1, JSEXN_TYPEERR, "Invalid request method {0}.") MSG_DEF(MSG_INVALID_REQUEST_MODE, 1, JSEXN_TYPEERR, "Invalid request mode {0}.") MSG_DEF(MSG_INVALID_REFERRER_URL, 1, JSEXN_TYPEERR, "Invalid referrer URL {0}.") +MSG_DEF(MSG_CROSS_ORIGIN_REFERRER_URL, 2, JSEXN_TYPEERR, "Referrer URL {0} cannot be cross-origin to the entry settings object ({1}).") MSG_DEF(MSG_FETCH_BODY_CONSUMED_ERROR, 0, JSEXN_TYPEERR, "Body has already been consumed.") MSG_DEF(MSG_RESPONSE_INVALID_STATUSTEXT_ERROR, 0, JSEXN_TYPEERR, "Response statusText may not contain newline or carriage return.") MSG_DEF(MSG_FETCH_FAILED, 0, JSEXN_TYPEERR, "NetworkError when attempting to fetch resource.") diff --git a/dom/cache/CachePushStreamChild.cpp b/dom/cache/CachePushStreamChild.cpp index 0a4c7048e1..32fa438bc7 100644 --- a/dom/cache/CachePushStreamChild.cpp +++ b/dom/cache/CachePushStreamChild.cpp @@ -17,7 +17,7 @@ namespace dom { namespace cache { class CachePushStreamChild::Callback final : public nsIInputStreamCallback - , public nsCancelableRunnable + , public CancelableRunnable { public: explicit Callback(CachePushStreamChild* aActor) @@ -89,7 +89,7 @@ private: }; NS_IMPL_ISUPPORTS_INHERITED(CachePushStreamChild::Callback, - nsCancelableRunnable, + CancelableRunnable, nsIInputStreamCallback); CachePushStreamChild::CachePushStreamChild(Feature* aFeature, diff --git a/dom/cache/CacheStorage.cpp b/dom/cache/CacheStorage.cpp index d0a6a2a63e..96e10a0de6 100644 --- a/dom/cache/CacheStorage.cpp +++ b/dom/cache/CacheStorage.cpp @@ -119,6 +119,7 @@ IsTrusted(const PrincipalInfo& aPrincipalInfo, bool aTestingPrefEnabled) nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen)); if (scheme.LowerCaseEqualsLiteral("https") || + scheme.LowerCaseEqualsLiteral("app") || scheme.LowerCaseEqualsLiteral("file")) { return true; } diff --git a/dom/cache/DBSchema.cpp b/dom/cache/DBSchema.cpp index 92ec657b29..8e575c7030 100644 --- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -2158,7 +2158,7 @@ BindId(mozIStorageStatement* aState, const nsACString& aName, const nsID* aId) char idBuf[NSID_LENGTH]; aId->ToProvidedString(idBuf); - rv = aState->BindUTF8StringByName(aName, nsAutoCString(idBuf)); + rv = aState->BindUTF8StringByName(aName, nsDependentCString(idBuf)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; diff --git a/dom/cache/ReadStream.cpp b/dom/cache/ReadStream.cpp index e1fe3681db..aa4179dd74 100644 --- a/dom/cache/ReadStream.cpp +++ b/dom/cache/ReadStream.cpp @@ -117,7 +117,7 @@ private: // be done on the thread associated with the PBackground actor. Must be // cancelable to execute on Worker threads (which can occur when the // ReadStream is constructed on a child process Worker thread). -class ReadStream::Inner::NoteClosedRunnable final : public nsCancelableRunnable +class ReadStream::Inner::NoteClosedRunnable final : public CancelableRunnable { public: explicit NoteClosedRunnable(ReadStream::Inner* aStream) @@ -152,7 +152,7 @@ private: // it on the thread associated with the PBackground actor. Must be // cancelable to execute on Worker threads (which can occur when the // ReadStream is constructed on a child process Worker thread). -class ReadStream::Inner::ForgetRunnable final : public nsCancelableRunnable +class ReadStream::Inner::ForgetRunnable final : public CancelableRunnable { public: explicit ForgetRunnable(ReadStream::Inner* aStream) diff --git a/dom/cache/TypeUtils.cpp b/dom/cache/TypeUtils.cpp index e8e4ca1aac..25c36c3cf0 100644 --- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -419,7 +419,8 @@ TypeUtils::ProcessURL(nsACString& aUrl, bool* aSchemeValidOut, if (aSchemeValidOut) { nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen)); *aSchemeValidOut = scheme.LowerCaseEqualsLiteral("http") || - scheme.LowerCaseEqualsLiteral("https"); + scheme.LowerCaseEqualsLiteral("https") || + scheme.LowerCaseEqualsLiteral("app"); } uint32_t queryPos; diff --git a/dom/cache/test/mochitest/driver.js b/dom/cache/test/mochitest/driver.js index 9549fd632e..2f556304a6 100644 --- a/dom/cache/test/mochitest/driver.js +++ b/dom/cache/test/mochitest/driver.js @@ -89,7 +89,8 @@ function runTests(testFile, order) { SimpleTest.waitForExplicitFinish(); if (typeof order == "undefined") { - order = "both"; // both by default + order = "sequential"; // sequential by default, see bug 1143222. + // TODO: Make this "both" again. } ok(order == "parallel" || order == "sequential" || order == "both", diff --git a/dom/cache/test/xpcshell/head.js b/dom/cache/test/xpcshell/head.js index fb09774499..3d51929b35 100644 --- a/dom/cache/test/xpcshell/head.js +++ b/dom/cache/test/xpcshell/head.js @@ -18,9 +18,6 @@ var sts = Cc['@mozilla.org/network/stream-transport-service;1'] var hash = Cc['@mozilla.org/security/hash;1'] .createInstance(Ci.nsICryptoHash); -var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); -prefs.setBoolPref("dom.requestcache.enabled", true); - // Expose Cache and Fetch symbols on the global Cu.importGlobalProperties(['caches', 'fetch']); diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp index 04a79a4afd..85a0075209 100644 --- a/dom/canvas/WebGLContext.cpp +++ b/dom/canvas/WebGLContext.cpp @@ -431,7 +431,9 @@ static bool IsFeatureInBlacklist(const nsCOMPtr& gfxInfo, int32_t feature) { int32_t status; - if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, feature, &status))) + nsCString discardFailureId; + if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, feature, + discardFailureId, &status))) return false; return status != nsIGfxInfo::FEATURE_STATUS_OK; @@ -442,28 +444,34 @@ HasAcceleratedLayers(const nsCOMPtr& gfxInfo) { int32_t status; + nsCString discardFailureId; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, + discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS, + discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, + discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, + discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_OPENGL_LAYERS, + discardFailureId, &status); if (status) return true; @@ -1527,7 +1535,7 @@ WebGLContext::RunContextLossTimer() mContextLossHandler->RunTimer(); } -class UpdateContextLossStatusTask : public nsCancelableRunnable +class UpdateContextLossStatusTask : public CancelableRunnable { RefPtr mWebGL; diff --git a/dom/canvas/WebGLContextLossHandler.cpp b/dom/canvas/WebGLContextLossHandler.cpp index 05f58b24d6..6c7530d5be 100644 --- a/dom/canvas/WebGLContextLossHandler.cpp +++ b/dom/canvas/WebGLContextLossHandler.cpp @@ -38,7 +38,7 @@ private: nsCOMPtr mEventTarget; }; -class ContextLossWorkerRunnable final : public nsCancelableRunnable +class ContextLossWorkerRunnable final : public CancelableRunnable { public: explicit ContextLossWorkerRunnable(nsIRunnable* aRunnable) diff --git a/dom/crypto/WebCryptoTask.h b/dom/crypto/WebCryptoTask.h index adb98ae985..a59f56ca9a 100644 --- a/dom/crypto/WebCryptoTask.h +++ b/dom/crypto/WebCryptoTask.h @@ -57,7 +57,7 @@ if (NS_FAILED(rv)) { \ return; \ } -class WebCryptoTask : public nsCancelableRunnable, +class WebCryptoTask : public CancelableRunnable, public nsNSSShutDownObject { public: diff --git a/dom/events/AsyncEventDispatcher.cpp b/dom/events/AsyncEventDispatcher.cpp index 5eaaf260dd..43cd09443c 100644 --- a/dom/events/AsyncEventDispatcher.cpp +++ b/dom/events/AsyncEventDispatcher.cpp @@ -30,7 +30,7 @@ AsyncEventDispatcher::AsyncEventDispatcher(EventTarget* aTarget, mEvent = do_QueryInterface(event); NS_ASSERTION(mEvent, "Should never fail to create an event"); mEvent->DuplicatePrivateData(); - mEvent->SetTrusted(aEvent.mFlags.mIsTrusted); + mEvent->SetTrusted(aEvent.IsTrusted()); } NS_IMETHODIMP diff --git a/dom/events/AsyncEventDispatcher.h b/dom/events/AsyncEventDispatcher.h index ff5062be98..094e764b6c 100644 --- a/dom/events/AsyncEventDispatcher.h +++ b/dom/events/AsyncEventDispatcher.h @@ -24,8 +24,8 @@ namespace mozilla { * want to ensure that the event handler doesn't mutate the DOM at * the wrong time, in order to avoid resulting instability. */ - -class AsyncEventDispatcher : public nsCancelableRunnable + +class AsyncEventDispatcher : public CancelableRunnable { public: /** @@ -95,7 +95,7 @@ public: mBlockedDoc->BlockOnload(); } } - + ~LoadBlockingAsyncEventDispatcher(); private: diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index b5a586d496..2cb3760b1c 100644 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -466,22 +466,21 @@ Event::GetTimeStamp(uint64_t* aTimeStamp) NS_IMETHODIMP Event::StopPropagation() { - mEvent->mFlags.mPropagationStopped = true; + mEvent->StopPropagation(); return NS_OK; } NS_IMETHODIMP Event::StopImmediatePropagation() { - mEvent->mFlags.mPropagationStopped = true; - mEvent->mFlags.mImmediatePropagationStopped = true; + mEvent->StopImmediatePropagation(); return NS_OK; } NS_IMETHODIMP Event::StopCrossProcessForwarding() { - mEvent->mFlags.mNoCrossProcessBoundaryForwarding = true; + mEvent->StopCrossProcessForwarding(); return NS_OK; } @@ -520,17 +519,7 @@ Event::PreventDefaultInternal(bool aCalledByDefaultHandler) return; } - mEvent->mFlags.mDefaultPrevented = true; - - // Note that even if preventDefault() has already been called by chrome, - // a call of preventDefault() by content needs to overwrite - // mDefaultPreventedByContent to true because in such case, defaultPrevented - // must be true when web apps check it after they call preventDefault(). - if (!aCalledByDefaultHandler) { - mEvent->mFlags.mDefaultPreventedByContent = true; - } else { - mEvent->mFlags.mDefaultPreventedByChrome = true; - } + mEvent->PreventDefault(aCalledByDefaultHandler); if (!IsTrusted()) { return; @@ -763,7 +752,7 @@ Event::GetEventPopupControlState(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent) } break; case eKeyboardEventClass: - if (aEvent->mFlags.mIsTrusted) { + if (aEvent->IsTrusted()) { uint32_t key = aEvent->AsKeyboardEvent()->keyCode; switch(aEvent->mMessage) { case eKeyPress: @@ -793,7 +782,7 @@ Event::GetEventPopupControlState(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent) } break; case eTouchEventClass: - if (aEvent->mFlags.mIsTrusted) { + if (aEvent->IsTrusted()) { switch (aEvent->mMessage) { case eTouchStart: if (PopupAllowedForEvent("touchstart")) { @@ -811,7 +800,7 @@ Event::GetEventPopupControlState(WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent) } break; case eMouseEventClass: - if (aEvent->mFlags.mIsTrusted && + if (aEvent->IsTrusted() && aEvent->AsMouseEvent()->button == WidgetMouseEvent::eLeftButton) { switch(aEvent->mMessage) { case eMouseUp: @@ -1082,14 +1071,14 @@ Event::DefaultPrevented(JSContext* aCx) const NS_ENSURE_TRUE(mEvent, false); // If preventDefault() has never been called, just return false. - if (!mEvent->mFlags.mDefaultPrevented) { + if (!mEvent->DefaultPrevented()) { return false; } // If preventDefault() has been called by content, return true. Otherwise, // i.e., preventDefault() has been called by chrome, return true only when // this is called by chrome. - return mEvent->mFlags.mDefaultPreventedByContent || IsChrome(aCx); + return mEvent->DefaultPreventedByContent() || IsChrome(aCx); } double diff --git a/dom/events/Event.h b/dom/events/Event.h index 2c35536cfc..cf0fb1f274 100644 --- a/dom/events/Event.h +++ b/dom/events/Event.h @@ -186,7 +186,7 @@ public: bool DefaultPrevented() const { - return mEvent->mFlags.mDefaultPrevented; + return mEvent->DefaultPrevented(); } bool MultipleActionsPrevented() const @@ -196,7 +196,7 @@ public: bool IsTrusted() const { - return mEvent->mFlags.mIsTrusted; + return mEvent->IsTrusted(); } bool IsSynthesized() const diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp index 09e1bb8007..24d3d272f3 100644 --- a/dom/events/EventDispatcher.cpp +++ b/dom/events/EventDispatcher.cpp @@ -85,9 +85,46 @@ private: uint32_t mInitialCount; }; +static bool IsEventTargetChrome(EventTarget* aEventTarget, + nsIDocument** aDocument = nullptr) +{ + if (aDocument) { + *aDocument = nullptr; + } + + if (NS_WARN_IF(!aEventTarget)) { + return false; + } + + nsCOMPtr doc = do_QueryInterface(aEventTarget); + if (!doc) { + nsCOMPtr node = do_QueryInterface(aEventTarget); + if (node) { + doc = node->OwnerDoc(); + } else { + nsCOMPtr window = do_QueryInterface(aEventTarget); + if (!window) { + return false; + } + doc = window->GetExtantDoc(); + } + if (!doc) { + return false; + } + } + bool isChrome = nsContentUtils::IsChromeDoc(doc); + if (aDocument) { + doc.swap(*aDocument); + } + return isChrome; +} + + #define NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH (1 << 0) #define NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT (1 << 1) #define NS_TARGET_CHAIN_MAY_HAVE_MANAGER (1 << 2) +#define NS_TARGET_CHAIN_CHECKED_IF_CHROME (1 << 3) +#define NS_TARGET_CHAIN_IS_CHROME_CONTENT (1 << 4) // EventTargetChainItem represents a single item in the event target chain. class EventTargetChainItem @@ -210,6 +247,11 @@ public: if (aVisitor.mEvent->mFlags.mPropagationStopped) { return; } + if (aVisitor.mEvent->mFlags.mOnlySystemGroupDispatchInContent && + !aVisitor.mEvent->mFlags.mInSystemGroup && + !IsCurrentTargetChrome()) { + return; + } if (!mManager) { if (!MayHaveListenerManager() && !aCd.MayHaveNewListenerManager()) { return; @@ -233,6 +275,7 @@ public: */ void PostHandleEvent(EventChainPostVisitor& aVisitor); +private: nsCOMPtr mTarget; uint16_t mFlags; uint16_t mItemFlags; @@ -241,6 +284,17 @@ public: nsCOMPtr mNewTarget; // Cache mTarget's event listener manager. RefPtr mManager; + + bool IsCurrentTargetChrome() + { + if (!(mFlags & NS_TARGET_CHAIN_CHECKED_IF_CHROME)) { + mFlags |= NS_TARGET_CHAIN_CHECKED_IF_CHROME; + if (IsEventTargetChrome(mTarget)) { + mFlags |= NS_TARGET_CHAIN_IS_CHROME_CONTENT; + } + } + return !!(mFlags & NS_TARGET_CHAIN_IS_CHROME_CONTENT); + } }; EventTargetChainItem::EventTargetChainItem(EventTarget* aTarget) @@ -468,18 +522,9 @@ EventDispatcher::Dispatch(nsISupports* aTarget, } if (aEvent->mFlags.mOnlyChromeDispatch) { - nsCOMPtr node = do_QueryInterface(aTarget); - if (!node) { - nsCOMPtr win = do_QueryInterface(aTarget); - if (win) { - node = win->GetExtantDoc(); - } - } - - NS_ENSURE_STATE(node); - nsIDocument* doc = node->OwnerDoc(); - if (!nsContentUtils::IsChromeDoc(doc)) { - nsPIDOMWindow* win = doc ? doc->GetInnerWindow() : nullptr; + nsCOMPtr doc; + if (!IsEventTargetChrome(target, getter_AddRefs(doc)) && doc) { + nsPIDOMWindow* win = doc->GetInnerWindow(); // If we can't dispatch the event to chrome, do nothing. EventTarget* piTarget = win ? win->GetParentTarget() : nullptr; if (!piTarget) { @@ -490,6 +535,8 @@ EventDispatcher::Dispatch(nsISupports* aTarget, aEvent->target = target; // but use chrome event handler or TabChildGlobal for event target chain. target = piTarget; + } else if (NS_WARN_IF(!doc)) { + return NS_ERROR_UNEXPECTED; } } diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index 85ccc7da1a..b7cba95bbf 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -1153,8 +1153,11 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, nsEventStatus* aEventStatus) { //Set the value of the internal PreventDefault flag properly based on aEventStatus - if (*aEventStatus == nsEventStatus_eConsumeNoDefault) { - aEvent->mFlags.mDefaultPrevented = true; + if (!aEvent->DefaultPrevented() && + *aEventStatus == nsEventStatus_eConsumeNoDefault) { + // Assume that if only aEventStatus claims that the event has already been + // consumed, the consumer is default event handler. + aEvent->PreventDefault(); } nsAutoTObserverArray::EndLimitedIterator iter(mListeners); @@ -1174,8 +1177,7 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, if (ListenerCanHandle(listener, aEvent)) { hasListener = true; if (listener->IsListening(aEvent) && - (aEvent->mFlags.mIsTrusted || - listener->mFlags.mAllowUntrustedEvents)) { + (aEvent->IsTrusted() || listener->mFlags.mAllowUntrustedEvents)) { if (!*aDOMEvent) { // This is tiny bit slow, but happens only once per event. nsCOMPtr et = @@ -1237,7 +1239,7 @@ EventListenerManager::HandleEventInternal(nsPresContext* aPresContext, mNoListenerForEventAtom = aEvent->userType; } - if (aEvent->mFlags.mDefaultPrevented) { + if (aEvent->DefaultPrevented()) { *aEventStatus = nsEventStatus_eConsumeNoDefault; } } diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index cd4f1ddc27..bc44bfbb25 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -537,7 +537,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, // Do not take account eMouseEnterIntoWidget/ExitFromWidget so that loading // a page when user is not active doesn't change the state to active. WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); - if (aEvent->mFlags.mIsTrusted && + if (aEvent->IsTrusted() && ((mouseEvent && mouseEvent->IsReal() && IsMessageMouseUserActivity(mouseEvent->mMessage)) || aEvent->mClass == eWheelEventClass || @@ -585,7 +585,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, #endif // Store last known screenPoint and clientPoint so pointer lock // can use these values as constants. - if (aEvent->mFlags.mIsTrusted && + if (aEvent->IsTrusted() && ((mouseEvent && mouseEvent->IsReal()) || aEvent->mClass == eWheelEventClass) && !sIsPointerLocked) { @@ -651,7 +651,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, // from ESM::DispatchMouseOrPointerEvent (sending is permanent)). // Flag mNoCrossProcessBoundaryForwarding helps to // suppress sending accidental event from widget code. - aEvent->mFlags.mNoCrossProcessBoundaryForwarding = true; + aEvent->StopCrossProcessForwarding(); break; case eMouseExitFromWidget: // If this is a remote frame, we receive eMouseExitFromWidget from the @@ -668,7 +668,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, // Flag helps to suppress double event sending into process of content. // For more information see comment above, at eMouseEnterIntoWidget case. - aEvent->mFlags.mNoCrossProcessBoundaryForwarding = true; + aEvent->StopCrossProcessForwarding(); // If the event is not a top-level window exit, then it's not // really an exit --- we may have traversed widget boundaries but @@ -742,10 +742,10 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, (modifierMask == Prefs::ChromeAccessModifierMask() || modifierMask == Prefs::ContentAccessModifierMask())) { AutoTArray accessCharCodes; - nsContentUtils::GetAccessKeyCandidates(keyEvent, accessCharCodes); + keyEvent->GetAccessKeyCandidates(accessCharCodes); if (HandleAccessKey(aPresContext, accessCharCodes, - keyEvent->mFlags.mIsTrusted, modifierMask)) { + keyEvent->IsTrusted(), modifierMask)) { *aStatus = nsEventStatus_eConsumeNoDefault; } } @@ -780,7 +780,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, case eWheelOperationStart: case eWheelOperationEnd: { - NS_ASSERTION(aEvent->mFlags.mIsTrusted, + NS_ASSERTION(aEvent->IsTrusted(), "Untrusted wheel event shouldn't be here"); nsIContent* content = GetFocusedContent(); @@ -825,7 +825,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext, DoContentCommandScrollEvent(aEvent->AsContentCommandEvent()); break; case eCompositionStart: - if (aEvent->mFlags.mIsTrusted) { + if (aEvent->IsTrusted()) { // If the event is trusted event, set the selected text to data of // composition event. WidgetCompositionEvent* compositionEvent = aEvent->AsCompositionEvent(); @@ -1382,7 +1382,7 @@ EventStateManager::CreateClickHoldTimer(nsPresContext* inPresContext, nsIFrame* inDownFrame, WidgetGUIEvent* inMouseDownEvent) { - if (!inMouseDownEvent->mFlags.mIsTrusted || + if (!inMouseDownEvent->IsTrusted() || IsRemoteTarget(mGestureDownContent) || sIsPointerLocked) { return; @@ -1740,11 +1740,10 @@ EventStateManager::GenerateDragGesture(nsPresContext* aPresContext, nsCOMPtr widget = mCurrentTarget->GetNearestWidget(); // get the widget from the target frame - WidgetDragEvent startEvent(aEvent->mFlags.mIsTrusted, - eDragStart, widget); + WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget); FillInEventFromGestureDown(&startEvent); - WidgetDragEvent gestureEvent(aEvent->mFlags.mIsTrusted, + WidgetDragEvent gestureEvent(aEvent->IsTrusted(), eLegacyDragGesture, widget); FillInEventFromGestureDown(&gestureEvent); @@ -1800,7 +1799,7 @@ EventStateManager::GenerateDragGesture(nsPresContext* aPresContext, targetContent, selection); if (dragStarted) { sActiveESM = nullptr; - aEvent->mFlags.mPropagationStopped = true; + aEvent->StopPropagation(); } } @@ -2232,7 +2231,7 @@ EventStateManager::DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame, nsWeakFrame targetFrame(aTargetFrame); MOZ_ASSERT(*aStatus != nsEventStatus_eConsumeNoDefault && - !aEvent->mFlags.mDefaultPrevented, + !aEvent->DefaultPrevented(), "If you make legacy events dispatched for default prevented wheel " "event, you need to initialize stateX and stateY"); EventState stateX, stateY; @@ -2272,11 +2271,14 @@ EventStateManager::DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame, } } - if (stateY.mDefaultPrevented || stateX.mDefaultPrevented) { + if (stateY.mDefaultPrevented) { *aStatus = nsEventStatus_eConsumeNoDefault; - aEvent->mFlags.mDefaultPrevented = true; - aEvent->mFlags.mDefaultPreventedByContent |= - stateY.mDefaultPreventedByContent || stateX.mDefaultPreventedByContent; + aEvent->PreventDefault(!stateY.mDefaultPreventedByContent); + } + + if (stateX.mDefaultPrevented) { + *aStatus = nsEventStatus_eConsumeNoDefault; + aEvent->PreventDefault(!stateX.mDefaultPreventedByContent); } } @@ -2297,7 +2299,7 @@ EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame, targetContent = targetContent->GetParent(); } - WidgetMouseScrollEvent event(aEvent->mFlags.mIsTrusted, + WidgetMouseScrollEvent event(aEvent->IsTrusted(), eLegacyMouseLineOrPageScroll, aEvent->widget); event.mFlags.mDefaultPrevented = aState.mDefaultPrevented; event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent; @@ -2315,8 +2317,8 @@ EventStateManager::SendLineScrollEvent(nsIFrame* aTargetFrame, EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(), &event, nullptr, &status); aState.mDefaultPrevented = - event.mFlags.mDefaultPrevented || status == nsEventStatus_eConsumeNoDefault; - aState.mDefaultPreventedByContent = event.mFlags.mDefaultPreventedByContent; + event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault; + aState.mDefaultPreventedByContent = event.DefaultPreventedByContent(); } void @@ -2337,7 +2339,7 @@ EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame, targetContent = targetContent->GetParent(); } - WidgetMouseScrollEvent event(aEvent->mFlags.mIsTrusted, + WidgetMouseScrollEvent event(aEvent->IsTrusted(), eLegacyMousePixelScroll, aEvent->widget); event.mFlags.mDefaultPrevented = aState.mDefaultPrevented; event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent; @@ -2355,8 +2357,8 @@ EventStateManager::SendPixelScrollEvent(nsIFrame* aTargetFrame, EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(), &event, nullptr, &status); aState.mDefaultPrevented = - event.mFlags.mDefaultPrevented || status == nsEventStatus_eConsumeNoDefault; - aState.mDefaultPreventedByContent = event.mFlags.mDefaultPreventedByContent; + event.DefaultPrevented() || status == nsEventStatus_eConsumeNoDefault; + aState.mDefaultPreventedByContent = event.DefaultPreventedByContent(); } nsIFrame* @@ -3133,7 +3135,7 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, break; case eWheelOperationEnd: { - MOZ_ASSERT(aEvent->mFlags.mIsTrusted); + MOZ_ASSERT(aEvent->IsTrusted()); ScrollbarsForWheel::MayInactivate(); WidgetWheelEvent* wheelEvent = aEvent->AsWheelEvent(); nsIScrollableFrame* scrollTarget = @@ -3147,7 +3149,7 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, case eWheel: case eWheelOperationStart: { - MOZ_ASSERT(aEvent->mFlags.mIsTrusted); + MOZ_ASSERT(aEvent->IsTrusted()); if (*aStatus == nsEventStatus_eConsumeNoDefault) { ScrollbarsForWheel::Inactivate(); @@ -3180,7 +3182,7 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, // APZ to handle it, because it will track the velocity and predicted // destination from the momentum. if (wheelEvent->mFlags.mHandledByAPZ) { - wheelEvent->mFlags.mDefaultPrevented = true; + wheelEvent->PreventDefault(); } action = WheelPrefs::GetInstance()->ComputeActionFor(wheelEvent); } else if (wheelEvent->mFlags.mHandledByAPZ) { @@ -3426,8 +3428,7 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, getter_AddRefs(targetContent)); nsCOMPtr widget = mCurrentTarget->GetNearestWidget(); - WidgetDragEvent event(aEvent->mFlags.mIsTrusted, - eLegacyDragDrop, widget); + WidgetDragEvent event(aEvent->IsTrusted(), eLegacyDragDrop, widget); WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent(); event.refPoint = mouseEvent->refPoint; @@ -3853,7 +3854,7 @@ CreateMouseOrPointerWidgetEvent(WidgetMouseEvent* aMouseEvent, nsAutoPtr newPointerEvent; newPointerEvent = - new WidgetPointerEvent(aMouseEvent->mFlags.mIsTrusted, aMessage, + new WidgetPointerEvent(aMouseEvent->IsTrusted(), aMessage, aMouseEvent->widget); newPointerEvent->isPrimary = sourcePointer->isPrimary; newPointerEvent->pointerId = sourcePointer->pointerId; @@ -3867,7 +3868,7 @@ CreateMouseOrPointerWidgetEvent(WidgetMouseEvent* aMouseEvent, aNewEvent = newPointerEvent.forget(); } else { aNewEvent = - new WidgetMouseEvent(aMouseEvent->mFlags.mIsTrusted, aMessage, + new WidgetMouseEvent(aMouseEvent->IsTrusted(), aMessage, aMouseEvent->widget, WidgetMouseEvent::eReal); aNewEvent->relatedTarget = aRelatedContent; } @@ -4263,7 +4264,7 @@ EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent) } else if (aMouseEvent->refPoint == sSynthCenteringPoint) { // This is the "synthetic native" event we dispatched to re-center the // pointer. Cancel it so we don't expose the centering move to content. - aMouseEvent->mFlags.mPropagationStopped = true; + aMouseEvent->StopPropagation(); // Clear sSynthCenteringPoint so we don't cancel other events // targeted at the center. sSynthCenteringPoint = kInvalidRefPoint; @@ -4509,8 +4510,7 @@ EventStateManager::FireDragEnterOrExit(nsPresContext* aPresContext, nsWeakFrame& aTargetFrame) { nsEventStatus status = nsEventStatus_eIgnore; - WidgetDragEvent event(aDragEvent->mFlags.mIsTrusted, aMessage, - aDragEvent->widget); + WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->widget); event.refPoint = aDragEvent->refPoint; event.modifiers = aDragEvent->modifiers; event.buttons = aDragEvent->buttons; @@ -4654,7 +4654,7 @@ EventStateManager::InitAndDispatchClickEvent(WidgetMouseEvent* aEvent, nsWeakFrame aCurrentTarget, bool aNoContentDispatch) { - WidgetMouseEvent event(aEvent->mFlags.mIsTrusted, aMessage, + WidgetMouseEvent event(aEvent->IsTrusted(), aMessage, aEvent->widget, WidgetMouseEvent::eReal); event.refPoint = aEvent->refPoint; @@ -5918,7 +5918,7 @@ AutoHandlingUserInputStatePusher::AutoHandlingUserInputStatePusher( nsIPresShell::SetCapturingContent(nullptr, 0); nsIPresShell::AllowMouseCapture(true); } - if (!aDocument || !aEvent || !aEvent->mFlags.mIsTrusted) { + if (!aDocument || !aEvent || !aEvent->IsTrusted()) { return; } mResetFMMouseButtonHandlingState = diff --git a/dom/events/IMEContentObserver.cpp b/dom/events/IMEContentObserver.cpp index 5e055cacfb..90282ca8cc 100644 --- a/dom/events/IMEContentObserver.cpp +++ b/dom/events/IMEContentObserver.cpp @@ -755,8 +755,8 @@ IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext, if (!mUpdatePreference.WantMouseButtonEventOnChar()) { return false; } - if (!aMouseEvent->mFlags.mIsTrusted || - aMouseEvent->mFlags.mDefaultPrevented || + if (!aMouseEvent->IsTrusted() || + aMouseEvent->DefaultPrevented() || !aMouseEvent->widget) { return false; } @@ -822,7 +822,9 @@ IMEContentObserver::OnMouseButtonEvent(nsPresContext* aPresContext, } bool consumed = (rv == NS_SUCCESS_EVENT_CONSUMED); - aMouseEvent->mFlags.mDefaultPrevented = consumed; + if (consumed) { + aMouseEvent->PreventDefault(); + } return consumed; } diff --git a/dom/events/IMEStateManager.cpp b/dom/events/IMEStateManager.cpp index 6b907f1300..54f2a9f215 100644 --- a/dom/events/IMEStateManager.cpp +++ b/dom/events/IMEStateManager.cpp @@ -1164,7 +1164,7 @@ IMEStateManager::DispatchCompositionEvent( GetBoolName(aCompositionEvent->mFlags.mPropagationStopped), GetBoolName(aIsSynthesized), tabParent.get())); - if (!aCompositionEvent->mFlags.mIsTrusted || + if (!aCompositionEvent->IsTrusted() || aCompositionEvent->mFlags.mPropagationStopped) { return; } @@ -1261,7 +1261,7 @@ IMEStateManager::HandleSelectionEvent(nsPresContext* aPresContext, GetBoolName(aSelectionEvent->mFlags.mIsTrusted), tabParent.get())); - if (!aSelectionEvent->mFlags.mIsTrusted) { + if (!aSelectionEvent->IsTrusted()) { return; } @@ -1303,7 +1303,7 @@ IMEStateManager::OnCompositionEventDiscarded( GetBoolName(aCompositionEvent->widget->Destroyed()), GetBoolName(aCompositionEvent->mFlags.mIsTrusted))); - if (!aCompositionEvent->mFlags.mIsTrusted) { + if (!aCompositionEvent->IsTrusted()) { return; } diff --git a/dom/events/TextComposition.cpp b/dom/events/TextComposition.cpp index f2cefeb44c..036ce5d3c7 100644 --- a/dom/events/TextComposition.cpp +++ b/dom/events/TextComposition.cpp @@ -118,7 +118,7 @@ TextComposition::CloneAndDispatchAs( MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->widget), "Should be called only when it's safe to dispatch an event"); - WidgetCompositionEvent compositionEvent(aCompositionEvent->mFlags.mIsTrusted, + WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(), aMessage, aCompositionEvent->widget); compositionEvent.time = aCompositionEvent->time; compositionEvent.timeStamp = aCompositionEvent->timeStamp; @@ -159,7 +159,7 @@ TextComposition::OnCompositionEventDiscarded( // Note that this method is never called for synthesized events for emulating // commit or cancel composition. - MOZ_ASSERT(aCompositionEvent->mFlags.mIsTrusted, + MOZ_ASSERT(aCompositionEvent->IsTrusted(), "Shouldn't be called with untrusted event"); if (mTabParent) { @@ -243,7 +243,7 @@ TextComposition::DispatchCompositionEvent( // remote process. if (mTabParent) { Unused << mTabParent->SendCompositionEvent(*aCompositionEvent); - aCompositionEvent->mFlags.mPropagationStopped = true; + aCompositionEvent->StopPropagation(); if (aCompositionEvent->CausesDOMTextEvent()) { mLastData = aCompositionEvent->mData; mLastRanges = aCompositionEvent->mRanges; @@ -411,7 +411,7 @@ TextComposition::HandleSelectionEvent(nsPresContext* aPresContext, // remote process. if (aTabParent) { Unused << aTabParent->SendSelectionEvent(*aSelectionEvent); - aSelectionEvent->mFlags.mPropagationStopped = true; + aSelectionEvent->StopPropagation(); return; } diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 7164336526..6ff2b99526 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -64,25 +64,6 @@ Request::RequestContextEnabled(JSContext* aCx, JSObject* aObj) return workerPrivate->RequestContextEnabled(); } -// static -bool -Request::RequestCacheEnabled(JSContext* aCx, JSObject* aObj) -{ - if (NS_IsMainThread()) { - return Preferences::GetBool("dom.requestcache.enabled", false); - } - - using namespace workers; - - // Otherwise, check the pref via the WorkerPrivate - WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); - if (!workerPrivate) { - return false; - } - - return workerPrivate->RequestCacheEnabled(); -} - already_AddRefed Request::GetInternalRequest() { @@ -384,7 +365,10 @@ Request::Constructor(const GlobalObject& aGlobal, nsresult rv = principal->CheckMayLoad(uri, /* report */ false, /* allowIfInheritsPrincipal */ false); if (NS_FAILED(rv)) { - aRv.ThrowTypeError(referrer); + nsAutoCString globalOrigin; + principal->GetOrigin(globalOrigin); + aRv.ThrowTypeError(referrer, + NS_ConvertUTF8toUTF16(globalOrigin)); return nullptr; } } @@ -412,7 +396,8 @@ Request::Constructor(const GlobalObject& aGlobal, new ReferrerSameOriginChecker(worker, referrerURL, rv); checker->Dispatch(aRv); if (aRv.Failed() || NS_FAILED(rv)) { - aRv.ThrowTypeError(referrer); + aRv.ThrowTypeError(referrer, + worker->GetLocationInfo().mOrigin); return nullptr; } } diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h index 191feb5b54..7e604f4659 100644 --- a/dom/fetch/Request.h +++ b/dom/fetch/Request.h @@ -36,8 +36,6 @@ public: static bool RequestContextEnabled(JSContext* aCx, JSObject* aObj); - static bool - RequestCacheEnabled(JSContext* aCx, JSObject* aObj); JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override diff --git a/dom/filesystem/CreateDirectoryTask.cpp b/dom/filesystem/CreateDirectoryTask.cpp index 57a1f432c4..4b7774ee6d 100644 --- a/dom/filesystem/CreateDirectoryTask.cpp +++ b/dom/filesystem/CreateDirectoryTask.cpp @@ -9,23 +9,32 @@ #include "mozilla/dom/Directory.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" +#include "mozilla/dom/PFileSystemParams.h" #include "mozilla/dom/Promise.h" +#include "mozilla/ipc/BackgroundParent.h" #include "nsIFile.h" #include "nsStringGlue.h" namespace mozilla { + +using namespace ipc; + namespace dom { -/* static */ already_AddRefed -CreateDirectoryTask::Create(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - ErrorResult& aRv) +/** + * CreateDirectoryTaskChild + */ + +/* static */ already_AddRefed +CreateDirectoryTaskChild::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - RefPtr task = - new CreateDirectoryTask(aFileSystem, aTargetPath); + RefPtr task = + new CreateDirectoryTaskChild(aFileSystem, aTargetPath); // aTargetPath can be null. In this case SetError will be called. @@ -44,63 +53,30 @@ CreateDirectoryTask::Create(FileSystemBase* aFileSystem, return task.forget(); } -/* static */ already_AddRefed -CreateDirectoryTask::Create(FileSystemBase* aFileSystem, - const FileSystemCreateDirectoryParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv) -{ - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); - - RefPtr task = - new CreateDirectoryTask(aFileSystem, aParam, aParent); - - NS_ConvertUTF16toUTF8 path(aParam.realPath()); - aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - return task.forget(); -} - -CreateDirectoryTask::CreateDirectoryTask(FileSystemBase* aFileSystem, - nsIFile* aTargetPath) - : FileSystemTaskBase(aFileSystem) +CreateDirectoryTaskChild::CreateDirectoryTaskChild(FileSystemBase* aFileSystem, + nsIFile* aTargetPath) + : FileSystemTaskChildBase(aFileSystem) , mTargetPath(aTargetPath) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); } -CreateDirectoryTask::CreateDirectoryTask(FileSystemBase* aFileSystem, - const FileSystemCreateDirectoryParams& aParam, - FileSystemRequestParent* aParent) - : FileSystemTaskBase(aFileSystem, aParam, aParent) +CreateDirectoryTaskChild::~CreateDirectoryTaskChild() { - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); -} - -CreateDirectoryTask::~CreateDirectoryTask() -{ - MOZ_ASSERT(!mPromise || NS_IsMainThread(), - "mPromise should be released on main thread!"); + MOZ_ASSERT(NS_IsMainThread()); } already_AddRefed -CreateDirectoryTask::GetPromise() +CreateDirectoryTaskChild::GetPromise() { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); return RefPtr(mPromise).forget(); } FileSystemParams -CreateDirectoryTask::GetRequestParams(const nsString& aSerializedDOMPath, - ErrorResult& aRv) const +CreateDirectoryTaskChild::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); @@ -113,11 +89,92 @@ CreateDirectoryTask::GetRequestParams(const nsString& aSerializedDOMPath, return FileSystemCreateDirectoryParams(aSerializedDOMPath, path); } -FileSystemResponseValue -CreateDirectoryTask::GetSuccessRequestResult(ErrorResult& aRv) const +void +CreateDirectoryTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + const FileSystemDirectoryResponse& r = + aValue.get_FileSystemDirectoryResponse(); + + aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(r.realPath()), true, + getter_AddRefs(mTargetPath)); + NS_WARN_IF(aRv.Failed()); +} + +void +CreateDirectoryTaskChild::HandlerCallback() +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + if (mFileSystem->IsShutdown()) { + mPromise = nullptr; + return; + } + + if (HasError()) { + mPromise->MaybeReject(mErrorValue); + mPromise = nullptr; + return; + } + + RefPtr dir = Directory::Create(mFileSystem->GetParentObject(), + mTargetPath, + Directory::eNotDOMRootDirectory, + mFileSystem); + MOZ_ASSERT(dir); + + mPromise->MaybeResolve(dir); + mPromise = nullptr; +} + +void +CreateDirectoryTaskChild::GetPermissionAccessType(nsCString& aAccess) const +{ + aAccess.AssignLiteral(CREATE_DIRECTORY_TASK_PERMISSION); +} + +/** + * CreateDirectoryTaskParent + */ + +/* static */ already_AddRefed +CreateDirectoryTaskParent::Create(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new CreateDirectoryTaskParent(aFileSystem, aParam, aParent); + + aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(aParam.realPath()), true, + getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return task.forget(); +} + +CreateDirectoryTaskParent::CreateDirectoryTaskParent(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent) + : FileSystemTaskParentBase(aFileSystem, aParam, aParent) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); +} + +FileSystemResponseValue +CreateDirectoryTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const +{ + AssertIsOnBackgroundThread(); + nsAutoString path; aRv = mTargetPath->GetPath(path); if (NS_WARN_IF(aRv.Failed())) { @@ -127,20 +184,8 @@ CreateDirectoryTask::GetSuccessRequestResult(ErrorResult& aRv) const return FileSystemDirectoryResponse(path); } -void -CreateDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, - ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - FileSystemDirectoryResponse r = aValue; - - NS_ConvertUTF16toUTF8 path(r.realPath()); - aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(mTargetPath)); - NS_WARN_IF(aRv.Failed()); -} - nsresult -CreateDirectoryTask::Work() +CreateDirectoryTaskParent::IOWork() { MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); @@ -169,33 +214,9 @@ CreateDirectoryTask::Work() } void -CreateDirectoryTask::HandlerCallback() +CreateDirectoryTaskParent::GetPermissionAccessType(nsCString& aAccess) const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (mFileSystem->IsShutdown()) { - mPromise = nullptr; - return; - } - - if (HasError()) { - mPromise->MaybeReject(mErrorValue); - mPromise = nullptr; - return; - } - RefPtr dir = Directory::Create(mFileSystem->GetParentObject(), - mTargetPath, - Directory::eNotDOMRootDirectory, - mFileSystem); - MOZ_ASSERT(dir); - - mPromise->MaybeResolve(dir); - mPromise = nullptr; -} - -void -CreateDirectoryTask::GetPermissionAccessType(nsCString& aAccess) const -{ - aAccess.AssignLiteral("create"); + aAccess.AssignLiteral(CREATE_DIRECTORY_TASK_PERMISSION); } } // namespace dom diff --git a/dom/filesystem/CreateDirectoryTask.h b/dom/filesystem/CreateDirectoryTask.h index 837730629d..6cb9c41ff8 100644 --- a/dom/filesystem/CreateDirectoryTask.h +++ b/dom/filesystem/CreateDirectoryTask.h @@ -11,27 +11,24 @@ #include "nsAutoPtr.h" #include "mozilla/ErrorResult.h" +#define CREATE_DIRECTORY_TASK_PERMISSION "create" + namespace mozilla { namespace dom { +class FileSystemCreateDirectoryParams; class Promise; -class CreateDirectoryTask final : public FileSystemTaskBase +class CreateDirectoryTaskChild final : public FileSystemTaskChildBase { public: - static already_AddRefed + static already_AddRefed Create(FileSystemBase* aFileSystem, nsIFile* aTargetPath, ErrorResult& aRv); - static already_AddRefed - Create(FileSystemBase* aFileSystem, - const FileSystemCreateDirectoryParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv); - virtual - ~CreateDirectoryTask(); + ~CreateDirectoryTaskChild(); already_AddRefed GetPromise(); @@ -39,36 +36,54 @@ public: virtual void GetPermissionAccessType(nsCString& aAccess) const override; + virtual void + HandlerCallback() override; + protected: virtual FileSystemParams GetRequestParams(const nsString& aSerializedDOMPath, ErrorResult& aRv) const override; - virtual FileSystemResponseValue - GetSuccessRequestResult(ErrorResult& aRv) const override; - virtual void SetSuccessRequestResult(const FileSystemResponseValue& aValue, ErrorResult& aRv) override; - virtual nsresult - Work() override; - - virtual void - HandlerCallback() override; private: - CreateDirectoryTask(FileSystemBase* aFileSystem, - nsIFile* aTargetPath); - - CreateDirectoryTask(FileSystemBase* aFileSystem, - const FileSystemCreateDirectoryParams& aParam, - FileSystemRequestParent* aParent); + CreateDirectoryTaskChild(FileSystemBase* aFileSystem, + nsIFile* aTargetPath); RefPtr mPromise; nsCOMPtr mTargetPath; }; +class CreateDirectoryTaskParent final : public FileSystemTaskParentBase +{ +public: + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); + + virtual void + GetPermissionAccessType(nsCString& aAccess) const override; + +protected: + virtual nsresult + IOWork() override; + + virtual FileSystemResponseValue + GetSuccessRequestResult(ErrorResult& aRv) const override; + +private: + CreateDirectoryTaskParent(FileSystemBase* aFileSystem, + const FileSystemCreateDirectoryParams& aParam, + FileSystemRequestParent* aParent); + + nsCOMPtr mTargetPath; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/CreateFileTask.cpp b/dom/filesystem/CreateFileTask.cpp index a8a02f02ae..6ee3ebc92e 100644 --- a/dom/filesystem/CreateFileTask.cpp +++ b/dom/filesystem/CreateFileTask.cpp @@ -5,6 +5,8 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CreateFileTask.h" +#include "CreateDirectoryTask.h" +#include "RemoveTask.h" #include @@ -12,46 +14,49 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" +#include "mozilla/dom/PFileSystemParams.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" #include "nsIFile.h" #include "nsNetUtil.h" #include "nsIOutputStream.h" #include "nsStringGlue.h" +#define GET_PERMISSION_ACCESS_TYPE(aAccess) \ + if (mReplace) { \ + aAccess.AssignLiteral(REMOVE_TASK_PERMISSION); \ + return; \ + } \ + aAccess.AssignLiteral(CREATE_DIRECTORY_TASK_PERMISSION); + namespace mozilla { namespace dom { -uint32_t CreateFileTask::sOutputBufferSize = 0; +/** + *CreateFileTaskChild + */ -/* static */ already_AddRefed -CreateFileTask::Create(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - Blob* aBlobData, - InfallibleTArray& aArrayData, - bool aReplace, - ErrorResult& aRv) +/* static */ already_AddRefed +CreateFileTaskChild::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Blob* aBlobData, + InfallibleTArray& aArrayData, + bool aReplace, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - RefPtr task = - new CreateFileTask(aFileSystem, aTargetPath, aReplace); + RefPtr task = + new CreateFileTaskChild(aFileSystem, aTargetPath, aReplace); // aTargetPath can be null. In this case SetError will be called. - task->GetOutputBufferSize(); - if (aBlobData) { - if (XRE_IsParentProcess()) { - aBlobData->GetInternalStream(getter_AddRefs(task->mBlobStream), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - } else { - task->mBlobData = aBlobData; - } + task->mBlobImpl = aBlobData->Impl(); } task->mArrayData.SwapElements(aArrayData); @@ -71,53 +76,10 @@ CreateFileTask::Create(FileSystemBase* aFileSystem, return task.forget(); } -/* static */ already_AddRefed -CreateFileTask::Create(FileSystemBase* aFileSystem, - const FileSystemCreateFileParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv) -{ - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); - - RefPtr task = - new CreateFileTask(aFileSystem, aParam, aParent); - - task->GetOutputBufferSize(); - - NS_ConvertUTF16toUTF8 path(aParam.realPath()); - aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - task->mReplace = aParam.replace(); - - auto& data = aParam.data(); - - if (data.type() == FileSystemFileDataValue::TArrayOfuint8_t) { - task->mArrayData = data; - return task.forget(); - } - - BlobParent* bp = static_cast(static_cast(data)); - RefPtr blobImpl = bp->GetBlobImpl(); - MOZ_ASSERT(blobImpl, "blobData should not be null."); - - ErrorResult rv; - blobImpl->GetInternalStream(getter_AddRefs(task->mBlobStream), rv); - if (NS_WARN_IF(rv.Failed())) { - rv.SuppressException(); - } - - return task.forget(); -} - -CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - bool aReplace) - : FileSystemTaskBase(aFileSystem) +CreateFileTaskChild::CreateFileTaskChild(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + bool aReplace) + : FileSystemTaskChildBase(aFileSystem) , mTargetPath(aTargetPath) , mReplace(aReplace) { @@ -125,37 +87,21 @@ CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem, MOZ_ASSERT(aFileSystem); } -CreateFileTask::CreateFileTask(FileSystemBase* aFileSystem, - const FileSystemCreateFileParams& aParam, - FileSystemRequestParent* aParent) - : FileSystemTaskBase(aFileSystem, aParam, aParent) - , mReplace(false) +CreateFileTaskChild::~CreateFileTaskChild() { - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); -} - -CreateFileTask::~CreateFileTask() -{ - MOZ_ASSERT((!mPromise && !mBlobData) || NS_IsMainThread(), - "mPromise and mBlobData should be released on main thread!"); - - if (mBlobStream) { - mBlobStream->Close(); - } + MOZ_ASSERT(NS_IsMainThread()); } already_AddRefed -CreateFileTask::GetPromise() +CreateFileTaskChild::GetPromise() { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); return RefPtr(mPromise).forget(); } FileSystemParams -CreateFileTask::GetRequestParams(const nsString& aSerializedDOMPath, - ErrorResult& aRv) const +CreateFileTaskChild::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); FileSystemCreateFileParams param; @@ -166,12 +112,21 @@ CreateFileTask::GetRequestParams(const nsString& aSerializedDOMPath, return param; } + // If we are here, PBackground must be up and running: this method is called + // when the task has been already started by FileSystemPermissionRequest + // class and this happens only when PBackground actor has already been + // created. + PBackgroundChild* actor = + mozilla::ipc::BackgroundChild::GetForCurrentThread(); + MOZ_ASSERT(actor); + param.replace() = mReplace; - if (mBlobData) { - BlobChild* actor = - ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlobData); - if (actor) { - param.data() = actor; + if (mBlobImpl) { + PBlobChild* blobActor = + mozilla::ipc::BackgroundChild::GetOrCreateActorForBlobImpl(actor, + mBlobImpl); + if (blobActor) { + param.data() = blobActor; } } else { param.data() = mArrayData; @@ -179,11 +134,108 @@ CreateFileTask::GetRequestParams(const nsString& aSerializedDOMPath, return param; } -FileSystemResponseValue -CreateFileTask::GetSuccessRequestResult(ErrorResult& aRv) const +void +CreateFileTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + const FileSystemFileResponse& r = aValue.get_FileSystemFileResponse(); + + NS_ConvertUTF16toUTF8 path(r.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } +} + +void +CreateFileTaskChild::HandlerCallback() +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + + if (mFileSystem->IsShutdown()) { + mPromise = nullptr; + return; + } + + if (HasError()) { + mPromise->MaybeReject(mErrorValue); + mPromise = nullptr; + return; + } + + RefPtr file = File::CreateFromFile(mFileSystem->GetParentObject(), + mTargetPath); + mPromise->MaybeResolve(file); + mPromise = nullptr; +} + +void +CreateFileTaskChild::GetPermissionAccessType(nsCString& aAccess) const +{ + GET_PERMISSION_ACCESS_TYPE(aAccess) +} + +/** + * CreateFileTaskParent + */ + +uint32_t CreateFileTaskParent::sOutputBufferSize = 0; + +/* static */ already_AddRefed +CreateFileTaskParent::Create(FileSystemBase* aFileSystem, + const FileSystemCreateFileParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new CreateFileTaskParent(aFileSystem, aParam, aParent); + + NS_ConvertUTF16toUTF8 path(aParam.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + task->mReplace = aParam.replace(); + + const FileSystemFileDataValue& data = aParam.data(); + + if (data.type() == FileSystemFileDataValue::TArrayOfuint8_t) { + task->mArrayData = data; + return task.forget(); + } + + MOZ_ASSERT(data.type() == FileSystemFileDataValue::TPBlobParent); + + BlobParent* bp = static_cast(static_cast(data)); + task->mBlobImpl = bp->GetBlobImpl(); + MOZ_ASSERT(task->mBlobImpl, "blobData should not be null."); + + return task.forget(); +} + +CreateFileTaskParent::CreateFileTaskParent(FileSystemBase* aFileSystem, + const FileSystemCreateFileParams& aParam, + FileSystemRequestParent* aParent) + : FileSystemTaskParentBase(aFileSystem, aParam, aParent) + , mReplace(false) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); +} + +FileSystemResponseValue +CreateFileTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const +{ + AssertIsOnBackgroundThread(); + nsAutoString path; aRv = mTargetPath->GetPath(path); if (NS_WARN_IF(aRv.Failed())) { @@ -193,22 +245,8 @@ CreateFileTask::GetSuccessRequestResult(ErrorResult& aRv) const return FileSystemFileResponse(path); } -void -CreateFileTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, - ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - FileSystemFileResponse r = aValue; - - NS_ConvertUTF16toUTF8 path(r.realPath()); - aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(mTargetPath)); - if (NS_WARN_IF(aRv.Failed())) { - return; - } -} - nsresult -CreateFileTask::Work() +CreateFileTaskParent::IOWork() { class MOZ_RAII AutoClose final { @@ -280,6 +318,7 @@ CreateFileTask::Work() } AutoClose acOutputStream(outputStream); + MOZ_ASSERT(sOutputBufferSize); nsCOMPtr bufferedOutputStream; rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), @@ -291,11 +330,17 @@ CreateFileTask::Work() AutoClose acBufferedOutputStream(bufferedOutputStream); - if (mBlobStream) { - // Write the file content from blob data. + // Write the file content from blob data. + if (mBlobImpl) { + ErrorResult error; + nsCOMPtr blobStream; + mBlobImpl->GetInternalStream(getter_AddRefs(blobStream), error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } uint64_t bufSize = 0; - rv = mBlobStream->Available(&bufSize); + rv = blobStream->Available(&bufSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -303,15 +348,14 @@ CreateFileTask::Work() while (bufSize && !mFileSystem->IsShutdown()) { uint32_t written = 0; uint32_t writeSize = bufSize < UINT32_MAX ? bufSize : UINT32_MAX; - rv = bufferedOutputStream->WriteFrom(mBlobStream, writeSize, &written); + rv = bufferedOutputStream->WriteFrom(blobStream, writeSize, &written); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bufSize -= written; } - mBlobStream->Close(); - mBlobStream = nullptr; + blobStream->Close(); if (mFileSystem->IsShutdown()) { return NS_ERROR_FAILURE; @@ -338,49 +382,23 @@ CreateFileTask::Work() return NS_OK; } -void -CreateFileTask::HandlerCallback() +nsresult +CreateFileTaskParent::MainThreadWork() { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (mFileSystem->IsShutdown()) { - mPromise = nullptr; - mBlobData = nullptr; - return; + MOZ_ASSERT(NS_IsMainThread()); + + if (!sOutputBufferSize) { + sOutputBufferSize = + mozilla::Preferences::GetUint("dom.filesystem.outputBufferSize", 4096 * 4); } - if (HasError()) { - mPromise->MaybeReject(mErrorValue); - mPromise = nullptr; - mBlobData = nullptr; - return; - } - - RefPtr file = File::CreateFromFile(mFileSystem->GetParentObject(), - mTargetPath); - mPromise->MaybeResolve(file); - mPromise = nullptr; - mBlobData = nullptr; + return FileSystemTaskParentBase::MainThreadWork(); } void -CreateFileTask::GetPermissionAccessType(nsCString& aAccess) const +CreateFileTaskParent::GetPermissionAccessType(nsCString& aAccess) const { - if (mReplace) { - aAccess.AssignLiteral("write"); - return; - } - - aAccess.AssignLiteral("create"); -} - -void -CreateFileTask::GetOutputBufferSize() const -{ - if (sOutputBufferSize || !XRE_IsParentProcess()) { - return; - } - sOutputBufferSize = - mozilla::Preferences::GetUint("dom.filesystem.outputBufferSize", 4096 * 4); + GET_PERMISSION_ACCESS_TYPE(aAccess) } } // namespace dom diff --git a/dom/filesystem/CreateFileTask.h b/dom/filesystem/CreateFileTask.h index ece93d3e9a..69708c9b9f 100644 --- a/dom/filesystem/CreateFileTask.h +++ b/dom/filesystem/CreateFileTask.h @@ -20,10 +20,10 @@ class Blob; class BlobImpl; class Promise; -class CreateFileTask final : public FileSystemTaskBase +class CreateFileTaskChild final : public FileSystemTaskChildBase { public: - static already_AddRefed + static already_AddRefed Create(FileSystemBase* aFileSystem, nsIFile* aFile, Blob* aBlobData, @@ -31,14 +31,8 @@ public: bool replace, ErrorResult& aRv); - static already_AddRefed - Create(FileSystemBase* aFileSystem, - const FileSystemCreateFileParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv); - virtual - ~CreateFileTask(); + ~CreateFileTaskChild(); already_AddRefed GetPromise(); @@ -51,40 +45,70 @@ protected: GetRequestParams(const nsString& aSerializedDOMPath, ErrorResult& aRv) const override; - virtual FileSystemResponseValue - GetSuccessRequestResult(ErrorResult& aRv) const override; - virtual void SetSuccessRequestResult(const FileSystemResponseValue& aValue, ErrorResult& aRv) override; - virtual nsresult - Work() override; - virtual void HandlerCallback() override; private: - CreateFileTask(FileSystemBase* aFileSystem, - nsIFile* aFile, - bool aReplace); + CreateFileTaskChild(FileSystemBase* aFileSystem, + nsIFile* aFile, + bool aReplace); - CreateFileTask(FileSystemBase* aFileSystem, - const FileSystemCreateFileParams& aParam, - FileSystemRequestParent* aParent); - - void - GetOutputBufferSize() const; - - static uint32_t sOutputBufferSize; RefPtr mPromise; nsCOMPtr mTargetPath; - // Not thread-safe and should be released on main thread. - RefPtr mBlobData; + RefPtr mBlobImpl; - nsCOMPtr mBlobStream; + // This is going to be the content of the file, received by createFile() + // params. InfallibleTArray mArrayData; + + bool mReplace; +}; + +class CreateFileTaskParent final : public FileSystemTaskParentBase +{ +public: + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemCreateFileParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); + + virtual bool + NeedToGoToMainThread() const override { return true; } + + virtual nsresult + MainThreadWork() override; + + virtual void + GetPermissionAccessType(nsCString& aAccess) const override; + +protected: + virtual FileSystemResponseValue + GetSuccessRequestResult(ErrorResult& aRv) const override; + + virtual nsresult + IOWork() override; + +private: + CreateFileTaskParent(FileSystemBase* aFileSystem, + const FileSystemCreateFileParams& aParam, + FileSystemRequestParent* aParent); + + static uint32_t sOutputBufferSize; + + nsCOMPtr mTargetPath; + + RefPtr mBlobImpl; + + // This is going to be the content of the file, received by createFile() + // params. + InfallibleTArray mArrayData; + bool mReplace; }; diff --git a/dom/filesystem/DeviceStorageFileSystem.cpp b/dom/filesystem/DeviceStorageFileSystem.cpp index 07b9e18c9e..c48ee1429f 100644 --- a/dom/filesystem/DeviceStorageFileSystem.cpp +++ b/dom/filesystem/DeviceStorageFileSystem.cpp @@ -23,15 +23,21 @@ namespace dom { DeviceStorageFileSystem::DeviceStorageFileSystem(const nsAString& aStorageType, const nsAString& aStorageName) - : mWindowId(0) + : mStorageType(aStorageType) + , mStorageName(aStorageName) + , mWindowId(0) { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + mPermissionCheckType = ePermissionCheckByTestingPref; - mStorageType = aStorageType; - mStorageName = aStorageName; - - mRequiresPermissionChecks = - !mozilla::Preferences::GetBool("device.storage.prompt.testing", false); + if (NS_IsMainThread()) { + if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) { + mPermissionCheckType = ePermissionCheckNotRequired; + } else { + mPermissionCheckType = ePermissionCheckRequired; + } + } else { + AssertIsOnBackgroundThread(); + } // Get the permission name required to access the file system. nsresult rv = @@ -53,18 +59,23 @@ DeviceStorageFileSystem::DeviceStorageFileSystem(const nsAString& aStorageType, // DeviceStorageTypeChecker is a singleton object and must be initialized on // the main thread. We initialize it here so that we can use it on the worker // thread. - DebugOnly typeChecker - = DeviceStorageTypeChecker::CreateOrGet(); - MOZ_ASSERT(typeChecker); + if (NS_IsMainThread()) { + DebugOnly typeChecker = + DeviceStorageTypeChecker::CreateOrGet(); + MOZ_ASSERT(typeChecker); + } } DeviceStorageFileSystem::~DeviceStorageFileSystem() { + AssertIsOnOwningThread(); } already_AddRefed DeviceStorageFileSystem::Clone() { + AssertIsOnOwningThread(); + RefPtr fs = new DeviceStorageFileSystem(mStorageType, mStorageName); @@ -77,7 +88,9 @@ void DeviceStorageFileSystem::Init(nsDOMDeviceStorage* aDeviceStorage) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnOwningThread(); MOZ_ASSERT(aDeviceStorage); + nsCOMPtr window = aDeviceStorage->GetOwner(); MOZ_ASSERT(window->IsInnerWindow()); mWindowId = window->WindowID(); @@ -86,7 +99,7 @@ DeviceStorageFileSystem::Init(nsDOMDeviceStorage* aDeviceStorage) void DeviceStorageFileSystem::Shutdown() { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnOwningThread(); mShutdown = true; } @@ -94,6 +107,8 @@ nsISupports* DeviceStorageFileSystem::GetParentObject() const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnOwningThread(); + nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowId); MOZ_ASSERT_IF(!mShutdown, window); return ToSupports(window); @@ -102,14 +117,15 @@ DeviceStorageFileSystem::GetParentObject() const void DeviceStorageFileSystem::GetRootName(nsAString& aRetval) const { + AssertIsOnOwningThread(); aRetval = mStorageName; } bool DeviceStorageFileSystem::IsSafeFile(nsIFile* aFile) const { - MOZ_ASSERT(XRE_IsParentProcess(), - "Should be on parent process!"); + MOZ_ASSERT(XRE_IsParentProcess(), "Should be on parent process!"); + MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aFile); nsCOMPtr rootPath; @@ -134,7 +150,7 @@ DeviceStorageFileSystem::IsSafeFile(nsIFile* aFile) const bool DeviceStorageFileSystem::IsSafeDirectory(Directory* aDir) const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnOwningThread(); MOZ_ASSERT(aDir); ErrorResult rv; @@ -157,6 +173,8 @@ DeviceStorageFileSystem::IsSafeDirectory(Directory* aDir) const void DeviceStorageFileSystem::SerializeDOMPath(nsAString& aString) const { + AssertIsOnOwningThread(); + // Generate the string representation of the file system. aString.AssignLiteral("devicestorage-"); aString.Append(mStorageType); @@ -164,5 +182,16 @@ DeviceStorageFileSystem::SerializeDOMPath(nsAString& aString) const aString.Append(mStorageName); } +nsresult +DeviceStorageFileSystem::MainThreadWork() +{ + MOZ_ASSERT(NS_IsMainThread()); + + DebugOnly typeChecker = + DeviceStorageTypeChecker::CreateOrGet(); + MOZ_ASSERT(typeChecker); + return NS_OK; +} + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/DeviceStorageFileSystem.h b/dom/filesystem/DeviceStorageFileSystem.h index 32168bc825..f77e15021e 100644 --- a/dom/filesystem/DeviceStorageFileSystem.h +++ b/dom/filesystem/DeviceStorageFileSystem.h @@ -48,6 +48,12 @@ public: virtual void SerializeDOMPath(nsAString& aSerializedString) const override; + virtual bool + NeedToGoToMainThread() const override { return true; } + + virtual nsresult + MainThreadWork() override; + private: virtual ~DeviceStorageFileSystem(); diff --git a/dom/filesystem/Directory.cpp b/dom/filesystem/Directory.cpp index a8066980f3..591ecec0ab 100644 --- a/dom/filesystem/Directory.cpp +++ b/dom/filesystem/Directory.cpp @@ -111,10 +111,21 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Directory) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -// static -already_AddRefed +/* static */ bool +Directory::DeviceStorageEnabled(JSContext* aCx, JSObject* aObj) +{ + if (!NS_IsMainThread()) { + return false; + } + + return Preferences::GetBool("device.storage.enabled", false); +} + +/* static */ already_AddRefed Directory::GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv) { + // Only exposed for DeviceStorage. + MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aFileSystem); nsCOMPtr path; @@ -124,8 +135,9 @@ Directory::GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv) return nullptr; } - RefPtr task = - GetFileOrDirectoryTask::Create(aFileSystem, path, eDOMRootDirectory, true, aRv); + RefPtr task = + GetFileOrDirectoryTaskChild::Create(aFileSystem, path, eDOMRootDirectory, + true, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -138,7 +150,6 @@ Directory::GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv) Directory::Create(nsISupports* aParent, nsIFile* aFile, DirectoryType aType, FileSystemBase* aFileSystem) { - MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aParent); MOZ_ASSERT(aFile); @@ -168,7 +179,6 @@ Directory::Directory(nsISupports* aParent, , mFile(aFile) , mType(aType) { - MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aFile); // aFileSystem can be null. In this case we create a OSFileSystem when needed. @@ -220,6 +230,9 @@ already_AddRefed Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions, ErrorResult& aRv) { + // Only exposed for DeviceStorage. + MOZ_ASSERT(NS_IsMainThread()); + RefPtr blobData; InfallibleTArray arrayData; bool replace = (aOptions.mIfExists == CreateIfExistsMode::Replace); @@ -252,8 +265,9 @@ Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions, return nullptr; } - RefPtr task = - CreateFileTask::Create(fs, realPath, blobData, arrayData, replace, aRv); + RefPtr task = + CreateFileTaskChild::Create(fs, realPath, blobData, arrayData, replace, + aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -266,6 +280,9 @@ Directory::CreateFile(const nsAString& aPath, const CreateFileOptions& aOptions, already_AddRefed Directory::CreateDirectory(const nsAString& aPath, ErrorResult& aRv) { + // Only exposed for DeviceStorage. + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr realPath; nsresult error = DOMPathToRealPath(aPath, getter_AddRefs(realPath)); @@ -274,8 +291,8 @@ Directory::CreateDirectory(const nsAString& aPath, ErrorResult& aRv) return nullptr; } - RefPtr task = - CreateDirectoryTask::Create(fs, realPath, aRv); + RefPtr task = + CreateDirectoryTaskChild::Create(fs, realPath, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -288,6 +305,9 @@ Directory::CreateDirectory(const nsAString& aPath, ErrorResult& aRv) already_AddRefed Directory::Get(const nsAString& aPath, ErrorResult& aRv) { + // Only exposed for DeviceStorage. + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr realPath; nsresult error = DOMPathToRealPath(aPath, getter_AddRefs(realPath)); @@ -296,9 +316,9 @@ Directory::Get(const nsAString& aPath, ErrorResult& aRv) return nullptr; } - RefPtr task = - GetFileOrDirectoryTask::Create(fs, realPath, eNotDOMRootDirectory, false, - aRv); + RefPtr task = + GetFileOrDirectoryTaskChild::Create(fs, realPath, eNotDOMRootDirectory, + false, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -311,12 +331,16 @@ Directory::Get(const nsAString& aPath, ErrorResult& aRv) already_AddRefed Directory::Remove(const StringOrFileOrDirectory& aPath, ErrorResult& aRv) { + // Only exposed for DeviceStorage. + MOZ_ASSERT(NS_IsMainThread()); return RemoveInternal(aPath, false, aRv); } already_AddRefed Directory::RemoveDeep(const StringOrFileOrDirectory& aPath, ErrorResult& aRv) { + // Only exposed for DeviceStorage. + MOZ_ASSERT(NS_IsMainThread()); return RemoveInternal(aPath, true, aRv); } @@ -324,6 +348,9 @@ already_AddRefed Directory::RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive, ErrorResult& aRv) { + // Only exposed for DeviceStorage. + MOZ_ASSERT(NS_IsMainThread()); + nsresult error = NS_OK; nsCOMPtr realPath; @@ -360,8 +387,8 @@ Directory::RemoveInternal(const StringOrFileOrDirectory& aPath, bool aRecursive, error = NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR; } - RefPtr task = - RemoveTask::Create(fs, mFile, realPath, aRecursive, aRv); + RefPtr task = + RemoveTaskChild::Create(fs, mFile, realPath, aRecursive, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -409,8 +436,8 @@ Directory::GetFilesAndDirectories(ErrorResult& aRv) return nullptr; } - RefPtr task = - GetDirectoryListingTask::Create(fs, mFile, mType, mFilters, aRv); + RefPtr task = + GetDirectoryListingTaskChild::Create(fs, mFile, mType, mFilters, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } @@ -480,5 +507,17 @@ Directory::DOMPathToRealPath(const nsAString& aPath, nsIFile** aFile) const return NS_OK; } +bool +Directory::ClonableToDifferentThreadOrProcess() const +{ + // If we don't have a fileSystem we are going to create a OSFileSystem that is + // clonable everywhere. + if (!mFileSystem) { + return true; + } + + return mFileSystem->ClonableToDifferentThreadOrProcess(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/Directory.h b/dom/filesystem/Directory.h index 1d121fbefe..88b2f56c70 100644 --- a/dom/filesystem/Directory.h +++ b/dom/filesystem/Directory.h @@ -53,6 +53,9 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Directory) + static bool + DeviceStorageEnabled(JSContext* aCx, JSObject* aObj); + static already_AddRefed GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv); @@ -144,6 +147,9 @@ public: return mType; } + bool + ClonableToDifferentThreadOrProcess() const; + private: Directory(nsISupports* aParent, nsIFile* aFile, DirectoryType aType, diff --git a/dom/filesystem/FileSystemBase.cpp b/dom/filesystem/FileSystemBase.cpp index 75909292c2..64aa7bb072 100644 --- a/dom/filesystem/FileSystemBase.cpp +++ b/dom/filesystem/FileSystemBase.cpp @@ -17,6 +17,9 @@ namespace dom { already_AddRefed FileSystemBase::DeserializeDOMPath(const nsAString& aString) { + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + if (StringBeginsWith(aString, NS_LITERAL_STRING("devicestorage-"))) { // The string representation of devicestorage file system is of the format: // devicestorage-StorageType-StorageName @@ -39,34 +42,41 @@ FileSystemBase::DeserializeDOMPath(const nsAString& aString) return f.forget(); } - return RefPtr(new OSFileSystem(aString)).forget(); + return RefPtr(new OSFileSystemParent(aString)).forget(); } FileSystemBase::FileSystemBase() : mShutdown(false) - , mRequiresPermissionChecks(true) + , mPermissionCheckType(eNotSet) +#ifdef DEBUG + , mOwningThread(PR_GetCurrentThread()) +#endif { } FileSystemBase::~FileSystemBase() { + AssertIsOnOwningThread(); } void FileSystemBase::Shutdown() { + AssertIsOnOwningThread(); mShutdown = true; } nsISupports* FileSystemBase::GetParentObject() const { + AssertIsOnOwningThread(); return nullptr; } bool FileSystemBase::GetRealPath(BlobImpl* aFile, nsIFile** aPath) const { + AssertIsOnOwningThread(); MOZ_ASSERT(aFile, "aFile Should not be null."); MOZ_ASSERT(aPath); @@ -89,12 +99,14 @@ FileSystemBase::GetRealPath(BlobImpl* aFile, nsIFile** aPath) const bool FileSystemBase::IsSafeFile(nsIFile* aFile) const { + AssertIsOnOwningThread(); return false; } bool FileSystemBase::IsSafeDirectory(Directory* aDir) const { + AssertIsOnOwningThread(); return false; } @@ -104,6 +116,7 @@ FileSystemBase::GetDOMPath(nsIFile* aFile, nsAString& aRetval, ErrorResult& aRv) const { + AssertIsOnOwningThread(); MOZ_ASSERT(aFile); if (aType == Directory::eDOMRootDirectory) { @@ -171,5 +184,12 @@ FileSystemBase::GetDOMPath(nsIFile* aFile, } } +void +FileSystemBase::AssertIsOnOwningThread() const +{ + MOZ_ASSERT(mOwningThread); + MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); +} + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/FileSystemBase.h b/dom/filesystem/FileSystemBase.h index a3cbfcecb0..8330f394d6 100644 --- a/dom/filesystem/FileSystemBase.h +++ b/dom/filesystem/FileSystemBase.h @@ -18,8 +18,8 @@ class BlobImpl; class FileSystemBase { - NS_INLINE_DECL_REFCOUNTING(FileSystemBase) public: + NS_INLINE_DECL_REFCOUNTING(FileSystemBase) // Create file system object from its string representation. static already_AddRefed @@ -87,23 +87,63 @@ public: return mPermission; } - bool - RequiresPermissionChecks() const + // The decision about doing or not doing the permission check cannot be done + // everywhere because, for some FileSystemBase implementation, this depends on + // a preference. + // This enum describes all the possible decisions. The implementation will do + // the check on the main-thread in the child and in the parent process when + // needed. + // Note: the permission check should not fail in PBackground because that + // means that the child has been compromised. If this happens the child + // process is killed. + enum ePermissionCheckType { + // When on the main-thread, we must check if we have + // device.storage.prompt.testing set to true. + ePermissionCheckByTestingPref, + + // No permission check must be done. + ePermissionCheckNotRequired, + + // Permission check is required. + ePermissionCheckRequired, + + // This is the default value. We crash if this is let like this. + eNotSet + }; + + ePermissionCheckType + PermissionCheckType() const { - return mRequiresPermissionChecks; + MOZ_ASSERT(mPermissionCheckType != eNotSet); + return mPermissionCheckType; } + // IPC initialization + // See how these 2 methods are used in FileSystemTaskChildBase. + + virtual bool + NeedToGoToMainThread() const { return false; } + + virtual nsresult + MainThreadWork() { return NS_ERROR_FAILURE; } + + virtual bool + ClonableToDifferentThreadOrProcess() const { return false; } + // CC methods virtual void Unlink() {} virtual void Traverse(nsCycleCollectionTraversalCallback &cb) {} + void + AssertIsOnOwningThread() const; + protected: virtual ~FileSystemBase(); // The local path of the root (i.e. the OS path, with OS path separators, of // the OS directory that acts as the root of this OSFileSystem). // This path must be set by the FileSystem implementation immediately - // because it will be used for the validation of any FileSystemTaskBase. + // because it will be used for the validation of any FileSystemTaskChildBase. // The concept of this path is that, any task will never go out of it and this // must be considered the OS 'root' of the current FileSystem. Different // Directory object can have different OS 'root' path. @@ -119,7 +159,11 @@ protected: // The permission name required to access the file system. nsCString mPermission; - bool mRequiresPermissionChecks; + ePermissionCheckType mPermissionCheckType; + +#ifdef DEBUG + PRThread* mOwningThread; +#endif }; } // namespace dom diff --git a/dom/filesystem/FileSystemPermissionRequest.cpp b/dom/filesystem/FileSystemPermissionRequest.cpp index b08eb1bdd9..42410b9220 100644 --- a/dom/filesystem/FileSystemPermissionRequest.cpp +++ b/dom/filesystem/FileSystemPermissionRequest.cpp @@ -8,28 +8,128 @@ #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemTaskBase.h" #include "mozilla/dom/FileSystemUtils.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/PBackgroundChild.h" #include "nsIDocument.h" +#include "nsIIPCBackgroundChildCreateCallback.h" #include "nsPIDOMWindow.h" #include "nsContentPermissionHelper.h" namespace mozilla { namespace dom { +namespace { + +// This class takes care of the PBackground initialization and, once this step +// is completed, it starts the task. +class PBackgroundInitializer final : public nsIIPCBackgroundChildCreateCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK + + static void + ScheduleTask(FileSystemTaskChildBase* aTask) + { + MOZ_ASSERT(aTask); + RefPtr pb = new PBackgroundInitializer(aTask); + } + +private: + explicit PBackgroundInitializer(FileSystemTaskChildBase* aTask) + : mTask(aTask) + { + MOZ_ASSERT(aTask); + + PBackgroundChild* actor = + mozilla::ipc::BackgroundChild::GetForCurrentThread(); + if (actor) { + ActorCreated(actor); + } else { + if (NS_WARN_IF( + !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) { + MOZ_CRASH(); + } + } + } + + ~PBackgroundInitializer() + {} + + RefPtr mTask; +}; + +NS_IMPL_ISUPPORTS(PBackgroundInitializer, + nsIIPCBackgroundChildCreateCallback) + +void +PBackgroundInitializer::ActorFailed() +{ + MOZ_CRASH("Failed to create a PBackgroundChild actor!"); +} + +void +PBackgroundInitializer::ActorCreated(mozilla::ipc::PBackgroundChild* aActor) +{ + mTask->Start(); +} + +// This must be a CancelableRunnable because it can be dispatched to a worker +// thread. But we don't care about the Cancel() because in that case, Run() is +// not called and the task is deleted by the DTOR. +class AsyncStartRunnable final : public CancelableRunnable +{ +public: + explicit AsyncStartRunnable(FileSystemTaskChildBase* aTask) + : mTask(aTask) + { + MOZ_ASSERT(aTask); + } + + NS_IMETHOD + Run() override + { + PBackgroundInitializer::ScheduleTask(mTask); + return NS_OK; + } + +private: + RefPtr mTask; +}; + +} // anonymous namespace + NS_IMPL_ISUPPORTS(FileSystemPermissionRequest, nsIRunnable, nsIContentPermissionRequest) -// static -void -FileSystemPermissionRequest::RequestForTask(FileSystemTaskBase* aTask) +/* static */ void +FileSystemPermissionRequest::RequestForTask(FileSystemTaskChildBase* aTask) { - MOZ_ASSERT(aTask, "aTask should not be null!"); + MOZ_ASSERT(aTask); + + RefPtr filesystem = aTask->GetFileSystem(); + if (!filesystem) { + return; + } + + if (filesystem->PermissionCheckType() == FileSystemBase::ePermissionCheckNotRequired) { + // Let's make the scheduling of this task asynchronous. + RefPtr runnable = new AsyncStartRunnable(aTask); + NS_DispatchToCurrentThread(runnable); + return; + } + + // We don't need any permission check for the FileSystem API. If we are here + // it's because we are dealing with a DeviceStorage API that is main-thread + // only. MOZ_ASSERT(NS_IsMainThread()); + RefPtr request = new FileSystemPermissionRequest(aTask); NS_DispatchToCurrentThread(request); } -FileSystemPermissionRequest::FileSystemPermissionRequest(FileSystemTaskBase* aTask) +FileSystemPermissionRequest::FileSystemPermissionRequest(FileSystemTaskChildBase* aTask) : mTask(aTask) { MOZ_ASSERT(mTask, "aTask should not be null!"); @@ -98,7 +198,7 @@ FileSystemPermissionRequest::Cancel() { MOZ_ASSERT(NS_IsMainThread()); mTask->SetError(NS_ERROR_DOM_SECURITY_ERR); - mTask->Start(); + ScheduleTask(); return NS_OK; } @@ -107,7 +207,7 @@ FileSystemPermissionRequest::Allow(JS::HandleValue aChoices) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aChoices.isUndefined()); - mTask->Start(); + ScheduleTask(); return NS_OK; } @@ -122,7 +222,13 @@ FileSystemPermissionRequest::Run() return NS_OK; } - if (!filesystem->RequiresPermissionChecks()) { + if (filesystem->PermissionCheckType() == FileSystemBase::ePermissionCheckNotRequired) { + Allow(JS::UndefinedHandleValue); + return NS_OK; + } + + if (filesystem->PermissionCheckType() == FileSystemBase::ePermissionCheckByTestingPref && + mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) { Allow(JS::UndefinedHandleValue); return NS_OK; } @@ -146,5 +252,11 @@ FileSystemPermissionRequest::GetRequester(nsIContentPermissionRequester** aReque return NS_OK; } +void +FileSystemPermissionRequest::ScheduleTask() +{ + PBackgroundInitializer::ScheduleTask(mTask); +} + } /* namespace dom */ } /* namespace mozilla */ diff --git a/dom/filesystem/FileSystemPermissionRequest.h b/dom/filesystem/FileSystemPermissionRequest.h index 54850074f7..f0c20fb10f 100644 --- a/dom/filesystem/FileSystemPermissionRequest.h +++ b/dom/filesystem/FileSystemPermissionRequest.h @@ -17,7 +17,7 @@ class nsPIDOMWindow; namespace mozilla { namespace dom { -class FileSystemTaskBase; +class FileSystemTaskChildBase; class FileSystemPermissionRequest final : public nsIContentPermissionRequest @@ -26,20 +26,27 @@ class FileSystemPermissionRequest final public: // Request permission for the given task. static void - RequestForTask(FileSystemTaskBase* aTask); + RequestForTask(FileSystemTaskChildBase* aTask); NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSICONTENTPERMISSIONREQUEST NS_DECL_NSIRUNNABLE -private: - explicit FileSystemPermissionRequest(FileSystemTaskBase* aTask); - virtual +private: + explicit FileSystemPermissionRequest(FileSystemTaskChildBase* aTask); + ~FileSystemPermissionRequest(); + // Once the permission check has been done, we must run the task using IPC and + // PBackground. This method checks if the PBackground thread is ready to + // receive the task and in case waits for ActorCreated() to be called using + // the PBackgroundInitializer class (see FileSystemPermissionRequest.cpp). + void + ScheduleTask(); + nsCString mPermissionType; nsCString mPermissionAccess; - RefPtr mTask; + RefPtr mTask; nsCOMPtr mWindow; nsCOMPtr mPrincipal; nsCOMPtr mRequester; diff --git a/dom/filesystem/FileSystemRequestParent.cpp b/dom/filesystem/FileSystemRequestParent.cpp index d2742a261c..12f4caee5f 100644 --- a/dom/filesystem/FileSystemRequestParent.cpp +++ b/dom/filesystem/FileSystemRequestParent.cpp @@ -18,18 +18,22 @@ namespace mozilla { namespace dom { FileSystemRequestParent::FileSystemRequestParent() + : mDestroyed(false) { + AssertIsOnBackgroundThread(); } FileSystemRequestParent::~FileSystemRequestParent() { + AssertIsOnBackgroundThread(); } #define FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(name) \ case FileSystemParams::TFileSystem##name##Params: { \ const FileSystem##name##Params& p = aParams; \ mFileSystem = FileSystemBase::DeserializeDOMPath(p.filesystem()); \ - task = name##Task::Create(mFileSystem, p, this, rv); \ + MOZ_ASSERT(mFileSystem); \ + mTask = name##TaskParent::Create(mFileSystem, p, this, rv); \ if (NS_WARN_IF(rv.Failed())) { \ return false; \ } \ @@ -37,11 +41,10 @@ FileSystemRequestParent::~FileSystemRequestParent() } bool -FileSystemRequestParent::Dispatch(ContentParent* aParent, - const FileSystemParams& aParams) +FileSystemRequestParent::Initialize(const FileSystemParams& aParams) { - MOZ_ASSERT(aParent, "aParent should not be null."); - RefPtr task; + AssertIsOnBackgroundThread(); + ErrorResult rv; switch (aParams.type()) { @@ -58,39 +61,47 @@ FileSystemRequestParent::Dispatch(ContentParent* aParent, } } - if (NS_WARN_IF(!task || !mFileSystem)) { + if (NS_WARN_IF(!mTask || !mFileSystem)) { // Should never reach here. return false; } - if (mFileSystem->RequiresPermissionChecks()) { - // Check the content process permission. + if (mFileSystem->PermissionCheckType() != FileSystemBase::ePermissionCheckNotRequired) { + nsAutoCString access; + mTask->GetPermissionAccessType(access); - nsCString access; - task->GetPermissionAccessType(access); - - nsAutoCString permissionName; - permissionName = mFileSystem->GetPermission(); - permissionName.Append('-'); - permissionName.Append(access); - - if (!AssertAppProcessPermission(aParent, permissionName.get())) { - return false; - } + mPermissionName = mFileSystem->GetPermission(); + mPermissionName.Append('-'); + mPermissionName.Append(access); } - task->Start(); return true; } void -FileSystemRequestParent::ActorDestroy(ActorDestroyReason why) +FileSystemRequestParent::Start() { + MOZ_ASSERT(!mDestroyed); + MOZ_ASSERT(mFileSystem); + MOZ_ASSERT(mTask); + + mTask->Start(); +} + +void +FileSystemRequestParent::ActorDestroy(ActorDestroyReason aWhy) +{ + AssertIsOnBackgroundThread(); + MOZ_ASSERT(!mDestroyed); + if (!mFileSystem) { return; } + mFileSystem->Shutdown(); mFileSystem = nullptr; + mTask = nullptr; + mDestroyed = true; } } // namespace dom diff --git a/dom/filesystem/FileSystemRequestParent.h b/dom/filesystem/FileSystemRequestParent.h index e9e0361115..ebc97c0ea1 100644 --- a/dom/filesystem/FileSystemRequestParent.h +++ b/dom/filesystem/FileSystemRequestParent.h @@ -8,39 +8,57 @@ #define mozilla_dom_FileSystemRequestParent_h #include "mozilla/dom/PFileSystemRequestParent.h" -#include "mozilla/dom/ContentChild.h" -#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/FileSystemBase.h" namespace mozilla { namespace dom { -class FileSystemBase; +class FileSystemParams; +class FileSystemTaskParentBase; -class FileSystemRequestParent final - : public PFileSystemRequestParent +class FileSystemRequestParent final : public PFileSystemRequestParent { NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemRequestParent) + public: FileSystemRequestParent(); - bool - IsRunning() + const nsCString& + PermissionName() const { - return state() == PFileSystemRequest::__Start; + return mPermissionName; + } + + FileSystemBase::ePermissionCheckType + PermissionCheckType() const + { + return mFileSystem ? mFileSystem->PermissionCheckType() + : FileSystemBase::eNotSet; } bool - Dispatch(ContentParent* aParent, const FileSystemParams& aParams); + Initialize(const FileSystemParams& aParams); + + void + Start(); + + bool Destroyed() const + { + return mDestroyed; + } virtual void ActorDestroy(ActorDestroyReason why) override; private: - // Private destructor, to discourage deletion outside of Release(): - virtual ~FileSystemRequestParent(); RefPtr mFileSystem; + RefPtr mTask; + + nsCString mPermissionName; + + bool mDestroyed; }; } // namespace dom diff --git a/dom/filesystem/FileSystemTaskBase.cpp b/dom/filesystem/FileSystemTaskBase.cpp index 3468d59d45..4d6f6ecf47 100644 --- a/dom/filesystem/FileSystemTaskBase.cpp +++ b/dom/filesystem/FileSystemTaskBase.cpp @@ -7,14 +7,13 @@ #include "mozilla/dom/FileSystemTaskBase.h" #include "nsNetCID.h" -#include "mozilla/dom/ContentChild.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemRequestParent.h" #include "mozilla/dom/FileSystemUtils.h" #include "mozilla/dom/Promise.h" -#include "mozilla/dom/PContent.h" #include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/ipc/BackgroundParent.h" #include "mozilla/unused.h" #include "nsProxyRelease.h" @@ -23,85 +22,124 @@ namespace dom { namespace { -class FileSystemReleaseRunnable final : public nsRunnable +nsresult +FileSystemErrorFromNsError(const nsresult& aErrorValue) { -public: - explicit FileSystemReleaseRunnable(RefPtr& aDoomed) - : mDoomed(nullptr) - { - aDoomed.swap(mDoomed); + uint16_t module = NS_ERROR_GET_MODULE(aErrorValue); + if (module == NS_ERROR_MODULE_DOM_FILESYSTEM || + module == NS_ERROR_MODULE_DOM_FILE || + module == NS_ERROR_MODULE_DOM) { + return aErrorValue; } - NS_IMETHOD Run() + switch (aErrorValue) { + case NS_OK: + return NS_OK; + + case NS_ERROR_FILE_INVALID_PATH: + case NS_ERROR_FILE_UNRECOGNIZED_PATH: + return NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; + + case NS_ERROR_FILE_DESTINATION_NOT_DIR: + return NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR; + + case NS_ERROR_FILE_ACCESS_DENIED: + case NS_ERROR_FILE_DIR_NOT_EMPTY: + return NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR; + + case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST: + case NS_ERROR_NOT_AVAILABLE: + return NS_ERROR_DOM_FILE_NOT_FOUND_ERR; + + case NS_ERROR_FILE_ALREADY_EXISTS: + return NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR; + + case NS_ERROR_FILE_NOT_DIRECTORY: + return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR; + + case NS_ERROR_UNEXPECTED: + default: + return NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR; + } +} + +nsresult +DispatchToIOThread(nsIRunnable* aRunnable) +{ + MOZ_ASSERT(aRunnable); + + nsCOMPtr target + = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + MOZ_ASSERT(target); + + return target->Dispatch(aRunnable, NS_DISPATCH_NORMAL); +} + +// This runnable is used when an error value is set before doing any real +// operation on the I/O thread. In this case we skip all and we directly +// communicate the error. +class ErrorRunnable final : public CancelableRunnable +{ +public: + explicit ErrorRunnable(FileSystemTaskChildBase* aTask) + : mTask(aTask) { - mDoomed->Release(); + MOZ_ASSERT(aTask); + } + + NS_IMETHOD + Run() override + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mTask->HasError()); + + mTask->HandlerCallback(); return NS_OK; } private: - FileSystemBase* MOZ_OWNING_REF mDoomed; + RefPtr mTask; }; } // anonymous namespace -FileSystemTaskBase::FileSystemTaskBase(FileSystemBase* aFileSystem) +/** + * FileSystemTaskBase class + */ + +FileSystemTaskChildBase::FileSystemTaskChildBase(FileSystemBase* aFileSystem) : mErrorValue(NS_OK) , mFileSystem(aFileSystem) { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem, "aFileSystem should not be null."); + aFileSystem->AssertIsOnOwningThread(); } -FileSystemTaskBase::FileSystemTaskBase(FileSystemBase* aFileSystem, - const FileSystemParams& aParam, - FileSystemRequestParent* aParent) - : mErrorValue(NS_OK) - , mFileSystem(aFileSystem) - , mRequestParent(aParent) +FileSystemTaskChildBase::~FileSystemTaskChildBase() { - MOZ_ASSERT(XRE_IsParentProcess(), - "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem, "aFileSystem should not be null."); -} - -FileSystemTaskBase::~FileSystemTaskBase() -{ - if (!NS_IsMainThread()) { - RefPtr runnable = - new FileSystemReleaseRunnable(mFileSystem); - MOZ_ASSERT(!mFileSystem); - NS_DispatchToMainThread(runnable); - } + mFileSystem->AssertIsOnOwningThread(); } FileSystemBase* -FileSystemTaskBase::GetFileSystem() const +FileSystemTaskChildBase::GetFileSystem() const { + mFileSystem->AssertIsOnOwningThread(); return mFileSystem.get(); } void -FileSystemTaskBase::Start() +FileSystemTaskChildBase::Start() { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + mFileSystem->AssertIsOnOwningThread(); if (HasError()) { - NS_DispatchToMainThread(this); + // In this case we don't want to use IPC at all. + RefPtr runnable = new ErrorRunnable(this); + nsresult rv = NS_DispatchToCurrentThread(runnable); + NS_WARN_IF(NS_FAILED(rv)); return; } - if (XRE_IsParentProcess()) { - // Run in parent process. - // Start worker thread. - nsCOMPtr target - = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); - NS_ASSERTION(target, "Must have stream transport service."); - target->Dispatch(this, NS_DISPATCH_NORMAL); - return; - } - - // Run in child process. if (mFileSystem->IsShutdown()) { return; } @@ -117,72 +155,24 @@ FileSystemTaskBase::Start() // Retain a reference so the task object isn't deleted without IPDL's // knowledge. The reference will be released by - // mozilla::dom::ContentChild::DeallocPFileSystemRequestChild. + // mozilla::ipc::BackgroundChildImpl::DeallocPFileSystemRequestChild. NS_ADDREF_THIS(); - ContentChild::GetSingleton()->SendPFileSystemRequestConstructor(this, - params); -} + // If we are here, PBackground must be up and running, because Start() is + // called only by FileSystemPermissionRequest, and that class takes care of + // PBackground initialization. + PBackgroundChild* actor = + mozilla::ipc::BackgroundChild::GetForCurrentThread(); + MOZ_ASSERT(actor); -NS_IMETHODIMP -FileSystemTaskBase::Run() -{ - if (!NS_IsMainThread()) { - // Run worker thread tasks - nsresult rv = Work(); - if (NS_FAILED(rv)) { - SetError(rv); - } - // Dispatch itself to main thread - NS_DispatchToMainThread(this); - return NS_OK; - } - - // Run main thread tasks - HandleResult(); - return NS_OK; + actor->SendPFileSystemRequestConstructor(this, params); } void -FileSystemTaskBase::HandleResult() +FileSystemTaskChildBase::SetRequestResult(const FileSystemResponseValue& aValue) { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (mFileSystem->IsShutdown()) { - return; - } - if (mRequestParent && mRequestParent->IsRunning()) { - Unused << mRequestParent->Send__delete__(mRequestParent, - GetRequestResult()); - } else { - HandlerCallback(); - } -} + mFileSystem->AssertIsOnOwningThread(); -FileSystemResponseValue -FileSystemTaskBase::GetRequestResult() const -{ - MOZ_ASSERT(XRE_IsParentProcess(), - "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (!HasError()) { - ErrorResult rv; - FileSystemResponseValue value = GetSuccessRequestResult(rv); - if (NS_WARN_IF(rv.Failed())) { - return FileSystemErrorResponse(rv.StealNSResult()); - } - - return value; - } - - return FileSystemErrorResponse(mErrorValue); -} - -void -FileSystemTaskBase::SetRequestResult(const FileSystemResponseValue& aValue) -{ - MOZ_ASSERT(!XRE_IsParentProcess(), - "Only call from child process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); if (aValue.type() == FileSystemResponseValue::TFileSystemErrorResponse) { FileSystemErrorResponse r = aValue; mErrorValue = r.error(); @@ -194,61 +184,172 @@ FileSystemTaskBase::SetRequestResult(const FileSystemResponseValue& aValue) } bool -FileSystemTaskBase::Recv__delete__(const FileSystemResponseValue& aValue) +FileSystemTaskChildBase::Recv__delete__(const FileSystemResponseValue& aValue) { + mFileSystem->AssertIsOnOwningThread(); + SetRequestResult(aValue); HandlerCallback(); return true; } void -FileSystemTaskBase::SetError(const nsresult& aErrorValue) +FileSystemTaskChildBase::SetError(const nsresult& aErrorValue) { - uint16_t module = NS_ERROR_GET_MODULE(aErrorValue); - if (module == NS_ERROR_MODULE_DOM_FILESYSTEM || - module == NS_ERROR_MODULE_DOM_FILE || - module == NS_ERROR_MODULE_DOM) { - mErrorValue = aErrorValue; + mErrorValue = FileSystemErrorFromNsError(aErrorValue); +} + +/** + * FileSystemTaskParentBase class + */ + +FileSystemTaskParentBase::FileSystemTaskParentBase(FileSystemBase* aFileSystem, + const FileSystemParams& aParam, + FileSystemRequestParent* aParent) + : mErrorValue(NS_OK) + , mFileSystem(aFileSystem) + , mRequestParent(aParent) + , mBackgroundEventTarget(NS_GetCurrentThread()) +{ + MOZ_ASSERT(XRE_IsParentProcess(), + "Only call from parent process!"); + MOZ_ASSERT(aFileSystem, "aFileSystem should not be null."); + MOZ_ASSERT(aParent); + MOZ_ASSERT(mBackgroundEventTarget); + AssertIsOnBackgroundThread(); +} + +FileSystemTaskParentBase::~FileSystemTaskParentBase() +{ + // This task can be released on different threads because we dispatch it (as + // runnable) to main-thread, I/O and then back to the PBackground thread. + NS_ProxyRelease(mBackgroundEventTarget, mFileSystem.forget()); + NS_ProxyRelease(mBackgroundEventTarget, mRequestParent.forget()); +} + +void +FileSystemTaskParentBase::Start() +{ + AssertIsOnBackgroundThread(); + mFileSystem->AssertIsOnOwningThread(); + + if (NeedToGoToMainThread()) { + nsresult rv = NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL); + NS_WARN_IF(NS_FAILED(rv)); return; } - switch (aErrorValue) { - case NS_OK: - mErrorValue = NS_OK; - return; + nsresult rv = DispatchToIOThread(this); + NS_WARN_IF(NS_FAILED(rv)); +} - case NS_ERROR_FILE_INVALID_PATH: - case NS_ERROR_FILE_UNRECOGNIZED_PATH: - mErrorValue = NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR; - return; +void +FileSystemTaskParentBase::HandleResult() +{ + AssertIsOnBackgroundThread(); + mFileSystem->AssertIsOnOwningThread(); - case NS_ERROR_FILE_DESTINATION_NOT_DIR: - mErrorValue = NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR; - return; - - case NS_ERROR_FILE_ACCESS_DENIED: - case NS_ERROR_FILE_DIR_NOT_EMPTY: - mErrorValue = NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR; - return; - - case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST: - case NS_ERROR_NOT_AVAILABLE: - mErrorValue = NS_ERROR_DOM_FILE_NOT_FOUND_ERR; - return; - - case NS_ERROR_FILE_ALREADY_EXISTS: - mErrorValue = NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR; - return; - - case NS_ERROR_FILE_NOT_DIRECTORY: - mErrorValue = NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR; - return; - - case NS_ERROR_UNEXPECTED: - default: - mErrorValue = NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR; - return; + if (mFileSystem->IsShutdown()) { + return; } + + MOZ_ASSERT(mRequestParent); + Unused << mRequestParent->Send__delete__(mRequestParent, GetRequestResult()); +} + +FileSystemResponseValue +FileSystemTaskParentBase::GetRequestResult() const +{ + AssertIsOnBackgroundThread(); + mFileSystem->AssertIsOnOwningThread(); + + if (HasError()) { + return FileSystemErrorResponse(mErrorValue); + } + + ErrorResult rv; + FileSystemResponseValue value = GetSuccessRequestResult(rv); + if (NS_WARN_IF(rv.Failed())) { + return FileSystemErrorResponse(rv.StealNSResult()); + } + + return value; +} + +void +FileSystemTaskParentBase::SetError(const nsresult& aErrorValue) +{ + mErrorValue = FileSystemErrorFromNsError(aErrorValue); +} + +bool +FileSystemTaskParentBase::NeedToGoToMainThread() const +{ + return mFileSystem->NeedToGoToMainThread(); +} + +nsresult +FileSystemTaskParentBase::MainThreadWork() +{ + MOZ_ASSERT(NS_IsMainThread()); + return mFileSystem->MainThreadWork(); +} + +NS_IMETHODIMP +FileSystemTaskParentBase::Run() +{ + // This method can run in 3 different threads. Here why: + // 1. if we are on the main-thread it's because the task must do something + // here. If no errors are returned we go the step 2. + // 2. We can be here directly if the task doesn't have nothing to do on the + // main-thread. We are are on the I/O thread and we call IOWork(). + // 3. Both step 1 (in case of error) and step 2 end up here where return the + // value back to the PBackground thread. + if (NS_IsMainThread()) { + MOZ_ASSERT(NeedToGoToMainThread()); + + nsresult rv = MainThreadWork(); + if (NS_WARN_IF(NS_FAILED(rv))) { + SetError(rv); + + // Something when wrong. Let's go to the Background thread directly + // skipping the I/O thread step. + rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // Next step must happen on the I/O thread. + rv = DispatchToIOThread(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + // Run I/O thread tasks + if (!IsOnBackgroundThread()) { + nsresult rv = IOWork(); + if (NS_WARN_IF(NS_FAILED(rv))) { + SetError(rv); + } + + // Let's go back to PBackground thread to finish the work. + rv = mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; + } + + // If we are here, it's because the I/O work has been done and we have to + // handle the result back via IPC. + AssertIsOnBackgroundThread(); + HandleResult(); + return NS_OK; } } // namespace dom diff --git a/dom/filesystem/FileSystemTaskBase.h b/dom/filesystem/FileSystemTaskBase.h index e769d6082d..b3a6e95c48 100644 --- a/dom/filesystem/FileSystemTaskBase.h +++ b/dom/filesystem/FileSystemTaskBase.h @@ -10,111 +10,118 @@ #include "mozilla/ErrorResult.h" #include "mozilla/dom/FileSystemRequestParent.h" #include "mozilla/dom/PFileSystemRequestChild.h" +#include "nsThreadUtils.h" namespace mozilla { namespace dom { class BlobImpl; -class BlobParent; class FileSystemBase; class FileSystemParams; +class PBlobParent; /* * The base class to implement a Task class. - * The task is used to handle the OOP (out of process) operations. - * The file system operations can only be performed in the parent process. When - * performing such a parent-process-only operation, a task will delivered the - * operation to the parent process if needed. + * The file system operations can only be performed in the parent process. In + * order to avoid duplicated code, we used PBackground for child-parent and + * parent-parent communications. * * The following diagram illustrates the how a API call from the content page * starts a task and gets call back results. * - * The left block is the call sequence inside the child process, while the - * right block is the call sequence inside the parent process. + * The left block is the call sequence inside any process loading content, while + * the right block is the call sequence only inside the parent process. * - * There are two types of API call. One is from the content page of the child - * process and we mark the steps as (1) to (8). The other is from the content - * page of the parent process and we mark the steps as (1') to (4'). + * Page + * | + * | (1) + * ______|_______________________ | __________________________________ + * | | | | | | + * | | | | | | + * | V | IPC | PBackground thread on | + * | [new FileSystemTaskChildBase()] | | | the parent process | + * | | | | | | + * | | (2) | | | | + * | V | | | | + * | [FileSystemPermissionRequest------------------\ | + * | ::RequestForTask()] <------------------------/ | + * | | | | | | + * | | (3) | | | + * | V | (4) | | + * | [GetRequestParams]------------------->[new FileSystemTaskParentBase()] | + * | | | | | + * | | | | | (5) _____________ | + * | | | | | | | | + * | | | | | | I/O Thread | | + * | | | | | | | | + * | | | | ---------> [IOWork] | | + * | | IPC | | | | | + * | | | | | | (6) | | + * | | | | -------------- | | + * | | | | | |_____________| | + * | | | | | | + * | | | | V | + * | | | | [HandleResult] | + * | | | | | | + * | | | | (7) | + * | | (8) | V | + * | [SetRequestResult]<---------------------[GetRequestResult] | + * | | | | | + * | | (9) | | | | + * | V | | | | + * |[HandlerCallback] | IPC | | + * |_______|_________________________| | |_________________________________| + * | | + * V + * Page * - * Page Page - * | | - * | (1) | (1') - * ______|________________ | _____________________|_____________ - * | | | | | | | - * | | Task in | | | Task in | | - * | | Child Process | | | Parent Process | | - * | V | IPC | V | - * [new FileSystemTaskBase()] | | [new FileSystemTaskBase()] | - * | | | | | | | - * | | (2) | | | (2') | - * | V | (3) | | | - * | [GetRequestParams]------------->[new FileSystemTaskBase(...)] | - * | | | | | | - * | | | | | (4) | | - * | | | | | V | - * | | | | -----------> [Work] | - * | | IPC | | | - * | | | | (5) | (3') | - * | | | | V | - * | | | | --------[HandleResult] | - * | | | | | | | - * | | | | (6) | | - * | | (7) | V | | - * | [SetRequestResult]<-------------[GetRequestResult] | | - * | | | | | (4') | - * | | (8) | | | | | - * | V | | | V | - * |[HandlerCallback] | IPC | [HandlerCallback] | - * |_______|_______________| | |_________________________|_________| - * | | | - * V V - * Page Page - * - * 1. From child process page - * Child: + * 1. From the process that is handling the request + * Child/Parent (it can be in any process): * (1) Call FileSystem API from content page with JS. Create a task and run. - * The base constructor [FileSystemTaskBase()] of the task should be called. - * (2) Forward the task to the parent process through the IPC and call + * The base constructor [FileSystemTaskChildBase()] of the task should be + * called. + * (2) The FileSystemTaskChildBase object is given to + * [FileSystemPermissionRequest::RequestForTask()] that will perform a + * permission check step if needed (See ePermissionCheckType enum). The real + * operation is done on the parent process but it's hidden by + * [nsContentPermissionUtils::AskPermission()]. If the permission check is not + * needed or if the page has the right permission, the + * FileSystemPermissionRequest will start the task (only once PBackground + * actor is fully initialized). + * (3) Forward the task to the parent process through the IPC and call * [GetRequestParams] to prepare the parameters of the IPC. * Parent: - * (3) The parent process receives IPC and handle it in - * FileystemRequestParent. - * Get the IPC parameters and create a task to run the IPC task. The base - * constructor [FileSystemTaskBase(aParam, aParent)] of the task should be - * called to set the task as an IPC task. - * (4) The task operation will be performed in the member function of [Work]. - * A worker thread will be created to run that function. If error occurs + * (4) The parent process receives IPC and handle it in + * FileystemRequestParent. Get the IPC parameters and create a task to run the + * IPC task. The base constructor [FileSystemTaskParentBase(aParam, aParent)] + * For security reasons, we do an additional permission check if needed. In + * the check fails, the child process will be killed. + * of the task should be called to set the task as an IPC task. + * (5) The task operation will be performed in the member function of [IOWork]. + * A I/O thread will be created to run that function. If error occurs * during the operation, call [SetError] to record the error and then abort. - * (5) After finishing the task operation, call [HandleResult] to send the + * (6) After finishing the task operation, call [HandleResult] to send the * result back to the child process though the IPC. - * (6) Call [GetRequestResult] request result to prepare the parameters of the + * (7) Call [GetRequestResult] request result to prepare the parameters of the * IPC. Because the formats of the error result for different task are the - * same, FileSystemTaskBase can handle the error message without interfering. + * same, FileSystemTaskChildBase can handle the error message without + * interfering. * Each task only needs to implement its specific success result preparation * function -[GetSuccessRequestResult]. - * Child: - * (7) The child process receives IPC and calls [SetRequestResult] to get the + * Child/Parent: + * (8) The process receives IPC and calls [SetRequestResult] to get the * task result. Each task needs to implement its specific success result * parsing function [SetSuccessRequestResult] to get the success result. - * (8) Call [HandlerCallback] to send the task result to the content page. - * 2. From parent process page - * We don't need to send the task parameters and result to other process. So - * there are less steps, but their functions are the same. The correspondence - * between the two types of steps is: - * (1') = (1), - * (2') = (4), - * (3') = (5), - * (4') = (8). + * (9) Call [HandlerCallback] to send the task result to the content page. */ -class FileSystemTaskBase - : public nsRunnable - , public PFileSystemRequestChild +class FileSystemTaskChildBase : public PFileSystemRequestChild { public: + NS_INLINE_DECL_REFCOUNTING(FileSystemTaskChildBase) + /* - * Start the task. If the task is running the child process, it will be - * forwarded to parent process by IPC, or else, creates a worker thread to - * do the task work. + * Start the task. It will dispatch all the information to the parent process, + * PBackground thread. This method must be called from the owning thread. */ void Start(); @@ -135,104 +142,157 @@ public: virtual void GetPermissionAccessType(nsCString& aAccess) const = 0; - NS_DECL_NSIRUNNABLE -protected: - /* - * To create a task to handle the page content request. - */ - explicit FileSystemTaskBase(FileSystemBase* aFileSystem); - - /* - * To create a parent process task delivered from the child process through - * IPC. - */ - FileSystemTaskBase(FileSystemBase* aFileSystem, - const FileSystemParams& aParam, - FileSystemRequestParent* aParent); - - virtual - ~FileSystemTaskBase(); - - /* - * The function to perform task operation. It will be run on the worker - * thread of the parent process. - * Overrides this function to define the task operation for individual task. - */ - virtual nsresult - Work() = 0; - /* * After the task is completed, this function will be called to pass the task - * result to the content page. + * result to the content page. This method is called in the owning thread. * Override this function to handle the call back to the content page. */ virtual void HandlerCallback() = 0; + bool + HasError() const { return NS_FAILED(mErrorValue); } + +protected: + /* + * To create a task to handle the page content request. + */ + explicit FileSystemTaskChildBase(FileSystemBase* aFileSystem); + + virtual + ~FileSystemTaskChildBase(); + /* * Wrap the task parameter to FileSystemParams for sending it through IPC. * It will be called when we need to forward a task from the child process to - * the prarent process. + * the parent process. This method runs in the owning thread. * @param filesystem The string representation of the file system. */ virtual FileSystemParams GetRequestParams(const nsString& aSerializedDOMPath, ErrorResult& aRv) const = 0; + /* + * Unwrap the IPC message to get the task success result. + * It will be called when the task is completed successfully and an IPC + * message is received in the child process and we want to get the task + * success result. This method runs in the owning thread. + */ + virtual void + SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) = 0; + + // Overrides PFileSystemRequestChild + virtual bool + Recv__delete__(const FileSystemResponseValue& value) override; + + nsresult mErrorValue; + RefPtr mFileSystem; + +private: + + /* + * Unwrap the IPC message to get the task result. + * It will be called when the task is completed and an IPC message is received + * in the content process and we want to get the task result. This runs on the + * owning thread. + */ + void + SetRequestResult(const FileSystemResponseValue& aValue); +}; + +// This class is the 'alter ego' of FileSystemTaskChildBase in the PBackground +// world. +class FileSystemTaskParentBase : public nsRunnable +{ +public: + /* + * Start the task. This must be called from the PBackground thread only. + */ + void + Start(); + + /* + * The error codes are defined in xpcom/base/ErrorList.h and their + * corresponding error name and message are defined in dom/base/domerr.msg. + */ + void + SetError(const nsresult& aErrorCode); + + /* + * The function to perform task operation. It will be run on the I/O + * thread of the parent process. + * Overrides this function to define the task operation for individual task. + */ + virtual nsresult + IOWork() = 0; + /* * Wrap the task success result to FileSystemResponseValue for sending it - * through IPC. + * through IPC. This method runs in the PBackground thread. * It will be called when the task is completed successfully and we need to * send the task success result back to the child process. */ virtual FileSystemResponseValue GetSuccessRequestResult(ErrorResult& aRv) const = 0; - /* - * Unwrap the IPC message to get the task success result. - * It will be called when the task is completed successfully and an IPC - * message is received in the child process and we want to get the task - * success result. - */ - virtual void - SetSuccessRequestResult(const FileSystemResponseValue& aValue, - ErrorResult& aRv) = 0; - - bool - HasError() const { return mErrorValue != NS_OK; } - - // Overrides PFileSystemRequestChild - virtual bool - Recv__delete__(const FileSystemResponseValue& value) override; - - nsresult mErrorValue; - - RefPtr mFileSystem; - RefPtr mRequestParent; -private: /* * After finishing the task operation, handle the task result. - * If it is an IPC task, send back the IPC result. Or else, send the result - * to the content page. + * If it is an IPC task, send back the IPC result. It runs on the PBackground + * thread. */ void HandleResult(); + // If this task must do something on the main-thread before IOWork(), it must + // overwrite this method. Otherwise it returns true if the FileSystem must be + // initialized on the main-thread. It's called from the Background thread. + virtual bool + NeedToGoToMainThread() const; + + // This method is called only if NeedToGoToMainThread() returns true. + // Of course, it runs on the main-thread. + virtual nsresult + MainThreadWork(); + + /* + * Get the type of permission access required to perform this task. + */ + virtual void + GetPermissionAccessType(nsCString& aAccess) const = 0; + + bool + HasError() const { return NS_FAILED(mErrorValue); } + + NS_IMETHOD + Run() override; + +private: /* * Wrap the task result to FileSystemResponseValue for sending it through IPC. * It will be called when the task is completed and we need to - * send the task result back to the child process. + * send the task result back to the content. This runs on the PBackground + * thread. */ FileSystemResponseValue GetRequestResult() const; +protected: /* - * Unwrap the IPC message to get the task result. - * It will be called when the task is completed and an IPC message is received - * in the child process and we want to get the task result. + * To create a parent process task delivered from the child process through + * IPC. */ - void - SetRequestResult(const FileSystemResponseValue& aValue); + FileSystemTaskParentBase(FileSystemBase* aFileSystem, + const FileSystemParams& aParam, + FileSystemRequestParent* aParent); + + virtual + ~FileSystemTaskParentBase(); + + nsresult mErrorValue; + RefPtr mFileSystem; + RefPtr mRequestParent; + nsCOMPtr mBackgroundEventTarget; }; } // namespace dom diff --git a/dom/filesystem/GetDirectoryListingTask.cpp b/dom/filesystem/GetDirectoryListingTask.cpp index 07b4695934..793ece18a3 100644 --- a/dom/filesystem/GetDirectoryListingTask.cpp +++ b/dom/filesystem/GetDirectoryListingTask.cpp @@ -11,27 +11,34 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" +#include "mozilla/dom/PFileSystemParams.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" #include "nsIFile.h" #include "nsStringGlue.h" +#define GET_DIRECTORY_LISTING_PERMISSION "read" + namespace mozilla { namespace dom { -/* static */ already_AddRefed -GetDirectoryListingTask::Create(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - Directory::DirectoryType aType, - const nsAString& aFilters, - ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); +/** + * GetDirectoryListingTaskChild + */ - RefPtr task = - new GetDirectoryListingTask(aFileSystem, aTargetPath, aType, aFilters); +/* static */ already_AddRefed +GetDirectoryListingTaskChild::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + const nsAString& aFilters, + ErrorResult& aRv) +{ + MOZ_ASSERT(aFileSystem); + aFileSystem->AssertIsOnOwningThread(); + + RefPtr task = + new GetDirectoryListingTaskChild(aFileSystem, aTargetPath, aType, aFilters); // aTargetPath can be null. In this case SetError will be called. @@ -50,72 +57,36 @@ GetDirectoryListingTask::Create(FileSystemBase* aFileSystem, return task.forget(); } -/* static */ already_AddRefed -GetDirectoryListingTask::Create(FileSystemBase* aFileSystem, - const FileSystemGetDirectoryListingParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv) -{ - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); - - RefPtr task = - new GetDirectoryListingTask(aFileSystem, aParam, aParent); - - NS_ConvertUTF16toUTF8 path(aParam.realPath()); - aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - task->mType = aParam.isRoot() - ? Directory::eDOMRootDirectory : Directory::eNotDOMRootDirectory; - return task.forget(); -} - -GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - Directory::DirectoryType aType, - const nsAString& aFilters) - : FileSystemTaskBase(aFileSystem) +GetDirectoryListingTaskChild::GetDirectoryListingTaskChild(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + const nsAString& aFilters) + : FileSystemTaskChildBase(aFileSystem) , mTargetPath(aTargetPath) , mFilters(aFilters) , mType(aType) { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); + aFileSystem->AssertIsOnOwningThread(); } -GetDirectoryListingTask::GetDirectoryListingTask(FileSystemBase* aFileSystem, - const FileSystemGetDirectoryListingParams& aParam, - FileSystemRequestParent* aParent) - : FileSystemTaskBase(aFileSystem, aParam, aParent) - , mFilters(aParam.filters()) +GetDirectoryListingTaskChild::~GetDirectoryListingTaskChild() { - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); -} - -GetDirectoryListingTask::~GetDirectoryListingTask() -{ - MOZ_ASSERT(!mPromise || NS_IsMainThread(), - "mPromise should be released on main thread!"); + mFileSystem->AssertIsOnOwningThread(); } already_AddRefed -GetDirectoryListingTask::GetPromise() +GetDirectoryListingTaskChild::GetPromise() { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + mFileSystem->AssertIsOnOwningThread(); return RefPtr(mPromise).forget(); } FileSystemParams -GetDirectoryListingTask::GetRequestParams(const nsString& aSerializedDOMPath, - ErrorResult& aRv) const +GetDirectoryListingTaskChild::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + mFileSystem->AssertIsOnOwningThread(); nsAutoString path; aRv = mTargetPath->GetPath(path); @@ -128,38 +99,11 @@ GetDirectoryListingTask::GetRequestParams(const nsString& aSerializedDOMPath, mFilters); } -FileSystemResponseValue -GetDirectoryListingTask::GetSuccessRequestResult(ErrorResult& aRv) const -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - - InfallibleTArray blobs; - - nsTArray inputs; - - for (unsigned i = 0; i < mTargetData.Length(); i++) { - if (mTargetData[i].mType == Directory::FileOrDirectoryPath::eFilePath) { - FileSystemDirectoryListingResponseFile fileData; - fileData.fileRealPath() = mTargetData[i].mPath; - inputs.AppendElement(fileData); - } else { - MOZ_ASSERT(mTargetData[i].mType == Directory::FileOrDirectoryPath::eDirectoryPath); - FileSystemDirectoryListingResponseDirectory directoryData; - directoryData.directoryRealPath() = mTargetData[i].mPath; - inputs.AppendElement(directoryData); - } - } - - FileSystemDirectoryListingResponse response; - response.data().SwapElements(inputs); - return response; -} - void -GetDirectoryListingTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, - ErrorResult& aRv) +GetDirectoryListingTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + mFileSystem->AssertIsOnOwningThread(); MOZ_ASSERT(aValue.type() == FileSystemResponseValue::TFileSystemDirectoryListingResponse); @@ -186,8 +130,153 @@ GetDirectoryListingTask::SetSuccessRequestResult(const FileSystemResponseValue& } } +void +GetDirectoryListingTaskChild::HandlerCallback() +{ + mFileSystem->AssertIsOnOwningThread(); + + if (mFileSystem->IsShutdown()) { + mPromise = nullptr; + return; + } + + if (HasError()) { + mPromise->MaybeReject(mErrorValue); + mPromise = nullptr; + return; + } + + size_t count = mTargetData.Length(); + + Sequence listing; + + if (!listing.SetLength(count, mozilla::fallible_t())) { + mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); + mPromise = nullptr; + return; + } + + for (unsigned i = 0; i < count; i++) { + nsCOMPtr path; + NS_ConvertUTF16toUTF8 fullPath(mTargetData[i].mPath); + nsresult rv = NS_NewNativeLocalFile(fullPath, true, getter_AddRefs(path)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPromise->MaybeReject(rv); + mPromise = nullptr; + return; + } + +#ifdef DEBUG + nsCOMPtr rootPath; + rv = NS_NewLocalFile(mFileSystem->LocalOrDeviceStorageRootPath(), false, + getter_AddRefs(rootPath)); + if (NS_WARN_IF(NS_FAILED(rv))) { + mPromise->MaybeReject(rv); + mPromise = nullptr; + return; + } + + MOZ_ASSERT(FileSystemUtils::IsDescendantPath(rootPath, path)); +#endif + + if (mTargetData[i].mType == Directory::FileOrDirectoryPath::eDirectoryPath) { + RefPtr directory = + Directory::Create(mFileSystem->GetParentObject(), path, + Directory::eNotDOMRootDirectory, mFileSystem); + MOZ_ASSERT(directory); + + // Propogate mFilter onto sub-Directory object: + directory->SetContentFilters(mFilters); + listing[i].SetAsDirectory() = directory; + } else { + MOZ_ASSERT(mTargetData[i].mType == Directory::FileOrDirectoryPath::eFilePath); + + RefPtr file = + File::CreateFromFile(mFileSystem->GetParentObject(), path); + MOZ_ASSERT(file); + + listing[i].SetAsFile() = file; + } + } + + mPromise->MaybeResolve(listing); + mPromise = nullptr; +} + +void +GetDirectoryListingTaskChild::GetPermissionAccessType(nsCString& aAccess) const +{ + aAccess.AssignLiteral(GET_DIRECTORY_LISTING_PERMISSION); +} + +/** + * GetDirectoryListingTaskParent + */ + +/* static */ already_AddRefed +GetDirectoryListingTaskParent::Create(FileSystemBase* aFileSystem, + const FileSystemGetDirectoryListingParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new GetDirectoryListingTaskParent(aFileSystem, aParam, aParent); + + NS_ConvertUTF16toUTF8 path(aParam.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + task->mType = aParam.isRoot() + ? Directory::eDOMRootDirectory : Directory::eNotDOMRootDirectory; + return task.forget(); +} + +GetDirectoryListingTaskParent::GetDirectoryListingTaskParent(FileSystemBase* aFileSystem, + const FileSystemGetDirectoryListingParams& aParam, + FileSystemRequestParent* aParent) + : FileSystemTaskParentBase(aFileSystem, aParam, aParent) + , mFilters(aParam.filters()) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); +} + +FileSystemResponseValue +GetDirectoryListingTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const +{ + AssertIsOnBackgroundThread(); + + InfallibleTArray blobs; + + nsTArray inputs; + + for (unsigned i = 0; i < mTargetData.Length(); i++) { + if (mTargetData[i].mType == Directory::FileOrDirectoryPath::eFilePath) { + FileSystemDirectoryListingResponseFile fileData; + fileData.fileRealPath() = mTargetData[i].mPath; + inputs.AppendElement(fileData); + } else { + MOZ_ASSERT(mTargetData[i].mType == Directory::FileOrDirectoryPath::eDirectoryPath); + FileSystemDirectoryListingResponseDirectory directoryData; + directoryData.directoryRealPath() = mTargetData[i].mPath; + inputs.AppendElement(directoryData); + } + } + + FileSystemDirectoryListingResponse response; + response.data().SwapElements(inputs); + return response; +} + nsresult -GetDirectoryListingTask::Work() +GetDirectoryListingTaskParent::IOWork() { MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); @@ -303,81 +392,9 @@ GetDirectoryListingTask::Work() } void -GetDirectoryListingTask::HandlerCallback() +GetDirectoryListingTaskParent::GetPermissionAccessType(nsCString& aAccess) const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (mFileSystem->IsShutdown()) { - mPromise = nullptr; - return; - } - - if (HasError()) { - mPromise->MaybeReject(mErrorValue); - mPromise = nullptr; - return; - } - - size_t count = mTargetData.Length(); - - Sequence listing; - - if (!listing.SetLength(count, mozilla::fallible_t())) { - mPromise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); - mPromise = nullptr; - return; - } - - for (unsigned i = 0; i < count; i++) { - nsCOMPtr path; - NS_ConvertUTF16toUTF8 fullPath(mTargetData[i].mPath); - nsresult rv = NS_NewNativeLocalFile(fullPath, true, getter_AddRefs(path)); - if (NS_WARN_IF(NS_FAILED(rv))) { - mPromise->MaybeReject(rv); - mPromise = nullptr; - return; - } - -#ifdef DEBUG - nsCOMPtr rootPath; - rv = NS_NewLocalFile(mFileSystem->LocalOrDeviceStorageRootPath(), false, - getter_AddRefs(rootPath)); - if (NS_WARN_IF(NS_FAILED(rv))) { - mPromise->MaybeReject(rv); - mPromise = nullptr; - return; - } - - MOZ_ASSERT(FileSystemUtils::IsDescendantPath(rootPath, path)); -#endif - - if (mTargetData[i].mType == Directory::FileOrDirectoryPath::eDirectoryPath) { - RefPtr directory = - Directory::Create(mFileSystem->GetParentObject(), path, - Directory::eNotDOMRootDirectory, mFileSystem); - MOZ_ASSERT(directory); - - // Propogate mFilter onto sub-Directory object: - directory->SetContentFilters(mFilters); - listing[i].SetAsDirectory() = directory; - } else { - MOZ_ASSERT(mTargetData[i].mType == Directory::FileOrDirectoryPath::eFilePath); - - RefPtr file = - File::CreateFromFile(mFileSystem->GetParentObject(), path); - MOZ_ASSERT(file); - - listing[i].SetAsFile() = file; - } - } - - mPromise->MaybeResolve(listing); - mPromise = nullptr; -} - -void -GetDirectoryListingTask::GetPermissionAccessType(nsCString& aAccess) const -{ - aAccess.AssignLiteral("read"); + aAccess.AssignLiteral(GET_DIRECTORY_LISTING_PERMISSION); } } // namespace dom diff --git a/dom/filesystem/GetDirectoryListingTask.h b/dom/filesystem/GetDirectoryListingTask.h index 2b75867958..50c15ec0f4 100644 --- a/dom/filesystem/GetDirectoryListingTask.h +++ b/dom/filesystem/GetDirectoryListingTask.h @@ -17,24 +17,18 @@ namespace dom { class BlobImpl; -class GetDirectoryListingTask final : public FileSystemTaskBase +class GetDirectoryListingTaskChild final : public FileSystemTaskChildBase { public: - static already_AddRefed + static already_AddRefed Create(FileSystemBase* aFileSystem, nsIFile* aTargetPath, Directory::DirectoryType aType, const nsAString& aFilters, ErrorResult& aRv); - static already_AddRefed - Create(FileSystemBase* aFileSystem, - const FileSystemGetDirectoryListingParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv); - virtual - ~GetDirectoryListingTask(); + ~GetDirectoryListingTaskChild(); already_AddRefed GetPromise(); @@ -44,29 +38,19 @@ public: private: // If aDirectoryOnly is set, we should ensure that the target is a directory. - GetDirectoryListingTask(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - Directory::DirectoryType aType, - const nsAString& aFilters); - - GetDirectoryListingTask(FileSystemBase* aFileSystem, - const FileSystemGetDirectoryListingParams& aParam, - FileSystemRequestParent* aParent); + GetDirectoryListingTaskChild(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + const nsAString& aFilters); virtual FileSystemParams GetRequestParams(const nsString& aSerializedDOMPath, ErrorResult& aRv) const override; - virtual FileSystemResponseValue - GetSuccessRequestResult(ErrorResult& aRv) const override; - virtual void SetSuccessRequestResult(const FileSystemResponseValue& aValue, ErrorResult& aRv) override; - virtual nsresult - Work() override; - virtual void HandlerCallback() override; @@ -80,6 +64,38 @@ private: FallibleTArray mTargetData; }; +class GetDirectoryListingTaskParent final : public FileSystemTaskParentBase +{ +public: + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemGetDirectoryListingParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); + + virtual void + GetPermissionAccessType(nsCString& aAccess) const override; + +private: + GetDirectoryListingTaskParent(FileSystemBase* aFileSystem, + const FileSystemGetDirectoryListingParams& aParam, + FileSystemRequestParent* aParent); + + virtual FileSystemResponseValue + GetSuccessRequestResult(ErrorResult& aRv) const override; + + virtual nsresult + IOWork() override; + + nsCOMPtr mTargetPath; + nsString mFilters; + Directory::DirectoryType mType; + + // We cannot store File or Directory objects bacause this object is created + // on a different thread and File and Directory are not thread-safe. + FallibleTArray mTargetData; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/GetFileOrDirectoryTask.cpp b/dom/filesystem/GetFileOrDirectoryTask.cpp index c3c0f6d1cc..e45a81b580 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.cpp +++ b/dom/filesystem/GetFileOrDirectoryTask.cpp @@ -10,27 +10,35 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" +#include "mozilla/dom/PFileSystemParams.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" #include "nsIFile.h" #include "nsStringGlue.h" +#define GET_FILE_OR_DIRECTORY_PERMISSION "read" + namespace mozilla { namespace dom { -/* static */ already_AddRefed -GetFileOrDirectoryTask::Create(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - Directory::DirectoryType aType, - bool aDirectoryOnly, - ErrorResult& aRv) +/** + * GetFileOrDirectoryTaskChild + */ + +/* static */ already_AddRefed +GetFileOrDirectoryTaskChild::Create(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + bool aDirectoryOnly, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); - RefPtr task = - new GetFileOrDirectoryTask(aFileSystem, aTargetPath, aType, aDirectoryOnly); + RefPtr task = + new GetFileOrDirectoryTaskChild(aFileSystem, aTargetPath, aType, + aDirectoryOnly); // aTargetPath can be null. In this case SetError will be called. @@ -49,35 +57,11 @@ GetFileOrDirectoryTask::Create(FileSystemBase* aFileSystem, return task.forget(); } -/* static */ already_AddRefed -GetFileOrDirectoryTask::Create(FileSystemBase* aFileSystem, - const FileSystemGetFileOrDirectoryParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv) -{ - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); - - RefPtr task = - new GetFileOrDirectoryTask(aFileSystem, aParam, aParent); - - NS_ConvertUTF16toUTF8 path(aParam.realPath()); - aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - task->mType = aParam.isRoot() - ? Directory::eDOMRootDirectory : Directory::eNotDOMRootDirectory; - return task.forget(); -} - -GetFileOrDirectoryTask::GetFileOrDirectoryTask(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - Directory::DirectoryType aType, - bool aDirectoryOnly) - : FileSystemTaskBase(aFileSystem) +GetFileOrDirectoryTaskChild::GetFileOrDirectoryTaskChild(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + bool aDirectoryOnly) + : FileSystemTaskChildBase(aFileSystem) , mTargetPath(aTargetPath) , mIsDirectory(aDirectoryOnly) , mType(aType) @@ -86,33 +70,21 @@ GetFileOrDirectoryTask::GetFileOrDirectoryTask(FileSystemBase* aFileSystem, MOZ_ASSERT(aFileSystem); } -GetFileOrDirectoryTask::GetFileOrDirectoryTask(FileSystemBase* aFileSystem, - const FileSystemGetFileOrDirectoryParams& aParam, - FileSystemRequestParent* aParent) - : FileSystemTaskBase(aFileSystem, aParam, aParent) - , mIsDirectory(false) +GetFileOrDirectoryTaskChild::~GetFileOrDirectoryTaskChild() { - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); -} - -GetFileOrDirectoryTask::~GetFileOrDirectoryTask() -{ - MOZ_ASSERT(!mPromise || NS_IsMainThread(), - "mPromise should be released on main thread!"); + MOZ_ASSERT(NS_IsMainThread()); } already_AddRefed -GetFileOrDirectoryTask::GetPromise() +GetFileOrDirectoryTaskChild::GetPromise() { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); return RefPtr(mPromise).forget(); } FileSystemParams -GetFileOrDirectoryTask::GetRequestParams(const nsString& aSerializedDOMPath, - ErrorResult& aRv) const +GetFileOrDirectoryTaskChild::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); @@ -126,26 +98,9 @@ GetFileOrDirectoryTask::GetRequestParams(const nsString& aSerializedDOMPath, mType == Directory::eDOMRootDirectory); } -FileSystemResponseValue -GetFileOrDirectoryTask::GetSuccessRequestResult(ErrorResult& aRv) const -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - nsAutoString path; - aRv = mTargetPath->GetPath(path); - if (NS_WARN_IF(aRv.Failed())) { - return FileSystemDirectoryResponse(); - } - - if (mIsDirectory) { - return FileSystemDirectoryResponse(path); - } - - return FileSystemFileResponse(path); -} - void -GetFileOrDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, - ErrorResult& aRv) +GetFileOrDirectoryTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); switch (aValue.type()) { @@ -180,8 +135,104 @@ GetFileOrDirectoryTask::SetSuccessRequestResult(const FileSystemResponseValue& a } } +void +GetFileOrDirectoryTaskChild::HandlerCallback() +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + if (mFileSystem->IsShutdown()) { + mPromise = nullptr; + return; + } + + if (HasError()) { + mPromise->MaybeReject(mErrorValue); + mPromise = nullptr; + return; + } + + if (mIsDirectory) { + RefPtr dir = Directory::Create(mFileSystem->GetParentObject(), + mTargetPath, + mType, + mFileSystem); + MOZ_ASSERT(dir); + + mPromise->MaybeResolve(dir); + mPromise = nullptr; + return; + } + + RefPtr file = File::CreateFromFile(mFileSystem->GetParentObject(), + mTargetPath); + mPromise->MaybeResolve(file); + mPromise = nullptr; +} + +void +GetFileOrDirectoryTaskChild::GetPermissionAccessType(nsCString& aAccess) const +{ + aAccess.AssignLiteral(GET_FILE_OR_DIRECTORY_PERMISSION); +} + +/** + * GetFileOrDirectoryTaskParent + */ + +/* static */ already_AddRefed +GetFileOrDirectoryTaskParent::Create(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new GetFileOrDirectoryTaskParent(aFileSystem, aParam, aParent); + + NS_ConvertUTF16toUTF8 path(aParam.realPath()); + aRv = NS_NewNativeLocalFile(path, true, getter_AddRefs(task->mTargetPath)); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + task->mType = aParam.isRoot() + ? Directory::eDOMRootDirectory : Directory::eNotDOMRootDirectory; + return task.forget(); +} + +GetFileOrDirectoryTaskParent::GetFileOrDirectoryTaskParent(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent) + : FileSystemTaskParentBase(aFileSystem, aParam, aParent) + , mIsDirectory(false) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); +} + +FileSystemResponseValue +GetFileOrDirectoryTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const +{ + AssertIsOnBackgroundThread(); + + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return FileSystemDirectoryResponse(); + } + + if (mIsDirectory) { + return FileSystemDirectoryResponse(path); + } + + return FileSystemFileResponse(path); +} + nsresult -GetFileOrDirectoryTask::Work() +GetFileOrDirectoryTaskParent::IOWork() { MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); @@ -245,42 +296,9 @@ GetFileOrDirectoryTask::Work() } void -GetFileOrDirectoryTask::HandlerCallback() +GetFileOrDirectoryTaskParent::GetPermissionAccessType(nsCString& aAccess) const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (mFileSystem->IsShutdown()) { - mPromise = nullptr; - return; - } - - if (HasError()) { - mPromise->MaybeReject(mErrorValue); - mPromise = nullptr; - return; - } - - if (mIsDirectory) { - RefPtr dir = Directory::Create(mFileSystem->GetParentObject(), - mTargetPath, - mType, - mFileSystem); - MOZ_ASSERT(dir); - - mPromise->MaybeResolve(dir); - mPromise = nullptr; - return; - } - - RefPtr file = File::CreateFromFile(mFileSystem->GetParentObject(), - mTargetPath); - mPromise->MaybeResolve(file); - mPromise = nullptr; -} - -void -GetFileOrDirectoryTask::GetPermissionAccessType(nsCString& aAccess) const -{ - aAccess.AssignLiteral("read"); + aAccess.AssignLiteral(GET_FILE_OR_DIRECTORY_PERMISSION); } } // namespace dom diff --git a/dom/filesystem/GetFileOrDirectoryTask.h b/dom/filesystem/GetFileOrDirectoryTask.h index 959180665a..a3e58e34f8 100644 --- a/dom/filesystem/GetFileOrDirectoryTask.h +++ b/dom/filesystem/GetFileOrDirectoryTask.h @@ -17,58 +17,42 @@ namespace dom { class BlobImpl; -class GetFileOrDirectoryTask final : public FileSystemTaskBase +class GetFileOrDirectoryTaskChild final : public FileSystemTaskChildBase { public: - static already_AddRefed + static already_AddRefed Create(FileSystemBase* aFileSystem, nsIFile* aTargetPath, Directory::DirectoryType aType, bool aDirectoryOnly, ErrorResult& aRv); - static already_AddRefed - Create(FileSystemBase* aFileSystem, - const FileSystemGetFileOrDirectoryParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv); - virtual - ~GetFileOrDirectoryTask(); + ~GetFileOrDirectoryTaskChild(); already_AddRefed GetPromise(); virtual void GetPermissionAccessType(nsCString& aAccess) const override; + protected: virtual FileSystemParams GetRequestParams(const nsString& aSerializedDOMPath, ErrorResult& aRv) const override; - virtual FileSystemResponseValue - GetSuccessRequestResult(ErrorResult& aRv) const override; - virtual void SetSuccessRequestResult(const FileSystemResponseValue& aValue, ErrorResult& aRv) override; - - virtual nsresult - Work() override; - virtual void HandlerCallback() override; private: // If aDirectoryOnly is set, we should ensure that the target is a directory. - GetFileOrDirectoryTask(FileSystemBase* aFileSystem, - nsIFile* aTargetPath, - Directory::DirectoryType aType, - bool aDirectoryOnly); - - GetFileOrDirectoryTask(FileSystemBase* aFileSystem, - const FileSystemGetFileOrDirectoryParams& aParam, - FileSystemRequestParent* aParent); + GetFileOrDirectoryTaskChild(FileSystemBase* aFileSystem, + nsIFile* aTargetPath, + Directory::DirectoryType aType, + bool aDirectoryOnly); RefPtr mPromise; nsCOMPtr mTargetPath; @@ -78,6 +62,37 @@ private: Directory::DirectoryType mType; }; +class GetFileOrDirectoryTaskParent final : public FileSystemTaskParentBase +{ +public: + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); + + virtual void + GetPermissionAccessType(nsCString& aAccess) const override; + +protected: + virtual FileSystemResponseValue + GetSuccessRequestResult(ErrorResult& aRv) const override; + + virtual nsresult + IOWork() override; + +private: + GetFileOrDirectoryTaskParent(FileSystemBase* aFileSystem, + const FileSystemGetFileOrDirectoryParams& aParam, + FileSystemRequestParent* aParent); + + nsCOMPtr mTargetPath; + + // Whether we get a directory. + bool mIsDirectory; + Directory::DirectoryType mType; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/OSFileSystem.cpp b/dom/filesystem/OSFileSystem.cpp index f5e3d76fb3..c4a1063c8c 100644 --- a/dom/filesystem/OSFileSystem.cpp +++ b/dom/filesystem/OSFileSystem.cpp @@ -20,10 +20,7 @@ namespace dom { OSFileSystem::OSFileSystem(const nsAString& aRootDir) { mLocalOrDeviceStorageRootPath = aRootDir; - - // Non-mobile devices don't have the concept of separate permissions to - // access different parts of devices storage like Pictures, or Videos, etc. - mRequiresPermissionChecks = false; + mPermissionCheckType = ePermissionCheckNotRequired; #ifdef DEBUG mPermission.AssignLiteral("never-used"); @@ -33,6 +30,8 @@ OSFileSystem::OSFileSystem(const nsAString& aRootDir) already_AddRefed OSFileSystem::Clone() { + AssertIsOnOwningThread(); + RefPtr fs = new OSFileSystem(mLocalOrDeviceStorageRootPath); if (mParent) { fs->Init(mParent); @@ -44,9 +43,10 @@ OSFileSystem::Clone() void OSFileSystem::Init(nsISupports* aParent) { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnOwningThread(); MOZ_ASSERT(!mParent, "No duple Init() calls"); MOZ_ASSERT(aParent); + mParent = aParent; #ifdef DEBUG @@ -58,13 +58,14 @@ OSFileSystem::Init(nsISupports* aParent) nsISupports* OSFileSystem::GetParentObject() const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnOwningThread(); return mParent; } void OSFileSystem::GetRootName(nsAString& aRetval) const { + AssertIsOnOwningThread(); aRetval.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL); } @@ -91,12 +92,15 @@ OSFileSystem::IsSafeDirectory(Directory* aDir) const void OSFileSystem::Unlink() { + AssertIsOnOwningThread(); mParent = nullptr; } void OSFileSystem::Traverse(nsCycleCollectionTraversalCallback &cb) { + AssertIsOnOwningThread(); + OSFileSystem* tmp = this; NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent); } @@ -104,8 +108,23 @@ OSFileSystem::Traverse(nsCycleCollectionTraversalCallback &cb) void OSFileSystem::SerializeDOMPath(nsAString& aOutput) const { + AssertIsOnOwningThread(); aOutput = mLocalOrDeviceStorageRootPath; } +/** + * OSFileSystemParent + */ + +OSFileSystemParent::OSFileSystemParent(const nsAString& aRootDir) +{ + mLocalOrDeviceStorageRootPath = aRootDir; + mPermissionCheckType = ePermissionCheckNotRequired; + +#ifdef DEBUG + mPermission.AssignLiteral("never-used"); +#endif +} + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/OSFileSystem.h b/dom/filesystem/OSFileSystem.h index bf0708cf9b..0042f196ee 100644 --- a/dom/filesystem/OSFileSystem.h +++ b/dom/filesystem/OSFileSystem.h @@ -40,6 +40,9 @@ public: virtual void SerializeDOMPath(nsAString& aOutput) const override; + virtual bool + ClonableToDifferentThreadOrProcess() const override { return true; } + // CC methods virtual void Unlink() override; virtual void Traverse(nsCycleCollectionTraversalCallback &cb) override; @@ -47,7 +50,71 @@ public: private: virtual ~OSFileSystem() {} - nsCOMPtr mParent; + nsCOMPtr mParent; +}; + +class OSFileSystemParent final : public FileSystemBase +{ +public: + explicit OSFileSystemParent(const nsAString& aRootDir); + + // Overrides FileSystemBase + + virtual already_AddRefed + Clone() override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + return nullptr; + } + + virtual nsISupports* + GetParentObject() const override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + return nullptr; + } + + virtual void + GetRootName(nsAString& aRetval) const override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + } + + virtual bool + IsSafeFile(nsIFile* aFile) const override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + return true; + } + + virtual bool + IsSafeDirectory(Directory* aDir) const override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + return true; + } + + virtual void + SerializeDOMPath(nsAString& aOutput) const override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + } + + // CC methods + virtual void + Unlink() override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + } + + virtual void + Traverse(nsCycleCollectionTraversalCallback &cb) override + { + MOZ_CRASH("This should not be called on the PBackground thread."); + } + +private: + virtual ~OSFileSystemParent() {} }; } // namespace dom diff --git a/dom/filesystem/PFileSystemParams.ipdlh b/dom/filesystem/PFileSystemParams.ipdlh new file mode 100644 index 0000000000..1196100524 --- /dev/null +++ b/dom/filesystem/PFileSystemParams.ipdlh @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PBlob; + +namespace mozilla { +namespace dom { + +struct FileSystemCreateDirectoryParams +{ + nsString filesystem; + nsString realPath; +}; + +union FileSystemFileDataValue +{ + uint8_t[]; + PBlob; +}; + +struct FileSystemCreateFileParams +{ + nsString filesystem; + nsString realPath; + FileSystemFileDataValue data; + bool replace; +}; + +struct FileSystemGetDirectoryListingParams +{ + nsString filesystem; + nsString realPath; + bool isRoot; + // 'filters' could be an array rather than a semicolon separated string + // (we'd then use InfallibleTArray internally), but that is + // wasteful. E10s requires us to pass the filters over as a string anyway, + // so avoiding using an array avoids serialization on the side passing the + // filters. Since an nsString can share its buffer when copied, + // using that instead of InfallibleTArray makes copying the filters + // around in any given process a bit more efficient too, since copying a + // single nsString is cheaper than copying InfallibleTArray member data and + // each nsString that it contains. + nsString filters; +}; + +struct FileSystemGetFileOrDirectoryParams +{ + nsString filesystem; + nsString realPath; + bool isRoot; +}; + +struct FileSystemRemoveParams +{ + nsString filesystem; + nsString directory; + nsString targetDirectory; + bool recursive; +}; + +union FileSystemParams +{ + FileSystemCreateDirectoryParams; + FileSystemCreateFileParams; + FileSystemGetDirectoryListingParams; + FileSystemGetFileOrDirectoryParams; + FileSystemRemoveParams; +}; + +} // dom namespace +} // mozilla namespace diff --git a/dom/filesystem/PFileSystemRequest.ipdl b/dom/filesystem/PFileSystemRequest.ipdl index b3671d4ead..aa567b5f1c 100644 --- a/dom/filesystem/PFileSystemRequest.ipdl +++ b/dom/filesystem/PFileSystemRequest.ipdl @@ -4,8 +4,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ +include protocol PBackground; include protocol PBlob; -include protocol PContent; namespace mozilla { namespace dom { @@ -62,9 +62,9 @@ union FileSystemResponseValue FileSystemErrorResponse; }; -sync protocol PFileSystemRequest +protocol PFileSystemRequest { - manager PContent; + manager PBackground; child: async __delete__(FileSystemResponseValue response); diff --git a/dom/filesystem/RemoveTask.cpp b/dom/filesystem/RemoveTask.cpp index 00b702ad47..ff4bd6f6cd 100644 --- a/dom/filesystem/RemoveTask.cpp +++ b/dom/filesystem/RemoveTask.cpp @@ -9,6 +9,7 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/FileSystemBase.h" #include "mozilla/dom/FileSystemUtils.h" +#include "mozilla/dom/PFileSystemParams.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/ipc/BlobChild.h" #include "mozilla/dom/ipc/BlobParent.h" @@ -18,20 +19,24 @@ namespace mozilla { namespace dom { -/* static */ already_AddRefed -RemoveTask::Create(FileSystemBase* aFileSystem, - nsIFile* aDirPath, - nsIFile* aTargetPath, - bool aRecursive, - ErrorResult& aRv) +/** + * RemoveTaskChild + */ + +/* static */ already_AddRefed +RemoveTaskChild::Create(FileSystemBase* aFileSystem, + nsIFile* aDirPath, + nsIFile* aTargetPath, + bool aRecursive, + ErrorResult& aRv) { MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); MOZ_ASSERT(aDirPath); MOZ_ASSERT(aTargetPath); - RefPtr task = - new RemoveTask(aFileSystem, aDirPath, aTargetPath, aRecursive); + RefPtr task = + new RemoveTaskChild(aFileSystem, aDirPath, aTargetPath, aRecursive); // aTargetPath can be null. In this case SetError will be called. @@ -50,18 +55,112 @@ RemoveTask::Create(FileSystemBase* aFileSystem, return task.forget(); } -/* static */ already_AddRefed -RemoveTask::Create(FileSystemBase* aFileSystem, - const FileSystemRemoveParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv) +RemoveTaskChild::RemoveTaskChild(FileSystemBase* aFileSystem, + nsIFile* aDirPath, + nsIFile* aTargetPath, + bool aRecursive) + : FileSystemTaskChildBase(aFileSystem) + , mDirPath(aDirPath) + , mTargetPath(aTargetPath) + , mRecursive(aRecursive) + , mReturnValue(false) { - MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); MOZ_ASSERT(aFileSystem); + MOZ_ASSERT(aDirPath); + MOZ_ASSERT(aTargetPath); +} - RefPtr task = - new RemoveTask(aFileSystem, aParam, aParent); +RemoveTaskChild::~RemoveTaskChild() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +already_AddRefed +RemoveTaskChild::GetPromise() +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + return RefPtr(mPromise).forget(); +} + +FileSystemParams +RemoveTaskChild::GetRequestParams(const nsString& aSerializedDOMPath, + ErrorResult& aRv) const +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + FileSystemRemoveParams param; + param.filesystem() = aSerializedDOMPath; + + aRv = mDirPath->GetPath(param.directory()); + if (NS_WARN_IF(aRv.Failed())) { + return param; + } + + param.recursive() = mRecursive; + + nsAutoString path; + aRv = mTargetPath->GetPath(path); + if (NS_WARN_IF(aRv.Failed())) { + return param; + } + + param.targetDirectory() = path; + + return param; +} + +void +RemoveTaskChild::SetSuccessRequestResult(const FileSystemResponseValue& aValue, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + + FileSystemBooleanResponse r = aValue; + mReturnValue = r.success(); +} + +void +RemoveTaskChild::HandlerCallback() +{ + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + + if (mFileSystem->IsShutdown()) { + mPromise = nullptr; + return; + } + + if (HasError()) { + mPromise->MaybeReject(mErrorValue); + mPromise = nullptr; + return; + } + + mPromise->MaybeResolve(mReturnValue); + mPromise = nullptr; +} + +void +RemoveTaskChild::GetPermissionAccessType(nsCString& aAccess) const +{ + aAccess.AssignLiteral(REMOVE_TASK_PERMISSION); +} + +/** + * RemoveTaskParent + */ + +/* static */ already_AddRefed +RemoveTaskParent::Create(FileSystemBase* aFileSystem, + const FileSystemRemoveParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv) +{ + MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); + AssertIsOnBackgroundThread(); + MOZ_ASSERT(aFileSystem); + + RefPtr task = + new RemoveTaskParent(aFileSystem, aParam, aParent); NS_ConvertUTF16toUTF8 directoryPath(aParam.directory()); aRv = NS_NewNativeLocalFile(directoryPath, true, @@ -86,91 +185,28 @@ RemoveTask::Create(FileSystemBase* aFileSystem, return task.forget(); } -RemoveTask::RemoveTask(FileSystemBase* aFileSystem, - nsIFile* aDirPath, - nsIFile* aTargetPath, - bool aRecursive) - : FileSystemTaskBase(aFileSystem) - , mDirPath(aDirPath) - , mTargetPath(aTargetPath) - , mRecursive(aRecursive) - , mReturnValue(false) -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - MOZ_ASSERT(aFileSystem); - MOZ_ASSERT(aDirPath); - MOZ_ASSERT(aTargetPath); -} - -RemoveTask::RemoveTask(FileSystemBase* aFileSystem, - const FileSystemRemoveParams& aParam, - FileSystemRequestParent* aParent) - : FileSystemTaskBase(aFileSystem, aParam, aParent) +RemoveTaskParent::RemoveTaskParent(FileSystemBase* aFileSystem, + const FileSystemRemoveParams& aParam, + FileSystemRequestParent* aParent) + : FileSystemTaskParentBase(aFileSystem, aParam, aParent) , mRecursive(false) , mReturnValue(false) { MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnBackgroundThread(); MOZ_ASSERT(aFileSystem); } -RemoveTask::~RemoveTask() -{ - MOZ_ASSERT(!mPromise || NS_IsMainThread(), - "mPromise should be released on main thread!"); -} - -already_AddRefed -RemoveTask::GetPromise() -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - return RefPtr(mPromise).forget(); -} - -FileSystemParams -RemoveTask::GetRequestParams(const nsString& aSerializedDOMPath, - ErrorResult& aRv) const -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - FileSystemRemoveParams param; - param.filesystem() = aSerializedDOMPath; - - aRv = mDirPath->GetPath(param.directory()); - if (NS_WARN_IF(aRv.Failed())) { - return param; - } - - param.recursive() = mRecursive; - - nsAutoString path; - aRv = mTargetPath->GetPath(path); - if (NS_WARN_IF(aRv.Failed())) { - return param; - } - - param.targetDirectory() = path; - - return param; -} - FileSystemResponseValue -RemoveTask::GetSuccessRequestResult(ErrorResult& aRv) const +RemoveTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); + AssertIsOnBackgroundThread(); + return FileSystemBooleanResponse(mReturnValue); } -void -RemoveTask::SetSuccessRequestResult(const FileSystemResponseValue& aValue, - ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - FileSystemBooleanResponse r = aValue; - mReturnValue = r.success(); -} - nsresult -RemoveTask::Work() +RemoveTaskParent::IOWork() { MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!"); @@ -214,28 +250,9 @@ RemoveTask::Work() } void -RemoveTask::HandlerCallback() +RemoveTaskParent::GetPermissionAccessType(nsCString& aAccess) const { - MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!"); - if (mFileSystem->IsShutdown()) { - mPromise = nullptr; - return; - } - - if (HasError()) { - mPromise->MaybeReject(mErrorValue); - mPromise = nullptr; - return; - } - - mPromise->MaybeResolve(mReturnValue); - mPromise = nullptr; -} - -void -RemoveTask::GetPermissionAccessType(nsCString& aAccess) const -{ - aAccess.AssignLiteral("write"); + aAccess.AssignLiteral(REMOVE_TASK_PERMISSION); } } // namespace dom diff --git a/dom/filesystem/RemoveTask.h b/dom/filesystem/RemoveTask.h index 6bef5bfb57..9963c9ad71 100644 --- a/dom/filesystem/RemoveTask.h +++ b/dom/filesystem/RemoveTask.h @@ -11,30 +11,26 @@ #include "nsAutoPtr.h" #include "mozilla/ErrorResult.h" +#define REMOVE_TASK_PERMISSION "write" + namespace mozilla { namespace dom { class BlobImpl; class Promise; -class RemoveTask final : public FileSystemTaskBase +class RemoveTaskChild final : public FileSystemTaskChildBase { public: - static already_AddRefed + static already_AddRefed Create(FileSystemBase* aFileSystem, nsIFile* aDirPath, nsIFile* aTargetPath, bool aRecursive, ErrorResult& aRv); - static already_AddRefed - Create(FileSystemBase* aFileSystem, - const FileSystemRemoveParams& aParam, - FileSystemRequestParent* aParent, - ErrorResult& aRv); - virtual - ~RemoveTask(); + ~RemoveTaskChild(); already_AddRefed GetPromise(); @@ -47,28 +43,18 @@ protected: GetRequestParams(const nsString& aSerializedDOMPath, ErrorResult& aRv) const override; - virtual FileSystemResponseValue - GetSuccessRequestResult(ErrorResult& aRv) const override; - virtual void SetSuccessRequestResult(const FileSystemResponseValue& aValue, ErrorResult& aRv) override; - virtual nsresult - Work() override; - virtual void HandlerCallback() override; private: - RemoveTask(FileSystemBase* aFileSystem, - nsIFile* aDirPath, - nsIFile* aTargetPath, - bool aRecursive); - - RemoveTask(FileSystemBase* aFileSystem, - const FileSystemRemoveParams& aParam, - FileSystemRequestParent* aParent); + RemoveTaskChild(FileSystemBase* aFileSystem, + nsIFile* aDirPath, + nsIFile* aTargetPath, + bool aRecursive); RefPtr mPromise; @@ -82,6 +68,40 @@ private: bool mReturnValue; }; +class RemoveTaskParent final : public FileSystemTaskParentBase +{ +public: + static already_AddRefed + Create(FileSystemBase* aFileSystem, + const FileSystemRemoveParams& aParam, + FileSystemRequestParent* aParent, + ErrorResult& aRv); + + virtual void + GetPermissionAccessType(nsCString& aAccess) const override; + +protected: + virtual FileSystemResponseValue + GetSuccessRequestResult(ErrorResult& aRv) const override; + + virtual nsresult + IOWork() override; + +private: + RemoveTaskParent(FileSystemBase* aFileSystem, + const FileSystemRemoveParams& aParam, + FileSystemRequestParent* aParent); + + // This path is the Directory::mFile. + nsCOMPtr mDirPath; + + // This is what we want to remove. mTargetPath is discendant path of mDirPath. + nsCOMPtr mTargetPath; + + bool mRecursive; + bool mReturnValue; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/filesystem/moz.build b/dom/filesystem/moz.build index adaf5794fe..f0698c3e1d 100644 --- a/dom/filesystem/moz.build +++ b/dom/filesystem/moz.build @@ -35,6 +35,7 @@ UNIFIED_SOURCES += [ FINAL_LIBRARY = 'xul' IPDL_SOURCES += [ + 'PFileSystemParams.ipdlh', 'PFileSystemRequest.ipdl', ] diff --git a/dom/filesystem/tests/mochitest.ini b/dom/filesystem/tests/mochitest.ini index 15292f2eaf..b9f531c641 100644 --- a/dom/filesystem/tests/mochitest.ini +++ b/dom/filesystem/tests/mochitest.ini @@ -1,5 +1,7 @@ [DEFAULT] support-files = script_fileList.js + worker_basic.js [test_basic.html] +[test_worker_basic.html] diff --git a/dom/filesystem/tests/test_worker_basic.html b/dom/filesystem/tests/test_worker_basic.html new file mode 100644 index 0000000000..0be7696242 --- /dev/null +++ b/dom/filesystem/tests/test_worker_basic.html @@ -0,0 +1,70 @@ + + + + Test for Directory API in workers + + + + + + + + + diff --git a/dom/filesystem/tests/worker_basic.js b/dom/filesystem/tests/worker_basic.js new file mode 100644 index 0000000000..4da78eef0e --- /dev/null +++ b/dom/filesystem/tests/worker_basic.js @@ -0,0 +1,58 @@ +function finish() { + postMessage({ type: 'finish' }); +} + +function ok(a, msg) { + postMessage({ type: 'test', test: !!a, message: msg }); +} + +function is(a, b, msg) { + ok(a === b, msg); +} + +function isnot(a, b, msg) { + ok(a != b, msg); +} + +function checkSubDir(dir) { + return dir.getFilesAndDirectories().then( + function(data) { + for (var i = 0; i < data.length; ++i) { + ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories"); + if (data[i] instanceof Directory) { + isnot(data[i].name, '/', "Subdirectory should be called with the leafname"); + isnot(data[i].path, '/', "Subdirectory path should be called with the leafname"); + isnot(data[i].path, dir.path, "Subdirectory path should contain the parent path."); + is(data[i].path,dir.path + '/' + data[i].name, "Subdirectory path should be called parentdir.path + '/' + leafname"); + } + } + } + ); +} + +onmessage = function(e) { + var fileList = e.data; + ok(fileList instanceof FileList, "This is a fileList."); + is(fileList.length, 1, "We want just 1 element."); + ok(fileList[0] instanceof Directory, "This is a directory."); + + fileList[0].getFilesAndDirectories().then( + function(data) { + ok(data.length, "We should have some data."); + var promises = []; + for (var i = 0; i < data.length; ++i) { + ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories"); + if (data[i] instanceof Directory) { + isnot(data[i].name, '/', "Subdirectory should be called with the leafname"); + is(data[i].path, '/' + data[i].name, "Subdirectory path should be called '/' + leafname"); + promises.push(checkSubDir(data[i])); + } + } + + return Promise.all(promises); + }, + function() { + ok(false, "Something when wrong"); + } + ).then(finish); +} diff --git a/dom/html/HTMLButtonElement.cpp b/dom/html/HTMLButtonElement.cpp index d749be5ebf..2c649a20ee 100644 --- a/dom/html/HTMLButtonElement.cpp +++ b/dom/html/HTMLButtonElement.cpp @@ -297,7 +297,7 @@ HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) eKeyPress == aVisitor.mEvent->mMessage) || (keyEvent->keyCode == NS_VK_SPACE && eKeyUp == aVisitor.mEvent->mMessage)) { - DispatchSimulatedClick(this, aVisitor.mEvent->mFlags.mIsTrusted, + DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(), aVisitor.mPresContext); aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; } @@ -308,7 +308,7 @@ HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent(); if (mouseEvent->button == WidgetMouseEvent::eLeftButton) { - if (mouseEvent->mFlags.mIsTrusted) { + if (mouseEvent->IsTrusted()) { EventStateManager* esm = aVisitor.mPresContext->EventStateManager(); EventStateManager::SetActiveManager( diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index bb7fbbf3c1..1fa5deaf2c 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -1262,7 +1262,7 @@ HTMLCanvasElement::OnVisibilityChange() } if (mOffscreenCanvas) { - class Runnable final : public nsCancelableRunnable + class Runnable final : public CancelableRunnable { public: explicit Runnable(AsyncCanvasRenderer* aRenderer) @@ -1304,7 +1304,7 @@ void HTMLCanvasElement::OnMemoryPressure() { if (mOffscreenCanvas) { - class Runnable final : public nsCancelableRunnable + class Runnable final : public CancelableRunnable { public: explicit Runnable(AsyncCanvasRenderer* aRenderer) diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp index 80e425ef42..b23223793a 100644 --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -527,7 +527,7 @@ HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor) aVisitor.mEvent->mMessage == eFormReset) && aVisitor.mEvent->mFlags.mInBubblingPhase && aVisitor.mEvent->originalTarget != static_cast(this)) { - aVisitor.mEvent->mFlags.mPropagationStopped = true; + aVisitor.mEvent->StopPropagation(); } return NS_OK; } diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index a86fe2b035..e959d563c7 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -3361,8 +3361,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor) } } - if (mType == NS_FORM_INPUT_NUMBER && - aVisitor.mEvent->mFlags.mIsTrusted) { + if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) { if (mNumberControlSpinnerIsSpinning) { // If the timer is running the user has depressed the mouse on one of the // spin buttons. If the mouse exits the button we either want to reverse @@ -3447,7 +3446,7 @@ HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor) // We do this after calling the base class' PreHandleEvent so that // nsIContent::PreHandleEvent doesn't reset any change we make to mCanHandle. if (mType == NS_FORM_INPUT_NUMBER && - aVisitor.mEvent->mFlags.mIsTrusted && + aVisitor.mEvent->IsTrusted() && aVisitor.mEvent->originalTarget != this) { // has an anonymous descendant. If // 'input' or 'change' events are fired at that text control then we need @@ -3724,7 +3723,7 @@ HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor) // - it's the left mouse button. // We do not prevent non-trusted click because authors can already use // .click(). However, the pickers will follow the rules of popup-blocking. - if (aVisitor.mEvent->mFlags.mDefaultPrevented) { + if (aVisitor.mEvent->DefaultPrevented()) { return NS_OK; } WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent(); @@ -3898,7 +3897,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent(); if (mType == NS_FORM_INPUT_NUMBER && keyEvent && keyEvent->mMessage == eKeyPress && - aVisitor.mEvent->mFlags.mIsTrusted && + aVisitor.mEvent->IsTrusted() && (keyEvent->keyCode == NS_VK_UP || keyEvent->keyCode == NS_VK_DOWN) && !(keyEvent->IsShift() || keyEvent->IsControl() || keyEvent->IsAlt() || keyEvent->IsMeta() || @@ -3915,7 +3914,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) // the editor's handling of up/down keypress events. For that reason we // just ignore aVisitor.mEventStatus here and go ahead and handle the // event to increase/decrease the value of the number control. - if (!aVisitor.mEvent->mFlags.mDefaultPreventedByContent && IsMutable()) { + if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) { StepNumberControlForUserEvent(keyEvent->keyCode == NS_VK_UP ? 1 : -1); aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; } @@ -3976,7 +3975,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) case NS_FORM_INPUT_IMAGE: // Bug 34418 case NS_FORM_INPUT_COLOR: { - DispatchSimulatedClick(this, aVisitor.mEvent->mFlags.mIsTrusted, + DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(), aVisitor.mPresContext); aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; } // case @@ -4005,7 +4004,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) rv = selectedRadioButton->Focus(); if (NS_SUCCEEDED(rv)) { rv = DispatchSimulatedClick(selectedRadioButton, - aVisitor.mEvent->mFlags.mIsTrusted, + aVisitor.mEvent->IsTrusted(), aVisitor.mPresContext); if (NS_SUCCEEDED(rv)) { aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault; @@ -4117,8 +4116,7 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) } } } - if (mType == NS_FORM_INPUT_NUMBER && - aVisitor.mEvent->mFlags.mIsTrusted) { + if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) { if (mouseEvent->button == WidgetMouseEvent::eLeftButton && !(mouseEvent->IsShift() || mouseEvent->IsControl() || mouseEvent->IsAlt() || mouseEvent->IsMeta() || @@ -5059,13 +5057,6 @@ HTMLInputElement::GetFilesAndDirectories(ErrorResult& aRv) for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) { if (filesAndDirs[i].IsDirectory()) { -#if defined(ANDROID) || defined(MOZ_B2G) - MOZ_ASSERT(false, - "Directory picking should have been redirected to normal " - "file picking for platforms that don't have a directory " - "picker"); -#endif - RefPtr directory = filesAndDirs[i].GetAsDirectory(); // In future we could refactor SetFilePickerFiltersFromAccept to return a diff --git a/dom/html/HTMLObjectElement.cpp b/dom/html/HTMLObjectElement.cpp index b89a812bb6..84c670dede 100644 --- a/dom/html/HTMLObjectElement.cpp +++ b/dom/html/HTMLObjectElement.cpp @@ -212,7 +212,7 @@ void HTMLObjectElement::HandleFocusBlurPlugin(Element* aElement, WidgetEvent* aEvent) { - if (!aEvent->mFlags.mIsTrusted) { + if (!aEvent->IsTrusted()) { return; } switch (aEvent->mMessage) { diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index a5f4a5efbb..6a4133aa63 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -2280,7 +2280,7 @@ nsGenericHTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, nsresult nsGenericHTMLFormElement::PreHandleEvent(EventChainPreVisitor& aVisitor) { - if (aVisitor.mEvent->mFlags.mIsTrusted) { + if (aVisitor.mEvent->IsTrusted()) { switch (aVisitor.mEvent->mMessage) { case eFocus: { // Check to see if focus has bubbled up from a form control's diff --git a/dom/indexedDB/ActorsChild.cpp b/dom/indexedDB/ActorsChild.cpp index f28cb7b549..d8a6946712 100644 --- a/dom/indexedDB/ActorsChild.cpp +++ b/dom/indexedDB/ActorsChild.cpp @@ -2531,9 +2531,9 @@ BackgroundRequestChild::Recv__delete__(const RequestResponse& aResponse) ******************************************************************************/ // Does not need to be threadsafe since this only runs on one thread, but -// inheriting from nsCancelableRunnable is easy. +// inheriting from CancelableRunnable is easy. class BackgroundCursorChild::DelayedActionRunnable final - : public nsCancelableRunnable + : public CancelableRunnable { using ActionFunc = void (BackgroundCursorChild::*)(); diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index 180b16e7a9..cc3046555f 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -67,7 +67,7 @@ const char kMemoryPressureObserverTopic[] = "memory-pressure"; const char kWindowObserverTopic[] = "inner-window-destroyed"; class CancelableRunnableWrapper final - : public nsCancelableRunnable + : public CancelableRunnable { nsCOMPtr mRunnable; diff --git a/dom/interfaces/push/nsIPushNotifier.idl b/dom/interfaces/push/nsIPushNotifier.idl index b30bda9aec..fccd88fa5e 100644 --- a/dom/interfaces/push/nsIPushNotifier.idl +++ b/dom/interfaces/push/nsIPushNotifier.idl @@ -24,6 +24,9 @@ interface nsIPushNotifier : nsISupports [array, size_is(dataLen)] in uint8_t data); void notifySubscriptionChange(in ACString scope, in nsIPrincipal principal); + + void notifyError(in ACString scope, in nsIPrincipal principal, + in DOMString message, in uint32_t flags); }; /** diff --git a/dom/ipc/Blob.cpp b/dom/ipc/Blob.cpp index 81857110f9..040374ab3e 100644 --- a/dom/ipc/Blob.cpp +++ b/dom/ipc/Blob.cpp @@ -212,7 +212,7 @@ EventTargetIsOnCurrentThread(nsIEventTarget* aEventTarget) } class CancelableRunnableWrapper final - : public nsCancelableRunnable + : public CancelableRunnable { nsCOMPtr mRunnable; #ifdef DEBUG @@ -241,7 +241,7 @@ private: nsresult Cancel() override; }; -NS_IMPL_ISUPPORTS_INHERITED0(CancelableRunnableWrapper, nsCancelableRunnable) +NS_IMPL_ISUPPORTS_INHERITED0(CancelableRunnableWrapper, CancelableRunnable) NS_IMETHODIMP CancelableRunnableWrapper::Run() diff --git a/dom/ipc/ContentBridgeChild.cpp b/dom/ipc/ContentBridgeChild.cpp index 67d583d826..0908319576 100644 --- a/dom/ipc/ContentBridgeChild.cpp +++ b/dom/ipc/ContentBridgeChild.cpp @@ -60,11 +60,11 @@ ContentBridgeChild::DeferredDestroy() bool ContentBridgeChild::RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { - return nsIContentChild::RecvAsyncMessage(aMsg, aData, Move(aCpows), aPrincipal); + return nsIContentChild::RecvAsyncMessage(aMsg, Move(aCpows), aPrincipal, aData); } PBlobChild* diff --git a/dom/ipc/ContentBridgeChild.h b/dom/ipc/ContentBridgeChild.h index 6959793d7a..820d2b9cb6 100644 --- a/dom/ipc/ContentBridgeChild.h +++ b/dom/ipc/ContentBridgeChild.h @@ -28,9 +28,9 @@ public: void DeferredDestroy(); virtual bool RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) override; + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) override; virtual PBlobChild* SendPBlobConstructor(PBlobChild* actor, diff --git a/dom/ipc/ContentBridgeParent.cpp b/dom/ipc/ContentBridgeParent.cpp index b4672f02e3..918455c3d9 100644 --- a/dom/ipc/ContentBridgeParent.cpp +++ b/dom/ipc/ContentBridgeParent.cpp @@ -84,12 +84,12 @@ ContentBridgeParent::RecvSyncMessage(const nsString& aMsg, bool ContentBridgeParent::RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { - return nsIContentParent::RecvAsyncMessage(aMsg, aData, Move(aCpows), - aPrincipal); + return nsIContentParent::RecvAsyncMessage(aMsg, Move(aCpows), + aPrincipal, aData); } PBlobParent* diff --git a/dom/ipc/ContentBridgeParent.h b/dom/ipc/ContentBridgeParent.h index 559ac62a4b..2094bc8251 100644 --- a/dom/ipc/ContentBridgeParent.h +++ b/dom/ipc/ContentBridgeParent.h @@ -88,9 +88,9 @@ protected: nsTArray* aRetvals) override; virtual bool RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) override; + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) override; virtual jsipc::PJavaScriptParent* AllocPJavaScriptParent() override; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index fd5d6e59b5..3d962fbf1f 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -174,8 +174,6 @@ #include "mozilla/dom/mobileconnection/MobileConnectionChild.h" #include "mozilla/dom/mobilemessage/SmsChild.h" #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h" -#include "mozilla/dom/PFileSystemRequestChild.h" -#include "mozilla/dom/FileSystemTaskBase.h" #include "mozilla/dom/bluetooth/PBluetoothChild.h" #include "mozilla/dom/PFMRadioChild.h" #include "mozilla/dom/PPresentationChild.h" @@ -850,6 +848,10 @@ ContentChild::ProvideWindowCommon(TabChild* aTabOpener, nsTArray frameScripts; nsCString urlToLoad; + PRenderFrameChild* renderFrame = newChild->SendPRenderFrameConstructor(); + TextureFactoryIdentifier textureFactoryIdentifier; + uint64_t layersId = 0; + if (aIframeMoz) { MOZ_ASSERT(aTabOpener); newChild->SendBrowserFrameOpenWindow(aTabOpener, NS_ConvertUTF8toUTF16(url), @@ -883,7 +885,7 @@ ContentChild::ProvideWindowCommon(TabChild* aTabOpener, } nsresult rv; - if (!SendCreateWindow(aTabOpener, newChild, + if (!SendCreateWindow(aTabOpener, newChild, renderFrame, aChromeFlags, aCalledFromJS, aPositionSpecified, aSizeSpecified, url, name, features, @@ -895,25 +897,25 @@ ContentChild::ProvideWindowCommon(TabChild* aTabOpener, &rv, aWindowIsNew, &frameScripts, - &urlToLoad)) { + &urlToLoad, + &textureFactoryIdentifier, + &layersId)) { + PRenderFrameChild::Send__delete__(renderFrame); return NS_ERROR_NOT_AVAILABLE; } if (NS_FAILED(rv)) { + PRenderFrameChild::Send__delete__(renderFrame); + PBrowserChild::Send__delete__(newChild); return rv; } } if (!*aWindowIsNew) { + PRenderFrameChild::Send__delete__(renderFrame); PBrowserChild::Send__delete__(newChild); return NS_ERROR_ABORT; } - TextureFactoryIdentifier textureFactoryIdentifier; - uint64_t layersId = 0; - PRenderFrameChild* renderFrame = newChild->SendPRenderFrameConstructor(); - newChild->SendGetRenderFrameInfo(renderFrame, - &textureFactoryIdentifier, - &layersId); if (layersId == 0) { // if renderFrame is invalid. PRenderFrameChild::Send__delete__(renderFrame); renderFrame = nullptr; @@ -1821,24 +1823,6 @@ ContentChild::DeallocPDeviceStorageRequestChild(PDeviceStorageRequestChild* aDev return true; } -PFileSystemRequestChild* -ContentChild::AllocPFileSystemRequestChild(const FileSystemParams& aParams) -{ - MOZ_CRASH("Should never get here!"); - return nullptr; -} - -bool -ContentChild::DeallocPFileSystemRequestChild(PFileSystemRequestChild* aFileSystem) -{ - mozilla::dom::FileSystemTaskBase* child = - static_cast(aFileSystem); - // The reference is increased in FileSystemTaskBase::Start of - // FileSystemTaskBase.cpp. We should decrease it after IPC. - NS_RELEASE(child); - return true; -} - PMobileConnectionChild* ContentChild::SendPMobileConnectionConstructor(PMobileConnectionChild* aActor, const uint32_t& aClientId) @@ -2426,9 +2410,9 @@ ContentChild::RecvLoadProcessScript(const nsString& aURL) bool ContentChild::RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { RefPtr cpm = nsFrameMessageManager::GetChildProcessManager(); if (cpm) { @@ -3287,5 +3271,22 @@ ContentChild::RecvPushSubscriptionChange(const nsCString& aScope, return true; } +bool +ContentChild::RecvPushError(const nsCString& aScope, const nsString& aMessage, + const uint32_t& aFlags) +{ +#ifndef MOZ_SIMPLEPUSH + nsCOMPtr pushNotifierIface = + do_GetService("@mozilla.org/push/Notifier;1"); + if (NS_WARN_IF(!pushNotifierIface)) { + return true; + } + PushNotifier* pushNotifier = + static_cast(pushNotifierIface.get()); + pushNotifier->NotifyErrorWorkers(aScope, aMessage, aFlags); +#endif + return true; +} + } // namespace dom } // namespace mozilla diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index e693182966..35568c863c 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -192,12 +192,6 @@ public: virtual bool DeallocPDeviceStorageRequestChild(PDeviceStorageRequestChild*) override; - virtual PFileSystemRequestChild* - AllocPFileSystemRequestChild(const FileSystemParams&) override; - - virtual bool - DeallocPFileSystemRequestChild(PFileSystemRequestChild*) override; - virtual PBlobChild* AllocPBlobChild(const BlobConstructorParams& aParams) override; @@ -419,9 +413,9 @@ public: virtual bool RecvLoadProcessScript(const nsString& aURL) override; virtual bool RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) override; + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) override; virtual bool RecvGeolocationUpdate(const GeoPosition& somewhere) override; @@ -534,6 +528,10 @@ public: RecvPushSubscriptionChange(const nsCString& aScope, const IPC::Principal& aPrincipal) override; + virtual bool + RecvPushError(const nsCString& aScope, const nsString& aMessage, + const uint32_t& aFlags) override; + // Get the directory for IndexedDB files. We query the parent for this and // cache the value nsString &GetIndexedDBPath(); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 0acd21638d..6704b1bf8e 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -47,7 +47,6 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/File.h" #include "mozilla/dom/ExternalHelperAppParent.h" -#include "mozilla/dom/FileSystemRequestParent.h" #include "mozilla/dom/GeolocationBinding.h" #include "mozilla/dom/Notification.h" #include "mozilla/dom/NuwaParent.h" @@ -85,6 +84,7 @@ #include "mozilla/layers/CompositorBridgeParent.h" #include "mozilla/layers/ImageBridgeParent.h" #include "mozilla/layers/SharedBufferManagerParent.h" +#include "mozilla/layout/RenderFrameParent.h" #include "mozilla/LookAndFeel.h" #include "mozilla/media/MediaParent.h" #include "mozilla/Move.h" @@ -297,6 +297,7 @@ using namespace mozilla::gmp; using namespace mozilla::hal; using namespace mozilla::ipc; using namespace mozilla::layers; +using namespace mozilla::layout; using namespace mozilla::net; using namespace mozilla::jsipc; using namespace mozilla::psm; @@ -1176,7 +1177,6 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext, RefPtr tp(new TabParent(constructorSender, tabId, aContext, chromeFlags)); tp->SetInitedByParent(); - tp->SetOwnerElement(aFrameElement); PBrowserParent* browser = constructorSender->SendPBrowserConstructor( @@ -1187,7 +1187,10 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext, constructorSender->ChildID(), constructorSender->IsForApp(), constructorSender->IsForBrowser()); - return TabParent::GetFrom(browser); + + RefPtr constructedTabParent = TabParent::GetFrom(browser); + constructedTabParent->SetOwnerElement(aFrameElement); + return constructedTabParent; } return nullptr; } @@ -1285,7 +1288,6 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext, RefPtr tp = new TabParent(parent, tabId, aContext, chromeFlags); tp->SetInitedByParent(); - tp->SetOwnerElement(aFrameElement); PBrowserParent* browser = parent->SendPBrowserConstructor( // DeallocPBrowserParent() releases this ref. RefPtr(tp).forget().take(), @@ -1296,6 +1298,11 @@ ContentParent::CreateBrowserOrApp(const TabContext& aContext, parent->IsForApp(), parent->IsForBrowser()); + if (browser) { + RefPtr constructedTabParent = TabParent::GetFrom(browser); + constructedTabParent->SetOwnerElement(aFrameElement); + } + if (isInContentProcess) { // Just return directly without the following check in content process. return TabParent::GetFrom(browser); @@ -3496,24 +3503,6 @@ ContentParent::DeallocPDeviceStorageRequestParent(PDeviceStorageRequestParent* d return true; } -PFileSystemRequestParent* -ContentParent::AllocPFileSystemRequestParent(const FileSystemParams& aParams) -{ - RefPtr result = new FileSystemRequestParent(); - if (!result->Dispatch(this, aParams)) { - return nullptr; - } - return result.forget().take(); -} - -bool -ContentParent::DeallocPFileSystemRequestParent(PFileSystemRequestParent* doomed) -{ - FileSystemRequestParent* parent = static_cast(doomed); - NS_RELEASE(parent); - return true; -} - PBlobParent* ContentParent::AllocPBlobParent(const BlobConstructorParams& aParams) { @@ -4240,25 +4229,6 @@ ContentParent::RecvSetURITitle(const URIParams& uri, return true; } -bool -ContentParent::RecvGetRandomValues(const uint32_t& length, - InfallibleTArray* randomValues) -{ - uint8_t* buf = Crypto::GetRandomValues(length); - if (!buf) { - return true; - } - - randomValues->SetCapacity(length); - randomValues->SetLength(length); - - memcpy(randomValues->Elements(), buf, length); - - NS_Free(buf); - - return true; -} - bool ContentParent::RecvGetSystemMemory(const uint64_t& aGetterId) { @@ -4423,12 +4393,12 @@ ContentParent::RecvRpcMessage(const nsString& aMsg, bool ContentParent::RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { - return nsIContentParent::RecvAsyncMessage(aMsg, aData, Move(aCpows), - aPrincipal); + return nsIContentParent::RecvAsyncMessage(aMsg, Move(aCpows), aPrincipal, + aData); } bool @@ -4672,7 +4642,7 @@ ContentParent::DoSendAsyncMessage(JSContext* aCx, // Nuwa won't receive frame messages after it is frozen. return NS_OK; } - if (!SendAsyncMessage(nsString(aMessage), data, cpows, Principal(aPrincipal))) { + if (!SendAsyncMessage(nsString(aMessage), cpows, Principal(aPrincipal), data)) { return NS_ERROR_UNEXPECTED; } return NS_OK; @@ -4897,6 +4867,7 @@ ContentParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus, bool ContentParent::RecvGetGraphicsFeatureStatus(const int32_t& aFeature, int32_t* aStatus, + nsCString* aFailureId, bool* aSuccess) { nsCOMPtr gfxInfo = services::GetGfxInfo(); @@ -4905,7 +4876,7 @@ ContentParent::RecvGetGraphicsFeatureStatus(const int32_t& aFeature, return true; } - *aSuccess = NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, aStatus)); + *aSuccess = NS_SUCCEEDED(gfxInfo->GetFeatureStatus(aFeature, *aFailureId, aStatus)); return true; } @@ -5378,6 +5349,7 @@ ContentParent::DeallocPContentPermissionRequestParent(PContentPermissionRequestP bool ContentParent::RecvCreateWindow(PBrowserParent* aThisTab, PBrowserParent* aNewTab, + PRenderFrameParent* aRenderFrame, const uint32_t& aChromeFlags, const bool& aCalledFromJS, const bool& aPositionSpecified, @@ -5391,7 +5363,9 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab, nsresult* aResult, bool* aWindowIsNew, InfallibleTArray* aFrameScripts, - nsCString* aURLToLoad) + nsCString* aURLToLoad, + TextureFactoryIdentifier* aTextureFactoryIdentifier, + uint64_t* aLayersId) { // We always expect to open a new window here. If we don't, it's an error. *aWindowIsNew = true; @@ -5480,7 +5454,7 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab, // Opening new tabs is the easy case... if (openLocation == nsIBrowserDOMWindow::OPEN_NEWTAB) { if (NS_WARN_IF(!browserDOMWin)) { - *aResult = NS_ERROR_FAILURE; + *aResult = NS_ERROR_ABORT; return true; } @@ -5506,6 +5480,13 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab, } newTab->SwapFrameScriptsFrom(*aFrameScripts); + + RenderFrameParent* rfp = static_cast(aRenderFrame); + if (!newTab->SetRenderFrame(rfp) || + !newTab->GetRenderFrameInfo(aTextureFactoryIdentifier, aLayersId)) { + *aResult = NS_ERROR_FAILURE; + } + return true; } @@ -5590,6 +5571,13 @@ ContentParent::RecvCreateWindow(PBrowserParent* aThisTab, MOZ_ASSERT(TabParent::GetFrom(newRemoteTab) == newTab); newTab->SwapFrameScriptsFrom(*aFrameScripts); + + RenderFrameParent* rfp = static_cast(aRenderFrame); + if (!newTab->SetRenderFrame(rfp) || + !newTab->GetRenderFrameInfo(aTextureFactoryIdentifier, aLayersId)) { + *aResult = NS_ERROR_FAILURE; + } + return true; } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index f343acb937..c2914a2873 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -63,8 +63,13 @@ class PJavaScriptParent; namespace layers { class PCompositorBridgeParent; class PSharedBufferManagerParent; +struct TextureFactoryIdentifier; } // namespace layers +namespace layout { +class PRenderFrameParent; +} // namespace layout + namespace dom { class Element; @@ -489,6 +494,7 @@ public: virtual bool RecvCreateWindow(PBrowserParent* aThisTabParent, PBrowserParent* aOpener, + layout::PRenderFrameParent* aRenderFrame, const uint32_t& aChromeFlags, const bool& aCalledFromJS, const bool& aPositionSpecified, @@ -502,7 +508,9 @@ public: nsresult* aResult, bool* aWindowIsNew, InfallibleTArray* aFrameScripts, - nsCString* aURLToLoad) override; + nsCString* aURLToLoad, + layers::TextureFactoryIdentifier* aTextureFactoryIdentifier, + uint64_t* aLayersId) override; static bool AllocateLayerTreeId(TabParent* aTabParent, uint64_t* aId); @@ -720,12 +728,6 @@ private: virtual bool DeallocPDeviceStorageRequestParent(PDeviceStorageRequestParent*) override; - virtual PFileSystemRequestParent* - AllocPFileSystemRequestParent(const FileSystemParams&) override; - - virtual bool - DeallocPFileSystemRequestParent(PFileSystemRequestParent*) override; - virtual PBlobParent* AllocPBlobParent(const BlobConstructorParams& aParams) override; @@ -738,9 +740,6 @@ private: virtual bool DeallocPCrashReporterParent(PCrashReporterParent* crashreporter) override; - virtual bool RecvGetRandomValues(const uint32_t& length, - InfallibleTArray* randomValues) override; - virtual bool RecvIsSecureURI(const uint32_t& aType, const URIParams& aURI, const uint32_t& aFlags, bool* aIsSecureURI) override; @@ -925,9 +924,9 @@ private: nsTArray* aRetvals) override; virtual bool RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) override; + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) override; virtual bool RecvFilePathUpdateNotify(const nsString& aType, const nsString& aStorageName, @@ -1014,6 +1013,7 @@ private: virtual bool RecvGetGraphicsFeatureStatus(const int32_t& aFeature, int32_t* aStatus, + nsCString* aFailureId, bool* aSuccess) override; virtual bool RecvGraphicsError(const nsCString& aError) override; diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 1509c18609..14149ec67d 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -107,8 +107,8 @@ prio(normal upto urgent) sync protocol PBrowser manages PPluginWidget; both: - async AsyncMessage(nsString aMessage, ClonedMessageData aData, CpowEntry[] aCpows, - Principal aPrincipal); + async AsyncMessage(nsString aMessage, CpowEntry[] aCpows, + Principal aPrincipal, ClonedMessageData aData); /** * Create a layout frame (encapsulating a remote layer tree) for @@ -482,10 +482,6 @@ parent: */ async RemotePaintIsReady(); - sync GetRenderFrameInfo(PRenderFrame aRenderFrame) - returns (TextureFactoryIdentifier textureFactoryIdentifier, - uint64_t layersId); - /** * Sent by the child to the parent to inform it that an update to the * dimensions has been requested, likely through win.moveTo or resizeTo @@ -715,6 +711,13 @@ child: float aVolume, bool aMuted); + /** + * Tells the root child docShell whether or not to use + * global history. This is sent right after the PBrowser + * is bound to a frameloader element. + */ + async SetUseGlobalHistory(bool aUse); + /* * FIXME: write protocol! diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 3f72afedba..f7c163c498 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -21,7 +21,6 @@ include protocol PHandlerService; include protocol PDeviceStorageRequest; include protocol PFileDescriptorSet; include protocol PFMRadio; -include protocol PFileSystemRequest; include protocol PHal; include protocol PHeapSnapshotTempFileHelper; include protocol PIcc; @@ -42,6 +41,7 @@ include protocol PPluginModule; include protocol PGMP; include protocol PPrinting; include protocol POfflineCacheUpdate; +include protocol PRenderFrame; include protocol PScreenManager; include protocol PSharedBufferManager; include protocol PSms; @@ -96,6 +96,7 @@ using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h"; using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h"; using class mozilla::dom::ipc::StructuredCloneData from "ipc/IPCMessageUtils.h"; using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h"; +using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h"; union ChromeRegistryItem { @@ -266,67 +267,6 @@ union FMRadioRequestParams FMRadioRequestCancelSeekParams; }; -struct FileSystemCreateDirectoryParams -{ - nsString filesystem; - nsString realPath; -}; - -union FileSystemFileDataValue -{ - uint8_t[]; - PBlob; -}; - -struct FileSystemCreateFileParams -{ - nsString filesystem; - nsString realPath; - FileSystemFileDataValue data; - bool replace; -}; - -struct FileSystemGetDirectoryListingParams -{ - nsString filesystem; - nsString realPath; - bool isRoot; - // 'filters' could be an array rather than a semicolon separated string - // (we'd then use InfallibleTArray internally), but that is - // wasteful. E10s requires us to pass the filters over as a string anyway, - // so avoiding using an array avoids serialization on the side passing the - // filters. Since an nsString can share its buffer when copied, - // using that instead of InfallibleTArray makes copying the filters - // around in any given process a bit more efficient too, since copying a - // single nsString is cheaper than copying InfallibleTArray member data and - // each nsString that it contains. - nsString filters; -}; - -struct FileSystemGetFileOrDirectoryParams -{ - nsString filesystem; - nsString realPath; - bool isRoot; -}; - -struct FileSystemRemoveParams -{ - nsString filesystem; - nsString directory; - nsString targetDirectory; - bool recursive; -}; - -union FileSystemParams -{ - FileSystemCreateDirectoryParams; - FileSystemCreateFileParams; - FileSystemGetDirectoryListingParams; - FileSystemGetFileOrDirectoryParams; - FileSystemRemoveParams; -}; - union PrefValue { nsCString; int32_t; @@ -473,7 +413,6 @@ prio(normal upto urgent) sync protocol PContent manages PCrashReporter; manages PCycleCollectWithLogs; manages PDeviceStorageRequest; - manages PFileSystemRequest; manages PPSMContentDownloader; manages PExternalHelperApp; manages PFileDescriptorSet; @@ -730,6 +669,11 @@ child: */ async PushSubscriptionChange(nsCString scope, Principal principal); + /** + * Send a Push error message to all service worker clients in the child. + */ + async PushError(nsCString scope, nsString message, uint32_t flags); + /** * Windows specific: associate this content process with the browsers * audio session. @@ -811,13 +755,8 @@ parent: async PRemoteSpellcheckEngine(); async PDeviceStorageRequest(DeviceStorageParams params); - async PFileSystemRequest(FileSystemParams params); - sync PCrashReporter(NativeThreadId tid, uint32_t processType); - prio(urgent) sync GetRandomValues(uint32_t length) - returns (uint8_t[] randomValues); - async GetSystemMemory(uint64_t getterId); sync IsSecureURI(uint32_t type, URIParams uri, uint32_t flags) @@ -1008,7 +947,8 @@ parent: bool isAudio, bool isVideo); - sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, bool aSuccess); + sync GetGraphicsFeatureStatus(int32_t aFeature) returns (int32_t aStatus, nsCString aFailureCode, + bool aSuccess); // Graphics errors async GraphicsError(nsCString aError); @@ -1166,6 +1106,7 @@ parent: sync CreateWindow(nullable PBrowser aThisTab, PBrowser aNewTab, + PRenderFrame aRenderFrame, uint32_t aChromeFlags, bool aCalledFromJS, bool aPositionSpecified, @@ -1179,7 +1120,9 @@ parent: returns (nsresult rv, bool windowOpened, FrameScriptInfo[] frameScripts, - nsCString urlToLoad); + nsCString urlToLoad, + TextureFactoryIdentifier textureFactoryIdentifier, + uint64_t layersId); sync GetDeviceStorageLocation(nsString type) returns (nsString path); @@ -1198,8 +1141,8 @@ parent: */ sync UngrabPointer(uint32_t time); both: - async AsyncMessage(nsString aMessage, ClonedMessageData aData, - CpowEntry[] aCpows, Principal aPrincipal); + async AsyncMessage(nsString aMessage, CpowEntry[] aCpows, + Principal aPrincipal, ClonedMessageData aData); }; } diff --git a/dom/ipc/PContentBridge.ipdl b/dom/ipc/PContentBridge.ipdl index 5295f9c037..77833f94d9 100644 --- a/dom/ipc/PContentBridge.ipdl +++ b/dom/ipc/PContentBridge.ipdl @@ -51,8 +51,8 @@ both: async PBlob(BlobConstructorParams params); - async AsyncMessage(nsString aMessage, ClonedMessageData aData, - CpowEntry[] aCpows, Principal aPrincipal); + async AsyncMessage(nsString aMessage, CpowEntry[] aCpows, + Principal aPrincipal, ClonedMessageData aData); }; } diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 9788823ee4..bcc9ea74c6 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -2261,9 +2261,9 @@ TabChild::RecvLoadRemoteScript(const nsString& aURL, const bool& aRunInGlobalSco bool TabChild::RecvAsyncMessage(const nsString& aMessage, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { if (mTabChildGlobal) { nsCOMPtr kungFuDeathGrip(GetGlobal()); @@ -2356,6 +2356,20 @@ TabChild::RecvAudioChannelChangeNotification(const uint32_t& aAudioChannel, return true; } +bool +TabChild::RecvSetUseGlobalHistory(const bool& aUse) +{ + nsCOMPtr docShell = do_GetInterface(WebNavigation()); + MOZ_ASSERT(docShell); + + nsresult rv = docShell->SetUseGlobalHistory(aUse); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to set UseGlobalHistory on TabChild docShell"); + } + + return true; +} + bool TabChild::RecvDestroy() { @@ -2781,8 +2795,8 @@ TabChild::DoSendAsyncMessage(JSContext* aCx, if (aCpows && !Manager()->GetCPOWManager()->Wrap(aCx, aCpows, &cpows)) { return NS_ERROR_UNEXPECTED; } - if (!SendAsyncMessage(PromiseFlatString(aMessage), data, cpows, - Principal(aPrincipal))) { + if (!SendAsyncMessage(PromiseFlatString(aMessage), cpows, + Principal(aPrincipal), data)) { return NS_ERROR_UNEXPECTED; } return NS_OK; diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index 70d1a16c3d..53462589b4 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -402,9 +402,9 @@ public: const bool& aRunInGlobalScope) override; virtual bool RecvAsyncMessage(const nsString& aMessage, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) override; + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) override; virtual bool RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline) override; @@ -543,6 +543,8 @@ public: const float& aVolume, const bool& aMuted) override; + virtual bool RecvSetUseGlobalHistory(const bool& aUse) override; + /** * Native widget remoting protocol for use with windowed plugins with e10s. */ diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index f66b69859a..558daca49e 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -105,12 +105,6 @@ using namespace mozilla::services; using namespace mozilla::widget; using namespace mozilla::jsipc; -#if DEUBG - #define LOG(args...) printf_stderr(args) -#else - #define LOG(...) -#endif - // The flags passed by the webProgress notifications are 16 bits shifted // from the ones registered by webProgressListeners. #define NOTIFY_FLAG_SHIFT 16 @@ -367,6 +361,12 @@ TabParent::SetOwnerElement(Element* aElement) newTopLevelWin->AddBrowser(this); } + if (mFrameElement) { + bool useGlobalHistory = + !mFrameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::disableglobalhistory); + Unused << SendSetUseGlobalHistory(useGlobalHistory); + } + AddWindowListeners(); TryCacheDPIAndScale(); } @@ -449,105 +449,6 @@ TabParent::IsVisible() const return visible; } -static void LogChannelRelevantInfo(nsIURI* aURI, - nsIPrincipal* aLoadingPrincipal, - nsIPrincipal* aChannelResultPrincipal, - nsContentPolicyType aContentPolicyType) { - nsCString loadingOrigin; - aLoadingPrincipal->GetOrigin(loadingOrigin); - - nsCString uriString; - aURI->GetAsciiSpec(uriString); - LOG("Loading %s from origin %s (type: %d)\n", uriString.get(), - loadingOrigin.get(), - aContentPolicyType); - - nsCString resultPrincipalOrigin; - aChannelResultPrincipal->GetOrigin(resultPrincipalOrigin); - LOG("Result principal origin: %s\n", resultPrincipalOrigin.get()); -} - -bool -TabParent::ShouldSwitchProcess(nsIChannel* aChannel) -{ - // If we lack of any information which is required to decide the need of - // process switch, consider that we should switch process. - - // Prepare the channel loading principal. - nsCOMPtr loadInfo; - aChannel->GetLoadInfo(getter_AddRefs(loadInfo)); - NS_ENSURE_TRUE(loadInfo, true); - nsCOMPtr loadingPrincipal; - loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal)); - NS_ENSURE_TRUE(loadingPrincipal, true); - - // Prepare the channel result principal. - nsCOMPtr resultPrincipal; - nsContentUtils::GetSecurityManager()-> - GetChannelResultPrincipal(aChannel, getter_AddRefs(resultPrincipal)); - - // Log the debug info which is used to decide the need of proces switch. - nsCOMPtr uri; - aChannel->GetURI(getter_AddRefs(uri)); - LogChannelRelevantInfo(uri, loadingPrincipal, resultPrincipal, - loadInfo->InternalContentPolicyType()); - - // Check if the signed package is loaded from the same origin. - bool sameOrigin = false; - loadingPrincipal->Equals(resultPrincipal, &sameOrigin); - if (sameOrigin) { - LOG("Loading singed package from the same origin. Don't switch process.\n"); - return false; - } - - // If this is not a top level document, there's no need to switch process. - if (nsIContentPolicy::TYPE_DOCUMENT != loadInfo->InternalContentPolicyType()) { - LOG("Subresource of a document. No need to switch process.\n"); - return false; - } - - // If this is a brand new process created to load the signed package - // (triggered by previous OnStartSignedPackageRequest), the loading origin - // will be "moz-safe-about:blank". In that case, we don't need to switch process - // again. We compare with "moz-safe-about:blank" without appId/isBrowserElement/etc - // taken into account. That's why we use originNoSuffix. - nsCString loadingOrigin; - loadingPrincipal->GetOrigin(loadingOrigin); - if (loadingOrigin.EqualsLiteral("moz-safe-about:blank")) { - LOG("The content is already loaded by a brand new process.\n"); - return false; - } - - return true; -} - -void -TabParent::OnStartSignedPackageRequest(nsIChannel* aChannel, - const nsACString& aPackageId) -{ - if (!ShouldSwitchProcess(aChannel)) { - return; - } - - nsCOMPtr uri; - aChannel->GetURI(getter_AddRefs(uri)); - - aChannel->Cancel(NS_BINDING_FAILED); - - nsCString uriString; - uri->GetAsciiSpec(uriString); - LOG("We decide to switch process. Call nsFrameLoader::SwitchProcessAndLoadURIs: %s\n", - uriString.get()); - - RefPtr frameLoader = GetFrameLoader(); - NS_ENSURE_TRUE_VOID(frameLoader); - - nsresult rv = frameLoader->SwitchProcessAndLoadURI(uri, aPackageId); - if (NS_FAILED(rv)) { - NS_WARNING("Failed to switch process."); - } -} - void TabParent::DestroyInternal() { @@ -579,6 +480,10 @@ TabParent::DestroyInternal() void TabParent::Destroy() { + // Aggressively release the window to avoid leaking the world in shutdown + // corner cases. + mBrowserDOMWindow = nullptr; + if (mIsDestroyed) { return; } @@ -858,22 +763,24 @@ TabParent::Show(const ScreenIntSize& size, bool aParentIsActive) uint64_t layersId = 0; bool success = false; RenderFrameParent* renderFrame = nullptr; - // If TabParent is initialized by parent side then the RenderFrame must also - // be created here. If TabParent is initialized by child side, - // child side will create RenderFrame. - MOZ_ASSERT(!GetRenderFrame()); if (IsInitedByParent()) { + // If TabParent is initialized by parent side then the RenderFrame must also + // be created here. If TabParent is initialized by child side, + // child side will create RenderFrame. + MOZ_ASSERT(!GetRenderFrame()); RefPtr frameLoader = GetFrameLoader(); if (frameLoader) { - renderFrame = - new RenderFrameParent(frameLoader, - &textureFactoryIdentifier, - &layersId, - &success); + renderFrame = new RenderFrameParent(frameLoader, &success); MOZ_ASSERT(success); + layersId = renderFrame->GetLayersId(); + renderFrame->GetTextureFactoryIdentifier(&textureFactoryIdentifier); AddTabParentToTable(layersId, this); Unused << SendPRenderFrameConstructor(renderFrame); } + } else { + // Otherwise, the child should have constructed the RenderFrame, + // and we should already know about it. + MOZ_ASSERT(GetRenderFrame()); } nsCOMPtr container = mFrameElement->OwnerDoc()->GetContainer(); @@ -1707,9 +1614,9 @@ TabParent::RecvRpcMessage(const nsString& aMessage, bool TabParent::RecvAsyncMessage(const nsString& aMessage, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { // FIXME Permission check for TabParent in Content process nsIPrincipal* principal = aPrincipal; @@ -2087,9 +1994,8 @@ TabParent::RecvReplyKeyEvent(const WidgetKeyboardEvent& event) NS_ENSURE_TRUE(mFrameElement, true); WidgetKeyboardEvent localEvent(event); - // Set mNoCrossProcessBoundaryForwarding to avoid this event from - // being infinitely redispatched and forwarded to the child again. - localEvent.mFlags.mNoCrossProcessBoundaryForwarding = true; + // Mark the event as not to be dispatched to remote process again. + localEvent.StopCrossProcessForwarding(); // Here we convert the WidgetEvent that we received to an nsIDOMEvent // to be able to dispatch it to the element as the target element. @@ -2119,7 +2025,7 @@ TabParent::RecvDispatchAfterKeyboardEvent(const WidgetKeyboardEvent& aEvent) PresShell::BeforeAfterKeyboardEventEnabled() && localEvent.mMessage != eKeyPress) { presShell->DispatchAfterKeyboardEvent(mFrameElement, localEvent, - aEvent.mFlags.mDefaultPrevented); + aEvent.DefaultPrevented()); } return true; @@ -2529,16 +2435,14 @@ TabParent::AllocPRenderFrameParent() { MOZ_ASSERT(ManagedPRenderFrameParent().IsEmpty()); RefPtr frameLoader = GetFrameLoader(); - TextureFactoryIdentifier textureFactoryIdentifier; uint64_t layersId = 0; bool success = false; PRenderFrameParent* renderFrame = - new RenderFrameParent(frameLoader, - &textureFactoryIdentifier, - &layersId, - &success); + new RenderFrameParent(frameLoader, &success); if (success) { + RenderFrameParent* rfp = static_cast(renderFrame); + layersId = rfp->GetLayersId(); AddTabParentToTable(layersId, this); } return renderFrame; @@ -2552,13 +2456,26 @@ TabParent::DeallocPRenderFrameParent(PRenderFrameParent* aFrame) } bool -TabParent::RecvGetRenderFrameInfo(PRenderFrameParent* aRenderFrame, - TextureFactoryIdentifier* aTextureFactoryIdentifier, - uint64_t* aLayersId) +TabParent::SetRenderFrame(PRenderFrameParent* aRFParent) { - RenderFrameParent* renderFrame = static_cast(aRenderFrame); - renderFrame->GetTextureFactoryIdentifier(aTextureFactoryIdentifier); - *aLayersId = renderFrame->GetLayersId(); + if (IsInitedByParent()) { + return false; + } + + RefPtr frameLoader = GetFrameLoader(); + + if (!frameLoader) { + return false; + } + + RenderFrameParent* renderFrame = static_cast(aRFParent); + bool success = renderFrame->Init(frameLoader); + if (!success) { + return false; + } + + uint64_t layersId = renderFrame->GetLayersId(); + AddTabParentToTable(layersId, this); if (mNeedLayerTreeReadyNotification) { RequestNotifyLayerTreeReady(); @@ -2568,6 +2485,20 @@ TabParent::RecvGetRenderFrameInfo(PRenderFrameParent* aRenderFrame, return true; } +bool +TabParent::GetRenderFrameInfo(TextureFactoryIdentifier* aTextureFactoryIdentifier, + uint64_t* aLayersId) +{ + RenderFrameParent* rfp = GetRenderFrame(); + if (!rfp) { + return false; + } + + *aLayersId = rfp->GetLayersId(); + rfp->GetTextureFactoryIdentifier(aTextureFactoryIdentifier); + return true; +} + bool TabParent::RecvAudioChannelActivityNotification(const uint32_t& aAudioChannel, const bool& aActive) @@ -2871,7 +2802,7 @@ bool TabParent::RequestNotifyLayerTreeReady() { RenderFrameParent* frame = GetRenderFrame(); - if (!frame) { + if (!frame || !frame->IsInitted()) { mNeedLayerTreeReadyNotification = true; } else { CompositorBridgeParent::RequestNotifyLayerTreeReady( diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index bdca5edb5b..ce0e3a2e83 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -183,9 +183,9 @@ public: nsTArray* aRetVal) override; virtual bool RecvAsyncMessage(const nsString& aMessage, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) override; + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) override; virtual bool RecvNotifyIMEFocus(const ContentCache& aContentCache, @@ -540,15 +540,13 @@ public: layout::RenderFrameParent* GetRenderFrame(); - // Called by HttpChannelParent. The function may use a new process to - // reload the URI associated with the given channel. - void OnStartSignedPackageRequest(nsIChannel* aChannel, - const nsACString& aPackageId); - void AudioChannelChangeNotification(nsPIDOMWindow* aWindow, AudioChannel aAudioChannel, float aVolume, bool aMuted); + bool SetRenderFrame(PRenderFrameParent* aRFParent); + bool GetRenderFrameInfo(TextureFactoryIdentifier* aTextureFactoryIdentifier, + uint64_t* aLayersId); protected: bool ReceiveMessage(const nsString& aMessage, @@ -575,10 +573,6 @@ protected: virtual bool RecvRemotePaintIsReady() override; - virtual bool RecvGetRenderFrameInfo(PRenderFrameParent* aRenderFrame, - TextureFactoryIdentifier* aTextureFactoryIdentifier, - uint64_t* aLayersId) override; - virtual bool RecvSetDimensions(const uint32_t& aFlags, const int32_t& aX, const int32_t& aY, const int32_t& aCx, const int32_t& aCy) override; @@ -589,10 +583,6 @@ protected: bool InitBrowserConfiguration(const nsCString& aURI, BrowserConfiguration& aConfiguration); - // Decide whether we have to use a new process to reload the URI associated - // with the given channel. - bool ShouldSwitchProcess(nsIChannel* aChannel); - ContentCacheInParent mContentCache; nsIntRect mRect; diff --git a/dom/ipc/nsIContentChild.cpp b/dom/ipc/nsIContentChild.cpp index d78b4bd328..0bf80cb558 100644 --- a/dom/ipc/nsIContentChild.cpp +++ b/dom/ipc/nsIContentChild.cpp @@ -111,9 +111,9 @@ nsIContentChild::GetOrCreateActorForBlobImpl(BlobImpl* aImpl) bool nsIContentChild::RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { RefPtr cpm = nsFrameMessageManager::GetChildProcessManager(); if (cpm) { diff --git a/dom/ipc/nsIContentChild.h b/dom/ipc/nsIContentChild.h index b1f873cfe8..509b9f237d 100644 --- a/dom/ipc/nsIContentChild.h +++ b/dom/ipc/nsIContentChild.h @@ -80,9 +80,9 @@ protected: virtual bool DeallocPBlobChild(PBlobChild* aActor); virtual bool RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal); + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData); }; NS_DEFINE_STATIC_IID_ACCESSOR(nsIContentChild, NS_ICONTENTCHILD_IID) diff --git a/dom/ipc/nsIContentParent.cpp b/dom/ipc/nsIContentParent.cpp index 78ec33c09c..7a1cd72a4c 100644 --- a/dom/ipc/nsIContentParent.cpp +++ b/dom/ipc/nsIContentParent.cpp @@ -269,9 +269,9 @@ nsIContentParent::RecvRpcMessage(const nsString& aMsg, bool nsIContentParent::RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal) + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData) { // FIXME Permission check in Content process nsIPrincipal* principal = aPrincipal; diff --git a/dom/ipc/nsIContentParent.h b/dom/ipc/nsIContentParent.h index f881c8aa10..b0fc0f153f 100644 --- a/dom/ipc/nsIContentParent.h +++ b/dom/ipc/nsIContentParent.h @@ -110,9 +110,9 @@ protected: // IPDL methods const IPC::Principal& aPrincipal, nsTArray* aRetvals); virtual bool RecvAsyncMessage(const nsString& aMsg, - const ClonedMessageData& aData, InfallibleTArray&& aCpows, - const IPC::Principal& aPrincipal); + const IPC::Principal& aPrincipal, + const ClonedMessageData& aData); protected: // members RefPtr mMessageManager; diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index f7c04c28ce..8bef829a82 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -196,3 +196,5 @@ TargetPrincipalDoesNotMatch=Failed to execute 'postMessage' on 'DOMWindow': The RewriteYoutubeEmbed=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible. # LOCALIZATION NOTE: Do not translate 'youtube'. %S values are origins, like https://domain.com:port RewriteYoutubeEmbedInvalidQuery=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Query was invalid and removed from URL. Please update page to use iframe instead of embed/object, if possible. +# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string. +PushMessageDecryptionFailure=The ServiceWorker for scope '%1$S' encountered an error decrypting a push message: '%2$S'. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption diff --git a/dom/media/android/AndroidMediaPluginHost.cpp b/dom/media/android/AndroidMediaPluginHost.cpp index f42a0e209c..02f16bbc76 100644 --- a/dom/media/android/AndroidMediaPluginHost.cpp +++ b/dom/media/android/AndroidMediaPluginHost.cpp @@ -111,7 +111,8 @@ static bool IsOmxSupported() nsCOMPtr gfxInfo = services::GetGfxInfo(); if (gfxInfo) { int32_t status; - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_STAGEFRIGHT, &status))) { + nsCString discardFailure; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_STAGEFRIGHT, discardFailure, &status))) { if (status != nsIGfxInfo::FEATURE_STATUS_OK) { NS_WARNING("XXX stagefright blacklisted\n"); return false; diff --git a/dom/media/bridge/MediaModule.cpp b/dom/media/bridge/MediaModule.cpp index e0b9617c42..92f57a70c5 100644 --- a/dom/media/bridge/MediaModule.cpp +++ b/dom/media/bridge/MediaModule.cpp @@ -14,11 +14,13 @@ #define PEERCONNECTION_CONTRACTID "@mozilla.org/peerconnection;1" -#include "stun_udp_socket_filter.h" +#include "stun_socket_filter.h" NS_DEFINE_NAMED_CID(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CID) +NS_DEFINE_NAMED_CID(NS_STUN_TCP_SOCKET_FILTER_HANDLER_CID) NS_GENERIC_FACTORY_CONSTRUCTOR(nsStunUDPSocketFilterHandler) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsStunTCPSocketFilterHandler) namespace mozilla @@ -33,12 +35,14 @@ NS_DEFINE_NAMED_CID(PEERCONNECTION_CID); static const mozilla::Module::CIDEntry kCIDs[] = { { &kPEERCONNECTION_CID, false, nullptr, mozilla::PeerConnectionImplConstructor }, { &kNS_STUN_UDP_SOCKET_FILTER_HANDLER_CID, false, nullptr, nsStunUDPSocketFilterHandlerConstructor }, + { &kNS_STUN_TCP_SOCKET_FILTER_HANDLER_CID, false, nullptr, nsStunTCPSocketFilterHandlerConstructor }, { nullptr } }; static const mozilla::Module::ContractIDEntry kContracts[] = { { PEERCONNECTION_CONTRACTID, &kPEERCONNECTION_CID }, { NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID, &kNS_STUN_UDP_SOCKET_FILTER_HANDLER_CID }, + { NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID, &kNS_STUN_TCP_SOCKET_FILTER_HANDLER_CID }, { nullptr } }; diff --git a/dom/media/gmp/GMPProcessParent.cpp b/dom/media/gmp/GMPProcessParent.cpp index 14b24627ff..5782e7b202 100644 --- a/dom/media/gmp/GMPProcessParent.cpp +++ b/dom/media/gmp/GMPProcessParent.cpp @@ -8,6 +8,9 @@ #include "GMPUtils.h" #include "nsIFile.h" #include "nsIRunnable.h" +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +#include "WinUtils.h" +#endif #include "base/string_util.h" #include "base/process_util.h" @@ -55,14 +58,26 @@ GMPProcessParent::Launch(int32_t aTimeoutMs) path->GetNativePath(voucherPath); vector args; - args.push_back(mGMPPath); - args.push_back(string(voucherPath.BeginReading(), voucherPath.EndReading())); #if defined(XP_WIN) && defined(MOZ_SANDBOX) std::wstring wGMPPath = UTF8ToWide(mGMPPath.c_str()); + + // The sandbox doesn't allow file system rules where the paths contain + // symbolic links or junction points. Sometimes the Users folder has been + // moved to another drive using a junction point, so allow for this specific + // case. See bug 1236680 for details. + if (!widget::WinUtils::ResolveMovedUsersFolder(wGMPPath)) { + NS_WARNING("ResolveMovedUsersFolder failed for GMP path."); + return false; + } mAllowedFilesRead.push_back(wGMPPath + L"\\*"); + args.push_back(WideToUTF8(wGMPPath)); +#else + args.push_back(mGMPPath); #endif + args.push_back(string(voucherPath.BeginReading(), voucherPath.EndReading())); + return SyncLaunch(args, aTimeoutMs, base::GetCurrentProcessArchitecture()); } diff --git a/dom/media/platforms/android/AndroidDecoderModule.cpp b/dom/media/platforms/android/AndroidDecoderModule.cpp index e5faa7bb20..8991bd0ab5 100644 --- a/dom/media/platforms/android/AndroidDecoderModule.cpp +++ b/dom/media/platforms/android/AndroidDecoderModule.cpp @@ -13,10 +13,12 @@ #include "MediaData.h" #include "MediaInfo.h" +#include "VPXDecoder.h" #include "nsThreadUtils.h" #include "nsAutoPtr.h" #include "nsPromiseFlatString.h" +#include "nsIGfxInfo.h" #include "prlog.h" @@ -53,9 +55,9 @@ namespace mozilla { static const char* TranslateMimeType(const nsACString& aMimeType) { - if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) { + if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8)) { return "video/x-vnd.on2.vp8"; - } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) { + } else if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9)) { return "video/x-vnd.on2.vp9"; } return PromiseFlatCString(aMimeType).get(); @@ -70,6 +72,18 @@ CreateDecoder(const nsACString& aMimeType) return codec; } +static bool +GetFeatureStatus(int32_t aFeature) +{ + nsCOMPtr gfxInfo = services::GetGfxInfo(); + int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN; + nsCString discardFailureId; + if (!gfxInfo || NS_FAILED(gfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) { + return false; + } + return status == nsIGfxInfo::FEATURE_STATUS_OK; +}; + class VideoDataDecoder : public MediaCodecDataDecoder { public: @@ -331,6 +345,13 @@ AndroidDecoderModule::SupportsMimeType(const nsACString& aMimeType) const return false; } + if ((VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8) && + !GetFeatureStatus(nsIGfxInfo::FEATURE_VP8_HW_DECODE)) || + (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9) && + !GetFeatureStatus(nsIGfxInfo::FEATURE_VP9_HW_DECODE))) { + return false; + } + return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType( nsCString(TranslateMimeType(aMimeType))); } diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp index 8fd3d5f849..d8558f766f 100644 --- a/dom/messagechannel/MessagePort.cpp +++ b/dom/messagechannel/MessagePort.cpp @@ -47,7 +47,7 @@ using namespace mozilla::dom::workers; namespace mozilla { namespace dom { -class PostMessageRunnable final : public nsCancelableRunnable +class PostMessageRunnable final : public CancelableRunnable { friend class MessagePort; diff --git a/dom/network/PTCPSocket.ipdl b/dom/network/PTCPSocket.ipdl index 76193ddd23..5c9c1c862e 100644 --- a/dom/network/PTCPSocket.ipdl +++ b/dom/network/PTCPSocket.ipdl @@ -45,7 +45,7 @@ parent: // address specified in |localAddr| and |localPort|. async OpenBind(nsCString host, uint16_t port, nsCString localAddr, uint16_t localPort, - bool useSSL, bool aUseArrayBuffers); + bool useSSL, bool aUseArrayBuffers, nsCString aFilter); // When child's send() is called, this message requrests parent to send // data and update it's trackingNumber. diff --git a/dom/network/TCPSocketChild.cpp b/dom/network/TCPSocketChild.cpp index 2de5ecfbe8..280c585525 100644 --- a/dom/network/TCPSocketChild.cpp +++ b/dom/network/TCPSocketChild.cpp @@ -102,6 +102,7 @@ TCPSocketChild::SendOpen(nsITCPSocketCallback* aSocket, bool aUseSSL, bool aUseA AddIPDLReference(); gNeckoChild->SendPTCPSocketConstructor(this, mHost, mPort); + MOZ_ASSERT(mFilterName.IsEmpty()); // Currently nobody should use this PTCPSocketChild::SendOpen(mHost, mPort, aUseSSL, aUseArrayBuffers); } @@ -118,7 +119,7 @@ TCPSocketChild::SendWindowlessOpenBind(nsITCPSocketCallback* aSocket, aRemotePort); PTCPSocketChild::SendOpenBind(nsCString(aRemoteHost), aRemotePort, nsCString(aLocalHost), aLocalPort, - aUseSSL, true); + aUseSSL, true, mFilterName); } void @@ -230,6 +231,17 @@ TCPSocketChild::GetPort(uint16_t* aPort) *aPort = mPort; } +nsresult +TCPSocketChild::SetFilterName(const nsACString& aFilterName) +{ + if (!mFilterName.IsEmpty()) { + // filter name can only be set once. + return NS_ERROR_FAILURE; + } + mFilterName = aFilterName; + return NS_OK; +} + bool TCPSocketChild::RecvRequestDelete() { diff --git a/dom/network/TCPSocketChild.h b/dom/network/TCPSocketChild.h index 1adbe90d1d..b36a193dea 100644 --- a/dom/network/TCPSocketChild.h +++ b/dom/network/TCPSocketChild.h @@ -76,9 +76,11 @@ public: virtual bool RecvRequestDelete() override; virtual bool RecvUpdateBufferedAmount(const uint32_t& aBufferred, const uint32_t& aTrackingNumber) override; + nsresult SetFilterName(const nsACString& aFilterName); private: nsString mHost; uint16_t mPort; + nsCString mFilterName; }; } // namespace dom diff --git a/dom/network/TCPSocketParent.cpp b/dom/network/TCPSocketParent.cpp index eca8b03b20..22398f2644 100644 --- a/dom/network/TCPSocketParent.cpp +++ b/dom/network/TCPSocketParent.cpp @@ -21,6 +21,13 @@ #include "nsIScriptSecurityManager.h" #include "nsNetUtil.h" +// +// set NSPR_LOG_MODULES=TCPSocket:5 +// +extern mozilla::LazyLogModule gTCPSocketLog; +#define TCPSOCKET_LOG(args) MOZ_LOG(gTCPSocketLog, mozilla::LogLevel::Debug, args) +#define TCPSOCKET_LOG_ENABLED() MOZ_LOG_TEST(gTCPSocketLog, mozilla::LogLevel::Debug) + namespace IPC { //Defined in TCPSocketChild.cpp @@ -176,7 +183,8 @@ TCPSocketParent::RecvOpenBind(const nsCString& aRemoteHost, const nsCString& aLocalAddr, const uint16_t& aLocalPort, const bool& aUseSSL, - const bool& aUseArrayBuffers) + const bool& aUseArrayBuffers, + const nsCString& aFilter) { if (net::UsingNeckoIPCSecurity() && !AssertAppProcessPermission(Manager()->Manager(), "tcp-socket")) { @@ -219,6 +227,24 @@ TCPSocketParent::RecvOpenBind(const nsCString& aRemoteHost, return true; } + if (!aFilter.IsEmpty()) { + nsAutoCString contractId(NS_NETWORK_TCP_SOCKET_FILTER_HANDLER_PREFIX); + contractId.Append(aFilter); + nsCOMPtr filterHandler = + do_GetService(contractId.get()); + if (!filterHandler) { + NS_ERROR("Content doesn't have a valid filter"); + FireInteralError(this, __LINE__); + return true; + } + rv = filterHandler->NewFilter(getter_AddRefs(mFilter)); + if (NS_FAILED(rv)) { + NS_ERROR("Cannot create filter that content specified"); + FireInteralError(this, __LINE__); + return true; + } + } + // Obtain App ID uint32_t appId = nsIScriptSecurityManager::NO_APP_ID; bool inBrowser = false; @@ -272,6 +298,25 @@ TCPSocketParent::RecvData(const SendableData& aData, const uint32_t& aTrackingNumber) { ErrorResult rv; + + if (mFilter) { + mozilla::net::NetAddr addr; // dummy value + bool allowed; + MOZ_ASSERT(aData.type() == SendableData::TArrayOfuint8_t, + "Unsupported data type for filtering"); + const InfallibleTArray& data(aData.get_ArrayOfuint8_t()); + nsresult nsrv = mFilter->FilterPacket(&addr, data.Elements(), + data.Length(), + nsISocketFilter::SF_OUTGOING, + &allowed); + + // Reject sending of unallowed data + if (NS_WARN_IF(NS_FAILED(nsrv)) || !allowed) { + TCPSOCKET_LOG(("%s: Dropping outgoing TCP packet", __FUNCTION__)); + return false; + } + } + switch (aData.type()) { case SendableData::TArrayOfuint8_t: { AutoSafeJSContext autoCx; @@ -324,6 +369,20 @@ TCPSocketParent::FireArrayBufferDataEvent(nsTArray& aBuffer, TCPReadySt { InfallibleTArray arr; arr.SwapElements(aBuffer); + + if (mFilter) { + bool allowed; + mozilla::net::NetAddr addr; + nsresult nsrv = mFilter->FilterPacket(&addr, arr.Elements(), arr.Length(), + nsISocketFilter::SF_INCOMING, + &allowed); + // receiving unallowed data, drop it. + if (NS_WARN_IF(NS_FAILED(nsrv)) || !allowed) { + TCPSOCKET_LOG(("%s: Dropping incoming TCP packet", __FUNCTION__)); + return; + } + } + SendableData data(arr); SendEvent(NS_LITERAL_STRING("data"), data, aReadyState); } @@ -331,7 +390,11 @@ TCPSocketParent::FireArrayBufferDataEvent(nsTArray& aBuffer, TCPReadySt void TCPSocketParent::FireStringDataEvent(const nsACString& aData, TCPReadyState aReadyState) { - SendEvent(NS_LITERAL_STRING("data"), SendableData(nsCString(aData)), aReadyState); + SendableData data((nsCString(aData))); + + MOZ_ASSERT(!mFilter, "Socket filtering doesn't support nsCString"); + + SendEvent(NS_LITERAL_STRING("data"), data, aReadyState); } void diff --git a/dom/network/TCPSocketParent.h b/dom/network/TCPSocketParent.h index 33e38865bf..b05225dc4a 100644 --- a/dom/network/TCPSocketParent.h +++ b/dom/network/TCPSocketParent.h @@ -11,6 +11,7 @@ #include "mozilla/net/PTCPSocketParent.h" #include "nsCycleCollectionParticipant.h" #include "nsCOMPtr.h" +#include "nsISocketFilter.h" #include "js/TypeDecls.h" #include "mozilla/net/OfflineObserver.h" @@ -57,7 +58,8 @@ public: const nsCString& aLocalAddr, const uint16_t& aLocalPort, const bool& aUseSSL, - const bool& aUseArrayBuffers) override; + const bool& aUseArrayBuffers, + const nsCString& aFilter) override; virtual bool RecvStartTLS() override; virtual bool RecvSuspend() override; @@ -82,6 +84,9 @@ public: private: virtual void ActorDestroy(ActorDestroyReason why) override; void SendEvent(const nsAString& aType, CallbackData aData, TCPReadyState aReadyState); + nsresult SetFilter(const nsCString& aFilter); + + nsCOMPtr mFilter; }; } // namespace dom diff --git a/dom/network/UDPSocketParent.cpp b/dom/network/UDPSocketParent.cpp index 421e1247f1..61719b0773 100644 --- a/dom/network/UDPSocketParent.cpp +++ b/dom/network/UDPSocketParent.cpp @@ -139,7 +139,7 @@ UDPSocketParent::Init(const IPC::Principal& aPrincipal, if (!aFilter.IsEmpty()) { nsAutoCString contractId(NS_NETWORK_UDP_SOCKET_FILTER_HANDLER_PREFIX); contractId.Append(aFilter); - nsCOMPtr filterHandler = + nsCOMPtr filterHandler = do_GetService(contractId.get()); if (filterHandler) { nsresult rv = filterHandler->NewFilter(getter_AddRefs(mFilter)); @@ -388,8 +388,6 @@ UDPSocketParent::RecvOutgoingData(const UDPData& aData, nsresult rv; if (mFilter) { - // TODO, Bug 933102, filter packets that are sent with hostname. - // Until then we simply throw away packets that are sent to a hostname. if (aAddr.type() != UDPSocketAddr::TNetAddr) { return true; } @@ -402,7 +400,7 @@ UDPSocketParent::RecvOutgoingData(const UDPData& aData, bool allowed; const InfallibleTArray& data(aData.get_ArrayOfuint8_t()); rv = mFilter->FilterPacket(&aAddr.get_NetAddr(), data.Elements(), - data.Length(), nsIUDPSocketFilter::SF_OUTGOING, + data.Length(), nsISocketFilter::SF_OUTGOING, &allowed); // Sending unallowed data, kill content. @@ -577,7 +575,7 @@ UDPSocketParent::OnPacketReceived(nsIUDPSocket* aSocket, nsIUDPMessage* aMessage fromAddr->GetNetAddr(&addr); nsresult rv = mFilter->FilterPacket(&addr, (const uint8_t*)buffer, len, - nsIUDPSocketFilter::SF_INCOMING, + nsISocketFilter::SF_INCOMING, &allowed); // Receiving unallowed data, drop. if (NS_WARN_IF(NS_FAILED(rv)) || !allowed) { diff --git a/dom/network/UDPSocketParent.h b/dom/network/UDPSocketParent.h index 04f70abd32..a7b852ddb2 100644 --- a/dom/network/UDPSocketParent.h +++ b/dom/network/UDPSocketParent.h @@ -10,7 +10,7 @@ #include "mozilla/net/PUDPSocketParent.h" #include "nsCOMPtr.h" #include "nsIUDPSocket.h" -#include "nsIUDPSocketFilter.h" +#include "nsISocketFilter.h" #include "mozilla/net/OfflineObserver.h" #include "mozilla/dom/PermissionMessageUtils.h" @@ -76,7 +76,7 @@ private: bool mIPCOpen; nsCOMPtr mSocket; - nsCOMPtr mFilter; + nsCOMPtr mFilter; RefPtr mObserver; nsCOMPtr mPrincipal; }; diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index 973749d09a..7709593c33 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -1688,7 +1688,7 @@ nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent) WidgetEvent* theEvent = aFocusEvent->WidgetEventPtr(); if (theEvent) { - WidgetGUIEvent focusEvent(theEvent->mFlags.mIsTrusted, theEvent->mMessage, + WidgetGUIEvent focusEvent(theEvent->IsTrusted(), theEvent->mMessage, nullptr); nsEventStatus rv = ProcessEvent(focusEvent); if (nsEventStatus_eConsumeNoDefault == rv) { @@ -2047,7 +2047,7 @@ nsPluginInstanceOwner::HandleEvent(nsIDOMEvent* aEvent) nsCOMPtr dragEvent(do_QueryInterface(aEvent)); if (dragEvent && mInstance) { WidgetEvent* ievent = aEvent->WidgetEventPtr(); - if (ievent && ievent->mFlags.mIsTrusted && + if (ievent && ievent->IsTrusted() && ievent->mMessage != eDragEnter && ievent->mMessage != eDragOver) { aEvent->PreventDefault(); } diff --git a/dom/plugins/ipc/BrowserStreamChild.cpp b/dom/plugins/ipc/BrowserStreamChild.cpp index 053fd5a5f5..d7cb626974 100644 --- a/dom/plugins/ipc/BrowserStreamChild.cpp +++ b/dom/plugins/ipc/BrowserStreamChild.cpp @@ -81,8 +81,8 @@ BrowserStreamChild::~BrowserStreamChild() bool BrowserStreamChild::RecvWrite(const int32_t& offset, - const Buffer& data, - const uint32_t& newlength) + const uint32_t& newlength, + const Buffer& data) { PLUGIN_LOG_DEBUG_FUNCTION; diff --git a/dom/plugins/ipc/BrowserStreamChild.h b/dom/plugins/ipc/BrowserStreamChild.h index 7808442be7..77f7fb813e 100644 --- a/dom/plugins/ipc/BrowserStreamChild.h +++ b/dom/plugins/ipc/BrowserStreamChild.h @@ -34,8 +34,8 @@ public: uint16_t* stype); virtual bool RecvWrite(const int32_t& offset, - const Buffer& data, - const uint32_t& newsize) override; + const uint32_t& newsize, + const Buffer& data) override; virtual bool RecvNPP_StreamAsFile(const nsCString& fname) override; virtual bool RecvNPP_DestroyStream(const NPReason& reason) override; virtual bool Recv__delete__() override; diff --git a/dom/plugins/ipc/BrowserStreamParent.cpp b/dom/plugins/ipc/BrowserStreamParent.cpp index e82ba161ad..c3c35d5a8d 100644 --- a/dom/plugins/ipc/BrowserStreamParent.cpp +++ b/dom/plugins/ipc/BrowserStreamParent.cpp @@ -193,8 +193,8 @@ BrowserStreamParent::Write(int32_t offset, len = kSendDataChunk; return SendWrite(offset, - nsCString(static_cast(buffer), len), - mStream->end) ? + mStream->end, + nsCString(static_cast(buffer), len)) ? len : -1; } diff --git a/dom/plugins/ipc/PBrowserStream.ipdl b/dom/plugins/ipc/PBrowserStream.ipdl index 8d927d60ee..dbd238750c 100644 --- a/dom/plugins/ipc/PBrowserStream.ipdl +++ b/dom/plugins/ipc/PBrowserStream.ipdl @@ -24,8 +24,8 @@ intr protocol PBrowserStream manager PPluginInstance; child: - async Write(int32_t offset, Buffer data, - uint32_t newlength); + async Write(int32_t offset, uint32_t newlength, + Buffer data); async NPP_StreamAsFile(nsCString fname); /** diff --git a/dom/promise/PromiseDebugging.cpp b/dom/promise/PromiseDebugging.cpp index 92d181f649..f12bf0f3be 100644 --- a/dom/promise/PromiseDebugging.cpp +++ b/dom/promise/PromiseDebugging.cpp @@ -21,7 +21,7 @@ namespace mozilla { namespace dom { -class FlushRejections: public nsCancelableRunnable +class FlushRejections: public CancelableRunnable { public: static void Init() { diff --git a/dom/push/Push.js b/dom/push/Push.js index 0c148d96d1..db2fe0d596 100644 --- a/dom/push/Push.js +++ b/dom/push/Push.js @@ -56,8 +56,7 @@ Push.prototype = { this._principal = aWindow.document.nodePrincipal; }, - setScope: function(scope){ - console.debug("setScope()", scope); + __init: function(scope) { this._scope = scope; }, @@ -203,7 +202,6 @@ PushSubscriptionCallback.prototype = { pushManager._scope, publicKey, authSecret); - sub.setPrincipal(pushManager._principal); this.resolve(sub); }, diff --git a/dom/push/Push.manifest b/dom/push/Push.manifest index df34889bb9..1d467d821a 100644 --- a/dom/push/Push.manifest +++ b/dom/push/Push.manifest @@ -6,3 +6,6 @@ contract @mozilla.org/push/PushManager;1 {cde1d019-fad8-4044-b141-65fb4fb7a245} component {daaa8d73-677e-4233-8acd-2c404bd01658} PushComponents.js contract @mozilla.org/push/Service;1 {daaa8d73-677e-4233-8acd-2c404bd01658} category app-startup PushServiceParent @mozilla.org/push/Service;1 + +# For immediate loading of PushService instead of delayed loading. +category android-push-service PushServiceParent @mozilla.org/push/Service;1 diff --git a/dom/push/PushComponents.js b/dom/push/PushComponents.js index dd1f9cf03b..fdec70457a 100644 --- a/dom/push/PushComponents.js +++ b/dom/push/PushComponents.js @@ -16,6 +16,14 @@ Cu.import("resource://gre/modules/Services.jsm"); var isParent = Services.appinfo.processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; +// The default Push service implementation. +XPCOMUtils.defineLazyGetter(this, "PushService", function() { + const {PushService} = Cu.import("resource://gre/modules/PushService.jsm", + {}); + PushService.init(); + return PushService; +}); + // Observer notification topics for system subscriptions. These are duplicated // and used in `PushNotifier.cpp`. They're exposed on `nsIPushService` instead // of `nsIPushNotifier` so that JS callers only need to import this service. @@ -47,6 +55,7 @@ PushServiceBase.prototype = { Ci.nsISupportsWeakReference, Ci.nsIPushService, Ci.nsIPushQuotaManager, + Ci.nsIPushErrorReporter, ]), pushTopic: OBSERVER_TOPIC_PUSH, @@ -74,6 +83,11 @@ PushServiceBase.prototype = { this._handleReady(); return; } + if (topic === "android-push-service") { + // Load PushService immediately. + this._handleReady(); + return; + } }, _deliverSubscription(request, props) { @@ -83,6 +97,12 @@ PushServiceBase.prototype = { } request.onPushSubscription(Cr.NS_OK, new PushSubscription(props)); }, + + _deliverSubscriptionError(request, error) { + let result = typeof error.result == "number" ? + error.result : Cr.NS_ERROR_FAILURE; + request.onPushSubscription(result, null); + }, }; /** @@ -99,14 +119,6 @@ PushServiceParent.prototype = Object.create(PushServiceBase.prototype); XPCOMUtils.defineLazyServiceGetter(PushServiceParent.prototype, "_mm", "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster"); -XPCOMUtils.defineLazyGetter(PushServiceParent.prototype, "_service", - function() { - const {PushService} = Cu.import("resource://gre/modules/PushService.jsm", - {}); - PushService.init(); - return PushService; -}); - Object.assign(PushServiceParent.prototype, { _xpcom_factory: XPCOMUtils.generateSingletonFactory(PushServiceParent), @@ -117,17 +129,23 @@ Object.assign(PushServiceParent.prototype, { "Push:Clear", "Push:NotificationForOriginShown", "Push:NotificationForOriginClosed", + "Push:ReportError", ], // nsIPushService methods subscribe(scope, principal, callback) { - return this._handleRequest("Push:Register", principal, { + this.subscribeWithKey(scope, principal, 0, null, callback); + }, + + subscribeWithKey(scope, principal, keyLen, key, callback) { + this._handleRequest("Push:Register", principal, { scope: scope, + appServerKey: key, }).then(result => { this._deliverSubscription(callback, result); }, error => { - callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null); + this._deliverSubscriptionError(callback, error); }).catch(Cu.reportError); }, @@ -147,7 +165,7 @@ Object.assign(PushServiceParent.prototype, { }).then(result => { this._deliverSubscription(callback, result); }, error => { - callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null); + this._deliverSubscriptionError(callback, error); }).catch(Cu.reportError); }, @@ -164,11 +182,17 @@ Object.assign(PushServiceParent.prototype, { // nsIPushQuotaManager methods notificationForOriginShown(origin) { - this._service.notificationForOriginShown(origin); + this.service.notificationForOriginShown(origin); }, notificationForOriginClosed(origin) { - this._service.notificationForOriginClosed(origin); + this.service.notificationForOriginClosed(origin); + }, + + // nsIPushErrorReporter methods + + reportDeliveryError(messageId, reason) { + this.service.reportDeliveryError(messageId, reason); }, receiveMessage(message) { @@ -187,6 +211,10 @@ Object.assign(PushServiceParent.prototype, { if (!target.assertPermission("push")) { return; } + if (name === "Push:ReportError") { + this.reportDeliveryError(data.messageId, data.reason); + return; + } let sender = target.QueryInterface(Ci.nsIMessageSender); return this._handleRequest(name, principal, data).then(result => { sender.sendAsyncMessage(this._getResponseName(name, "OK"), { @@ -196,12 +224,13 @@ Object.assign(PushServiceParent.prototype, { }, error => { sender.sendAsyncMessage(this._getResponseName(name, "KO"), { requestID: data.requestID, + result: error.result, }); }).catch(Cu.reportError); }, _handleReady() { - this._service.init(); + this.service.init(); }, _toPageRecord(principal, data) { @@ -228,7 +257,7 @@ Object.assign(PushServiceParent.prototype, { _handleRequest(name, principal, data) { if (name == "Push:Clear") { - return this._service.clear(data); + return this.service.clear(data); } let pageRecord; @@ -239,13 +268,13 @@ Object.assign(PushServiceParent.prototype, { } if (name === "Push:Register") { - return this._service.register(pageRecord); + return this.service.register(pageRecord); } if (name === "Push:Registration") { - return this._service.registration(pageRecord); + return this.service.registration(pageRecord); } if (name === "Push:Unregister") { - return this._service.unregister(pageRecord); + return this.service.unregister(pageRecord); } return Promise.reject(new Error("Invalid request: unknown name")); @@ -259,12 +288,22 @@ Object.assign(PushServiceParent.prototype, { // Methods used for mocking in tests. replaceServiceBackend(options) { - this._service.changeTestServer(options.serverURI, options); + return this.service.changeTestServer(options.serverURI, options); }, restoreServiceBackend() { var defaultServerURL = Services.prefs.getCharPref("dom.push.serverURL"); - this._service.changeTestServer(defaultServerURL); + return this.service.changeTestServer(defaultServerURL); + }, +}); + +// Used to replace the implementation with a mock. +Object.defineProperty(PushServiceParent.prototype, "service", { + get() { + return this._service || PushService; + }, + set(impl) { + this._service = impl; }, }); @@ -303,9 +342,14 @@ Object.assign(PushServiceContent.prototype, { // nsIPushService methods subscribe(scope, principal, callback) { + this.subscribeWithKey(scope, principal, 0, null, callback); + }, + + subscribeWithKey(scope, principal, keyLen, key, callback) { let requestId = this._addRequest(callback); this._mm.sendAsyncMessage("Push:Register", { scope: scope, + appServerKey: key, requestID: requestId, }, null, principal); }, @@ -344,6 +388,15 @@ Object.assign(PushServiceContent.prototype, { this._mm.sendAsyncMessage("Push:NotificationForOriginClosed", origin); }, + // nsIPushErrorReporter methods + + reportDeliveryError(messageId, reason) { + this._mm.sendAsyncMessage("Push:ReportError", { + messageId: messageId, + reason: reason, + }); + }, + _addRequest(data) { let id = ++this._requestId; this._requests.set(id, data); @@ -375,7 +428,7 @@ Object.assign(PushServiceContent.prototype, { case "PushService:Register:KO": case "PushService:Registration:KO": - request.onPushSubscription(Cr.NS_ERROR_FAILURE, null); + this._deliverSubscriptionError(request, data); break; case "PushService:Unregister:OK": @@ -457,11 +510,15 @@ PushSubscription.prototype = { * receive the key size and buffer as out parameters. */ getKey(name, outKeyLen) { - if (name === "p256dh") { - return this._getRawKey(this._props.p256dhKey, outKeyLen); - } - if (name === "auth") { - return this._getRawKey(this._props.authenticationSecret, outKeyLen); + switch (name) { + case "p256dh": + return this._getRawKey(this._props.p256dhKey, outKeyLen); + + case "auth": + return this._getRawKey(this._props.authenticationSecret, outKeyLen); + + case "appServer": + return this._getRawKey(this._props.appServerKey, outKeyLen); } return null; }, diff --git a/dom/push/PushCrypto.jsm b/dom/push/PushCrypto.jsm index 45dcdca16a..b41dcf7ae7 100644 --- a/dom/push/PushCrypto.jsm +++ b/dom/push/PushCrypto.jsm @@ -10,15 +10,23 @@ const Cu = Components.utils; Cu.importGlobalProperties(['crypto']); this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray', - 'getCryptoParams', - 'base64UrlDecode']; + 'getCryptoParams']; var UTF8 = new TextEncoder('utf-8'); -var ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128'); + +// Legacy encryption scheme (draft-thomson-http-encryption-02). +var AESGCM128_ENCODING = 'aesgcm128'; +var AESGCM128_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128'); + +// New encryption scheme (draft-ietf-httpbis-encryption-encoding-01). +var AESGCM_ENCODING = 'aesgcm'; +var AESGCM_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm'); + var NONCE_INFO = UTF8.encode('Content-Encoding: nonce'); var AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus var P256DH_INFO = UTF8.encode('P-256\0'); var ECDH_KEY = { name: 'ECDH', namedCurve: 'P-256' }; +var ECDSA_KEY = { name: 'ECDSA', namedCurve: 'P-256' }; // A default keyid with a name that won't conflict with a real keyid. var DEFAULT_KEYID = ''; @@ -52,15 +60,24 @@ this.getCryptoParams = function(headers) { return null; } - var requiresAuthenticationSecret = true; - var keymap = getEncryptionKeyParams(headers.crypto_key); - if (!keymap) { - requiresAuthenticationSecret = false; + var keymap; + var padSize; + if (headers.encoding == AESGCM_ENCODING) { + // aesgcm uses the Crypto-Key header, 2 bytes for the pad length, and an + // authentication secret. + // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-01 + keymap = getEncryptionKeyParams(headers.crypto_key); + padSize = 2; + } else if (headers.encoding == AESGCM128_ENCODING) { + // aesgcm128 uses Encryption-Key, 1 byte for the pad length, and no secret. + // https://tools.ietf.org/html/draft-thomson-http-encryption-02 keymap = getEncryptionKeyParams(headers.encryption_key); - if (!keymap) { - return null; - } + padSize = 1; } + if (!keymap) { + return null; + } + var enc = getEncryptionParams(headers.encryption); if (!enc) { return null; @@ -69,10 +86,10 @@ this.getCryptoParams = function(headers) { var salt = enc.salt; var rs = (enc.rs)? parseInt(enc.rs, 10) : 4096; - if (!dh || !salt || isNaN(rs) || (rs <= 1)) { + if (!dh || !salt || isNaN(rs) || (rs <= padSize)) { return null; } - return {dh, salt, rs, auth: requiresAuthenticationSecret}; + return {dh, salt, rs, padSize}; } var parseHeaderFieldParams = (m, v) => { @@ -101,34 +118,6 @@ function chunkArray(array, size) { return result; } -this.base64UrlDecode = function(s) { - s = s.replace(/-/g, '+').replace(/_/g, '/'); - - // Replace padding if it was stripped by the sender. - // See http://tools.ietf.org/html/rfc4648#section-4 - switch (s.length % 4) { - case 0: - break; // No pad chars in this case - case 2: - s += '=='; - break; // Two pad chars - case 3: - s += '='; - break; // One pad char - default: - throw new Error('Illegal base64url string!'); - } - - // With correct padding restored, apply the standard base64 decoder - var decoded = atob(s); - - var array = new Uint8Array(new ArrayBuffer(decoded.length)); - for (var i = 0; i < decoded.length; i++) { - array[i] = decoded.charCodeAt(i); - } - return array; -}; - this.concatArray = function(arrays) { var size = arrays.reduce((total, a) => total + a.byteLength, 0); var index = 0; @@ -183,7 +172,13 @@ function generateNonce(base, index) { this.PushCrypto = { generateAuthenticationSecret() { - return crypto.getRandomValues(new Uint8Array(12)); + return crypto.getRandomValues(new Uint8Array(16)); + }, + + validateAppServerKey(key) { + return crypto.subtle.importKey('raw', key, ECDSA_KEY, + true, ['verify']) + .then(_ => key); }, generateKeys() { @@ -195,8 +190,8 @@ this.PushCrypto = { ])); }, - decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey, - aSalt, aRs, aAuthenticationSecret) { + decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey, aSalt, aRs, + aAuthenticationSecret, aPadSize) { if (aData.byteLength === 0) { // Zero length messages will be passed as null. @@ -209,7 +204,11 @@ this.PushCrypto = { return Promise.reject(new Error('Data truncated')); } - let senderKey = base64UrlDecode(aSenderPublicKey) + let senderKey = ChromeUtils.base64URLDecode(aSenderPublicKey, { + // draft-ietf-httpbis-encryption-encoding-01 prohibits padding. + padding: "reject", + }); + return Promise.all([ crypto.subtle.importKey('raw', senderKey, ECDH_KEY, false, ['deriveBits']), @@ -219,53 +218,56 @@ this.PushCrypto = { .then(([appServerKey, subscriptionPrivateKey]) => crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey }, subscriptionPrivateKey, 256)) - .then(ikm => this._deriveKeyAndNonce(new Uint8Array(ikm), - base64UrlDecode(aSalt), + .then(ikm => this._deriveKeyAndNonce(aPadSize, + new Uint8Array(ikm), + ChromeUtils.base64URLDecode(aSalt, + { padding: "reject" }), aPublicKey, senderKey, aAuthenticationSecret)) .then(r => // AEAD_AES_128_GCM expands ciphertext to be 16 octets longer. Promise.all(chunkArray(aData, aRs + 16).map((slice, index) => - this._decodeChunk(slice, index, r[1], r[0])))) + this._decodeChunk(aPadSize, slice, index, r[1], r[0])))) .then(r => concatArray(r)); }, - _deriveKeyAndNonce(ikm, salt, receiverKey, senderKey, authenticationSecret) { + _deriveKeyAndNonce(padSize, ikm, salt, receiverKey, senderKey, + authenticationSecret) { var kdfPromise; var context; - // The authenticationSecret, when present, is mixed with the ikm using HKDF. - // This is its primary purpose. However, since the authentication secret - // was added at the same time that the info string was changed, we also use - // its presence to change how the final info string is calculated: + var encryptInfo; + // The size of the padding determines which key derivation we use. // - // 1. When there is no authenticationSecret, the context string is simply - // "Content-Encoding: ". This corresponds to old, deprecated versions - // of the content encoding. This should eventually be removed: bug 1230038. + // 1. If the pad size is 1, we assume "aesgcm128". This scheme ignores the + // authenticationSecret, and uses "Content-Encoding: " for the + // context string. It should eventually be removed: bug 1230038. // - // 2. When there is an authenticationSecret, the context string is: + // 2. If the pad size is 2, we assume "aesgcm", and mix the + // authenticationSecret with the ikm using HKDF. The context string is: // "Content-Encoding: \0P-256\0" then the length and value of both the // receiver key and sender key. - if (authenticationSecret) { + if (padSize == 2) { // Since we are using an authentication secret, we need to run an extra // round of HKDF with the authentication secret as salt. var authKdf = new hkdf(authenticationSecret, ikm); kdfPromise = authKdf.extract(AUTH_INFO, 32) .then(ikm2 => new hkdf(salt, ikm2)); - // We also use the presence of the authentication secret to indicate that - // we have extra context to add to the info parameter. + // aesgcm requires extra context for the info parameter. context = concatArray([ new Uint8Array([0]), P256DH_INFO, this._encodeLength(receiverKey), receiverKey, this._encodeLength(senderKey), senderKey ]); + encryptInfo = AESGCM_ENCRYPT_INFO; } else { kdfPromise = Promise.resolve(new hkdf(salt, ikm)); context = new Uint8Array(0); + encryptInfo = AESGCM128_ENCRYPT_INFO; } return kdfPromise.then(kdf => Promise.all([ - kdf.extract(concatArray([ENCRYPT_INFO, context]), 16) + kdf.extract(concatArray([encryptInfo, context]), 16) .then(gcmBits => crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false, ['decrypt'])), kdf.extract(concatArray([NONCE_INFO, context]), 12) @@ -276,27 +278,44 @@ this.PushCrypto = { return new Uint8Array([0, buffer.byteLength]); }, - _decodeChunk(aSlice, aIndex, aNonce, aKey) { + _decodeChunk(aPadSize, aSlice, aIndex, aNonce, aKey) { let params = { name: 'AES-GCM', iv: generateNonce(aNonce, aIndex) }; return crypto.subtle.decrypt(params, aKey, aSlice) - .then(decoded => { - decoded = new Uint8Array(decoded); - if (decoded.length == 0) { - return Promise.reject(new Error('Decoded array is too short!')); - } else if (decoded[0] > decoded.length) { - return Promise.reject(new Error ('Padding is wrong!')); - } else { - // All padded bytes must be zero except the first one. - for (var i = 1; i <= decoded[0]; i++) { - if (decoded[i] != 0) { - return Promise.reject(new Error('Padding is wrong!')); - } - } - return decoded.slice(decoded[0] + 1); - } - }); - } + .then(decoded => this._unpadChunk(aPadSize, new Uint8Array(decoded))); + }, + + /** + * Removes padding from a decrypted chunk. + * + * @param {Number} padSize The size of the padding length prepended to each + * chunk. For aesgcm, the padding length is expressed as a 16-bit unsigned + * big endian integer. For aesgcm128, the padding is an 8-bit integer. + * @param {Uint8Array} decoded The decrypted, padded chunk. + * @returns {Uint8Array} The chunk with padding removed. + */ + _unpadChunk(padSize, decoded) { + if (padSize < 1 || padSize > 2) { + throw new Error('Unsupported pad size'); + } + if (decoded.length < padSize) { + throw new Error('Decoded array is too short!'); + } + var pad = decoded[0]; + if (padSize == 2) { + pad = (pad << 8) | decoded[1]; + } + if (pad > decoded.length) { + throw new Error ('Padding is wrong!'); + } + // All padded bytes must be zero except the first one. + for (var i = padSize; i <= pad; i++) { + if (decoded[i] !== 0) { + throw new Error('Padding is wrong!'); + } + } + return decoded.slice(pad + padSize); + }, }; diff --git a/dom/push/PushManager.cpp b/dom/push/PushManager.cpp index 4f501614a8..cbeecb9772 100644 --- a/dom/push/PushManager.cpp +++ b/dom/push/PushManager.cpp @@ -6,13 +6,10 @@ #include "mozilla/dom/PushManager.h" -#include "mozilla/Base64.h" -#include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/unused.h" #include "mozilla/dom/PushManagerBinding.h" -#include "mozilla/dom/PushSubscriptionBinding.h" -#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" +#include "mozilla/dom/PushSubscription.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" @@ -23,8 +20,7 @@ #include "nsIPushService.h" #include "nsComponentManagerUtils.h" -#include "nsFrameMessageManager.h" -#include "nsContentCID.h" +#include "nsContentUtils.h" #include "WorkerRunnable.h" #include "WorkerPrivate.h" @@ -39,7 +35,7 @@ namespace { nsresult GetPermissionState(nsIPrincipal* aPrincipal, - PushPermissionState& aState) + PushPermissionState& aState) { nsCOMPtr permManager = mozilla::services::GetPermissionManager(); @@ -63,523 +59,27 @@ GetPermissionState(nsIPrincipal* aPrincipal, } else { aState = PushPermissionState::Prompt; } + return NS_OK; } -void -SubscriptionToJSON(PushSubscriptionJSON& aJSON, const nsString& aEndpoint, - const nsTArray& aRawP256dhKey, - const nsTArray& aAuthSecret) -{ - aJSON.mEndpoint.Construct(); - aJSON.mEndpoint.Value() = aEndpoint; - - aJSON.mKeys.mP256dh.Construct(); - nsresult rv = Base64URLEncode(aRawP256dhKey.Length(), - aRawP256dhKey.Elements(), - aJSON.mKeys.mP256dh.Value()); - Unused << NS_WARN_IF(NS_FAILED(rv)); - - aJSON.mKeys.mAuth.Construct(); - rv = Base64URLEncode(aAuthSecret.Length(), aAuthSecret.Elements(), - aJSON.mKeys.mAuth.Value()); - Unused << NS_WARN_IF(NS_FAILED(rv)); -} - -} // anonymous namespace - -class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback -{ -public: - NS_DECL_ISUPPORTS - - explicit UnsubscribeResultCallback(Promise* aPromise) - : mPromise(aPromise) - { - AssertIsOnMainThread(); - } - - NS_IMETHOD - OnUnsubscribe(nsresult aStatus, bool aSuccess) override - { - if (NS_SUCCEEDED(aStatus)) { - mPromise->MaybeResolve(aSuccess); - } else { - mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); - } - - return NS_OK; - } - -private: - ~UnsubscribeResultCallback() - {} - - RefPtr mPromise; -}; - -NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback) - -already_AddRefed -PushSubscription::Unsubscribe(ErrorResult& aRv) -{ - MOZ_ASSERT(mPrincipal); - - nsCOMPtr service = - do_GetService("@mozilla.org/push/Service;1"); - if (NS_WARN_IF(!service)) { - aRv = NS_ERROR_FAILURE; - return nullptr; - } - - RefPtr p = Promise::Create(mGlobal, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - RefPtr callback = - new UnsubscribeResultCallback(p); - Unused << NS_WARN_IF(NS_FAILED( - service->Unsubscribe(mScope, mPrincipal, callback))); - return p.forget(); -} - -void -PushSubscription::ToJSON(PushSubscriptionJSON& aJSON) -{ - SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret); -} - -PushSubscription::PushSubscription(nsIGlobalObject* aGlobal, - const nsAString& aEndpoint, - const nsAString& aScope, - const nsTArray& aRawP256dhKey, - const nsTArray& aAuthSecret) - : mGlobal(aGlobal) - , mEndpoint(aEndpoint) - , mScope(aScope) - , mRawP256dhKey(aRawP256dhKey) - , mAuthSecret(aAuthSecret) -{ -} - -PushSubscription::~PushSubscription() -{ -} - -JSObject* -PushSubscription::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return PushSubscriptionBinding::Wrap(aCx, this, aGivenProto); -} - -void -PushSubscription::GetKey(JSContext* aCx, - PushEncryptionKeyName aType, - JS::MutableHandle aKey) -{ - if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) { - aKey.set(ArrayBuffer::Create(aCx, - mRawP256dhKey.Length(), - mRawP256dhKey.Elements())); - } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) { - aKey.set(ArrayBuffer::Create(aCx, - mAuthSecret.Length(), - mAuthSecret.Elements())); - } else { - aKey.set(nullptr); - } -} - -void -PushSubscription::SetPrincipal(nsIPrincipal* aPrincipal) -{ - MOZ_ASSERT(!mPrincipal); - mPrincipal = aPrincipal; -} - -// static -already_AddRefed -PushSubscription::Constructor(GlobalObject& aGlobal, - const nsAString& aEndpoint, - const nsAString& aScope, - const Nullable& aP256dhKey, - const Nullable& aAuthSecret, - ErrorResult& aRv) -{ - MOZ_ASSERT(!aEndpoint.IsEmpty()); - MOZ_ASSERT(!aScope.IsEmpty()); - - nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); - - nsTArray rawKey; - if (!aP256dhKey.IsNull()) { - const ArrayBuffer& key = aP256dhKey.Value(); - key.ComputeLengthAndData(); - rawKey.InsertElementsAt(0, key.Data(), key.Length()); - } - - nsTArray authSecret; - if (!aAuthSecret.IsNull()) { - const ArrayBuffer& sekrit = aAuthSecret.Value(); - sekrit.ComputeLengthAndData(); - authSecret.InsertElementsAt(0, sekrit.Data(), sekrit.Length()); - } - RefPtr sub = new PushSubscription(global, - aEndpoint, - aScope, - rawKey, - authSecret); - - return sub.forget(); -} - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mPrincipal) - -NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription) -NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -PushManager::PushManager(nsIGlobalObject* aGlobal, const nsAString& aScope) - : mGlobal(aGlobal), mScope(aScope) -{ - AssertIsOnMainThread(); -} - -PushManager::~PushManager() -{} - -JSObject* -PushManager::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - // XXXnsm I don't know if this is the right way to do it, but I want to assert - // that an implementation has been set before this object gets exposed to JS. - MOZ_ASSERT(mImpl); - return PushManagerBinding::Wrap(aCx, this, aGivenProto); -} - -void -PushManager::SetPushManagerImpl(PushManagerImpl& foo, ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(!mImpl); - mImpl = &foo; -} - -already_AddRefed -PushManager::Subscribe(ErrorResult& aRv) -{ - MOZ_ASSERT(mImpl); - return mImpl->Subscribe(aRv); -} - -already_AddRefed -PushManager::GetSubscription(ErrorResult& aRv) -{ - MOZ_ASSERT(mImpl); - return mImpl->GetSubscription(aRv); -} - -already_AddRefed -PushManager::PermissionState(ErrorResult& aRv) -{ - MOZ_ASSERT(mImpl); - return mImpl->PermissionState(aRv); -} - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushManager, mGlobal, mImpl) -NS_IMPL_CYCLE_COLLECTING_ADDREF(PushManager) -NS_IMPL_CYCLE_COLLECTING_RELEASE(PushManager) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushManager) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -// WorkerPushSubscription - -WorkerPushSubscription::WorkerPushSubscription(const nsAString& aEndpoint, - const nsAString& aScope, - const nsTArray& aRawP256dhKey, - const nsTArray& aAuthSecret) - : mEndpoint(aEndpoint) - , mScope(aScope) - , mRawP256dhKey(aRawP256dhKey) - , mAuthSecret(aAuthSecret) -{ - MOZ_ASSERT(!aScope.IsEmpty()); - MOZ_ASSERT(!aEndpoint.IsEmpty()); -} - -WorkerPushSubscription::~WorkerPushSubscription() -{} - -JSObject* -WorkerPushSubscription::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return PushSubscriptionBinding_workers::Wrap(aCx, this, aGivenProto); -} - -// static -already_AddRefed -WorkerPushSubscription::Constructor(GlobalObject& aGlobal, - const nsAString& aEndpoint, - const nsAString& aScope, - const Nullable& aP256dhKey, - const Nullable& aAuthSecret, - ErrorResult& aRv) -{ - WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(worker); - worker->AssertIsOnWorkerThread(); - - nsTArray rawKey; - if (!aP256dhKey.IsNull()) { - const ArrayBuffer& key = aP256dhKey.Value(); - key.ComputeLengthAndData(); - rawKey.SetLength(key.Length()); - rawKey.ReplaceElementsAt(0, key.Length(), key.Data(), key.Length()); - } - - nsTArray authSecret; - if (!aAuthSecret.IsNull()) { - const ArrayBuffer& sekrit = aAuthSecret.Value(); - sekrit.ComputeLengthAndData(); - authSecret.SetLength(sekrit.Length()); - authSecret.ReplaceElementsAt(0, sekrit.Length(), - sekrit.Data(), sekrit.Length()); - } - RefPtr sub = new WorkerPushSubscription(aEndpoint, - aScope, - rawKey, - authSecret); - - return sub.forget(); -} - -void -WorkerPushSubscription::GetKey(JSContext* aCx, - PushEncryptionKeyName aType, - JS::MutableHandle aKey) -{ - if (aType == mozilla::dom::PushEncryptionKeyName::P256dh && - !mRawP256dhKey.IsEmpty()) { - aKey.set(ArrayBuffer::Create(aCx, - mRawP256dhKey.Length(), - mRawP256dhKey.Elements())); - } else if (aType == mozilla::dom::PushEncryptionKeyName::Auth && - !mAuthSecret.IsEmpty()) { - aKey.set(ArrayBuffer::Create(aCx, - mAuthSecret.Length(), - mAuthSecret.Elements())); - } else { - aKey.set(nullptr); - } -} - -class UnsubscribeResultRunnable final : public WorkerRunnable -{ -public: - UnsubscribeResultRunnable(PromiseWorkerProxy* aProxy, - nsresult aStatus, - bool aSuccess) - : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount) - , mProxy(aProxy) - , mStatus(aStatus) - , mSuccess(aSuccess) - { - AssertIsOnMainThread(); - } - - bool - WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override - { - MOZ_ASSERT(aWorkerPrivate); - aWorkerPrivate->AssertIsOnWorkerThread(); - - RefPtr promise = mProxy->WorkerPromise(); - if (NS_SUCCEEDED(mStatus)) { - promise->MaybeResolve(mSuccess); - } else { - promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); - } - - mProxy->CleanUp(); - return true; - } -private: - ~UnsubscribeResultRunnable() - {} - - RefPtr mProxy; - nsresult mStatus; - bool mSuccess; -}; - -class WorkerUnsubscribeResultCallback final : public nsIUnsubscribeResultCallback -{ -public: - NS_DECL_ISUPPORTS - - explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy) - : mProxy(aProxy) - { - AssertIsOnMainThread(); - } - - NS_IMETHOD - OnUnsubscribe(nsresult aStatus, bool aSuccess) override - { - AssertIsOnMainThread(); - MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?"); - - RefPtr proxy = mProxy.forget(); - - MutexAutoLock lock(proxy->Lock()); - if (proxy->CleanedUp()) { - return NS_OK; - } - - RefPtr r = - new UnsubscribeResultRunnable(proxy, aStatus, aSuccess); - r->Dispatch(); - return NS_OK; - } - -private: - ~WorkerUnsubscribeResultCallback() - { - } - - RefPtr mProxy; -}; - -NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback) - -class UnsubscribeRunnable final : public nsRunnable -{ -public: - UnsubscribeRunnable(PromiseWorkerProxy* aProxy, - const nsAString& aScope) - : mProxy(aProxy) - , mScope(aScope) - { - MOZ_ASSERT(aProxy); - MOZ_ASSERT(!aScope.IsEmpty()); - } - - NS_IMETHOD - Run() override - { - AssertIsOnMainThread(); - - nsCOMPtr principal; - { - MutexAutoLock lock(mProxy->Lock()); - if (mProxy->CleanedUp()) { - return NS_OK; - } - principal = mProxy->GetWorkerPrivate()->GetPrincipal(); - } - MOZ_ASSERT(principal); - - RefPtr callback = - new WorkerUnsubscribeResultCallback(mProxy); - - nsCOMPtr service = - do_GetService("@mozilla.org/push/Service;1"); - if (!service) { - callback->OnUnsubscribe(NS_ERROR_FAILURE, false); - return NS_OK; - } - - if (NS_WARN_IF(NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) { - callback->OnUnsubscribe(NS_ERROR_FAILURE, false); - return NS_OK; - } - return NS_OK; - } - -private: - ~UnsubscribeRunnable() - {} - - RefPtr mProxy; - nsString mScope; -}; - -already_AddRefed -WorkerPushSubscription::Unsubscribe(ErrorResult &aRv) -{ - WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(worker); - worker->AssertIsOnWorkerThread(); - - nsCOMPtr global = worker->GlobalScope(); - RefPtr p = Promise::Create(global, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - RefPtr proxy = PromiseWorkerProxy::Create(worker, p); - if (!proxy) { - p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); - return p.forget(); - } - - RefPtr r = - new UnsubscribeRunnable(proxy, mScope); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); - - return p.forget(); -} - -void -WorkerPushSubscription::ToJSON(PushSubscriptionJSON& aJSON) -{ - SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret); -} - -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerPushSubscription) - -NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerPushSubscription) -NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerPushSubscription) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerPushSubscription) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END - -// WorkerPushManager - -WorkerPushManager::WorkerPushManager(const nsAString& aScope) - : mScope(aScope) -{ -} - -JSObject* -WorkerPushManager::WrapObject(JSContext* aCx, JS::Handle aGivenProto) -{ - return PushManagerBinding_workers::Wrap(aCx, this, aGivenProto); -} - class GetSubscriptionResultRunnable final : public WorkerRunnable { public: - GetSubscriptionResultRunnable(PromiseWorkerProxy* aProxy, + GetSubscriptionResultRunnable(WorkerPrivate* aWorkerPrivate, + already_AddRefed&& aProxy, nsresult aStatus, const nsAString& aEndpoint, const nsAString& aScope, - const nsTArray& aRawP256dhKey, - const nsTArray& aAuthSecret) - : WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount) - , mProxy(aProxy) + nsTArray&& aRawP256dhKey, + nsTArray&& aAuthSecret) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) + , mProxy(Move(aProxy)) , mStatus(aStatus) , mEndpoint(aEndpoint) , mScope(aScope) - , mRawP256dhKey(aRawP256dhKey) - , mAuthSecret(aAuthSecret) + , mRawP256dhKey(Move(aRawP256dhKey)) + , mAuthSecret(Move(aAuthSecret)) { } bool @@ -590,9 +90,9 @@ public: if (mEndpoint.IsEmpty()) { promise->MaybeResolve(JS::NullHandleValue); } else { - RefPtr sub = - new WorkerPushSubscription(mEndpoint, mScope, - mRawP256dhKey, mAuthSecret); + RefPtr sub = + new PushSubscription(nullptr, mEndpoint, mScope, + Move(mRawP256dhKey), Move(mAuthSecret)); promise->MaybeResolve(sub); } } else if (NS_ERROR_GET_MODULE(mStatus) == NS_ERROR_MODULE_DOM_PUSH ) { @@ -602,6 +102,7 @@ public: } mProxy->CleanUp(); + return true; } private: @@ -634,10 +135,8 @@ public: AssertIsOnMainThread(); MOZ_ASSERT(mProxy, "OnPushSubscription() called twice?"); - RefPtr proxy = mProxy.forget(); - - MutexAutoLock lock(proxy->Lock()); - if (proxy->CleanedUp()) { + MutexAutoLock lock(mProxy->Lock()); + if (mProxy->CleanedUp()) { return NS_OK; } @@ -648,14 +147,17 @@ public: authSecret); } + WorkerPrivate* worker = mProxy->GetWorkerPrivate(); RefPtr r = - new GetSubscriptionResultRunnable(proxy, + new GetSubscriptionResultRunnable(worker, + mProxy.forget(), aStatus, endpoint, mScope, - rawP256dhKey, - authSecret); - r->Dispatch(); + Move(rawP256dhKey), + Move(authSecret)); + MOZ_ALWAYS_TRUE(r->Dispatch()); + return NS_OK; } @@ -677,6 +179,7 @@ private: { NS_Free(aKey); NS_Free(aAuthSecret); + return aStatus; } @@ -734,7 +237,7 @@ class GetSubscriptionRunnable final : public nsRunnable public: GetSubscriptionRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope, - WorkerPushManager::SubscriptionAction aAction) + PushManager::SubscriptionAction aAction) : mProxy(aProxy) , mScope(aScope), mAction(aAction) {} @@ -745,6 +248,7 @@ public: AssertIsOnMainThread(); nsCOMPtr principal; + { // Bug 1228723: If permission is revoked or an error occurs, the // subscription callback will be called synchronously. This causes @@ -756,6 +260,7 @@ public: } principal = mProxy->GetWorkerPrivate()->GetPrincipal(); } + MOZ_ASSERT(principal); RefPtr callback = new GetSubscriptionCallback(mProxy, mScope); @@ -768,7 +273,7 @@ public: } if (state != PushPermissionState::Granted) { - if (mAction == WorkerPushManager::GetSubscriptionAction) { + if (mAction == PushManager::GetSubscriptionAction) { callback->OnPushSubscriptionError(NS_OK); return NS_OK; } @@ -778,15 +283,15 @@ public: nsCOMPtr service = do_GetService("@mozilla.org/push/Service;1"); - if (!service) { + if (NS_WARN_IF(!service)) { callback->OnPushSubscriptionError(NS_ERROR_FAILURE); return NS_OK; } - if (mAction == WorkerPushManager::SubscribeAction) { + if (mAction == PushManager::SubscribeAction) { rv = service->Subscribe(mScope, principal, callback); } else { - MOZ_ASSERT(mAction == WorkerPushManager::GetSubscriptionAction); + MOZ_ASSERT(mAction == PushManager::GetSubscriptionAction); rv = service->GetSubscription(mScope, principal, callback); } @@ -804,47 +309,9 @@ private: RefPtr mProxy; nsString mScope; - WorkerPushManager::SubscriptionAction mAction; + PushManager::SubscriptionAction mAction; }; -already_AddRefed -WorkerPushManager::PerformSubscriptionAction(SubscriptionAction aAction, ErrorResult& aRv) -{ - WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); - MOZ_ASSERT(worker); - worker->AssertIsOnWorkerThread(); - - nsCOMPtr global = worker->GlobalScope(); - RefPtr p = Promise::Create(global, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - RefPtr proxy = PromiseWorkerProxy::Create(worker, p); - if (!proxy) { - p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR); - return p.forget(); - } - - RefPtr r = - new GetSubscriptionRunnable(proxy, mScope, aAction); - MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); - - return p.forget(); -} - -already_AddRefed -WorkerPushManager::Subscribe(ErrorResult& aRv) -{ - return PerformSubscriptionAction(SubscribeAction, aRv); -} - -already_AddRefed -WorkerPushManager::GetSubscription(ErrorResult& aRv) -{ - return PerformSubscriptionAction(GetSubscriptionAction, aRv); -} - class PermissionResultRunnable final : public WorkerRunnable { public: @@ -873,6 +340,7 @@ public: } mProxy->CleanUp(); + return true; } @@ -909,7 +377,8 @@ public: RefPtr r = new PermissionResultRunnable(mProxy, rv, state); - r->Dispatch(); + MOZ_ALWAYS_TRUE(r->Dispatch()); + return NS_OK; } @@ -920,9 +389,99 @@ private: RefPtr mProxy; }; -already_AddRefed -WorkerPushManager::PermissionState(ErrorResult& aRv) +} // anonymous namespace + +PushManager::PushManager(nsIGlobalObject* aGlobal, PushManagerImpl* aImpl) + : mGlobal(aGlobal) + , mImpl(aImpl) { + AssertIsOnMainThread(); + MOZ_ASSERT(aImpl); +} + +PushManager::PushManager(const nsAString& aScope) + : mScope(aScope) +{ +#ifdef DEBUG + // There's only one global on a worker, so we don't need to pass a global + // object to the constructor. + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); +#endif +} + +PushManager::~PushManager() +{} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushManager, mGlobal, mImpl) +NS_IMPL_CYCLE_COLLECTING_ADDREF(PushManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PushManager) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushManager) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +PushManager::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return PushManagerBinding::Wrap(aCx, this, aGivenProto); +} + +// static +already_AddRefed +PushManager::Constructor(GlobalObject& aGlobal, + const nsAString& aScope, + ErrorResult& aRv) +{ + if (!NS_IsMainThread()) { + RefPtr ret = new PushManager(aScope); + return ret.forget(); + } + + RefPtr impl = PushManagerImpl::Constructor(aGlobal, + aGlobal.Context(), + aScope, aRv); + if (aRv.Failed()) { + return nullptr; + } + + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr ret = new PushManager(global, impl); + + return ret.forget(); +} + +already_AddRefed +PushManager::Subscribe(ErrorResult& aRv) +{ + if (mImpl) { + MOZ_ASSERT(NS_IsMainThread()); + return mImpl->Subscribe(aRv); + } + + return PerformSubscriptionActionFromWorker(SubscribeAction, aRv); +} + +already_AddRefed +PushManager::GetSubscription(ErrorResult& aRv) +{ + if (mImpl) { + MOZ_ASSERT(NS_IsMainThread()); + return mImpl->GetSubscription(aRv); + } + + return PerformSubscriptionActionFromWorker(GetSubscriptionAction, aRv); +} + +already_AddRefed +PushManager::PermissionState(ErrorResult& aRv) +{ + if (mImpl) { + MOZ_ASSERT(NS_IsMainThread()); + return mImpl->PermissionState(aRv); + } + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); worker->AssertIsOnWorkerThread(); @@ -946,15 +505,32 @@ WorkerPushManager::PermissionState(ErrorResult& aRv) return p.forget(); } -WorkerPushManager::~WorkerPushManager() -{} +already_AddRefed +PushManager::PerformSubscriptionActionFromWorker( + SubscriptionAction aAction, ErrorResult& aRv) +{ + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); + + nsCOMPtr global = worker->GlobalScope(); + RefPtr p = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr proxy = PromiseWorkerProxy::Create(worker, p); + if (!proxy) { + p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR); + return p.forget(); + } + + RefPtr r = + new GetSubscriptionRunnable(proxy, mScope, aAction); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); + + return p.forget(); +} -NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WorkerPushManager) -NS_IMPL_CYCLE_COLLECTING_ADDREF(WorkerPushManager) -NS_IMPL_CYCLE_COLLECTING_RELEASE(WorkerPushManager) -NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerPushManager) - NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY - NS_INTERFACE_MAP_ENTRY(nsISupports) -NS_INTERFACE_MAP_END } // namespace dom } // namespace mozilla diff --git a/dom/push/PushManager.h b/dom/push/PushManager.h index 2870bca297..8433c5eda2 100644 --- a/dom/push/PushManager.h +++ b/dom/push/PushManager.h @@ -5,24 +5,19 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ /** - * We would like to expose PushManager and PushSubscription on window and - * workers. Parts of the Push API is implemented in JS out of necessity due to: - * 1) Using frame message managers, in which - * nsIMessageListener::receiveMessage() must be in JS. - * 2) It is easier to use certain APIs like the permission prompt and Promises - * from JS. + * PushManager and PushSubscription are exposed on the main and worker threads. + * The main thread version is implemented in Push.js. The JS implementation + * makes it easier to use certain APIs like the permission prompt and Promises. * - * Unfortunately, JS-implemented WebIDL is not supported off the main thread. To - * aid in fixing this, the nsIPushClient is introduced which deals with part (1) - * above. Part (2) is handled by PushManagerImpl on the main thread. PushManager - * wraps this in C++ since our bindings code cannot accomodate "JS-implemented - * on the main thread, C++ on the worker" bindings. PushManager simply forwards - * the calls to the JS component. + * Unfortunately, JS-implemented WebIDL is not supported off the main thread. + * To work around this, we use a chain of runnables to query the JS-implemented + * nsIPushService component for subscription information, and return the + * results to the worker. We don't have to deal with permission prompts, since + * we just reject calls if the principal does not have permission. * - * On the worker threads, we don't have to deal with permission prompts, instead - * we just reject calls if the principal does not have permission. On workers - * WorkerPushManager dispatches runnables to the main thread which directly call - * nsIPushClient. + * On the main thread, PushManager wraps a JS-implemented PushManagerImpl + * instance. The C++ wrapper is necessary because our bindings code cannot + * accomodate "JS-implemented on the main thread, C++ on the worker" bindings. * * PushSubscription is in C++ on both threads since it isn't particularly * verbose to implement in C++ compared to JS. @@ -40,13 +35,10 @@ #include "nsCOMPtr.h" #include "mozilla/RefPtr.h" -#include "jsapi.h" class nsIGlobalObject; class nsIPrincipal; -#include "mozilla/dom/PushSubscriptionBinding.h" - namespace mozilla { namespace dom { @@ -57,68 +49,6 @@ class WorkerPrivate; class Promise; class PushManagerImpl; -class PushSubscription final : public nsISupports - , public nsWrapperCache -{ -public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscription) - - explicit PushSubscription(nsIGlobalObject* aGlobal, - const nsAString& aEndpoint, - const nsAString& aScope, - const nsTArray& aP256dhKey, - const nsTArray& aAuthSecret); - - JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - nsIGlobalObject* - GetParentObject() const - { - return mGlobal; - } - - void - GetEndpoint(nsAString& aEndpoint) const - { - aEndpoint = mEndpoint; - } - - void - GetKey(JSContext* cx, - PushEncryptionKeyName aType, - JS::MutableHandle aKey); - - static already_AddRefed - Constructor(GlobalObject& aGlobal, - const nsAString& aEndpoint, - const nsAString& aScope, - const Nullable& aP256dhKey, - const Nullable& aAuthSecret, - ErrorResult& aRv); - - void - SetPrincipal(nsIPrincipal* aPrincipal); - - already_AddRefed - Unsubscribe(ErrorResult& aRv); - - void - ToJSON(PushSubscriptionJSON& aJSON); - -protected: - ~PushSubscription(); - -private: - nsCOMPtr mGlobal; - nsCOMPtr mPrincipal; - nsString mEndpoint; - nsString mScope; - nsTArray mRawP256dhKey; - nsTArray mAuthSecret; -}; - class PushManager final : public nsISupports , public nsWrapperCache { @@ -126,7 +56,16 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushManager) - explicit PushManager(nsIGlobalObject* aGlobal, const nsAString& aScope); + enum SubscriptionAction { + SubscribeAction, + GetSubscriptionAction, + }; + + // The main thread constructor. + PushManager(nsIGlobalObject* aGlobal, PushManagerImpl* aImpl); + + // The worker thread constructor. + explicit PushManager(const nsAString& aScope); nsIGlobalObject* GetParentObject() const @@ -137,6 +76,14 @@ public: JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + static already_AddRefed + Constructor(GlobalObject& aGlobal, const nsAString& aScope, + ErrorResult& aRv); + + already_AddRefed + PerformSubscriptionActionFromWorker(SubscriptionAction aAction, + ErrorResult& aRv); + already_AddRefed Subscribe(ErrorResult& aRv); @@ -146,112 +93,15 @@ public: already_AddRefed PermissionState(ErrorResult& aRv); - void - SetPushManagerImpl(PushManagerImpl& foo, ErrorResult& aRv); - protected: ~PushManager(); private: + // The following are only set and accessed on the main thread. nsCOMPtr mGlobal; RefPtr mImpl; - nsString mScope; -}; -class WorkerPushSubscription final : public nsISupports - , public nsWrapperCache -{ -public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkerPushSubscription) - - explicit WorkerPushSubscription(const nsAString& aEndpoint, - const nsAString& aScope, - const nsTArray& aRawP256dhKey, - const nsTArray& aAuthSecret); - - nsIGlobalObject* - GetParentObject() const - { - return nullptr; - } - - JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - static already_AddRefed - Constructor(GlobalObject& aGlobal, - const nsAString& aEndpoint, - const nsAString& aScope, - const Nullable& aP256dhKey, - const Nullable& aAuthSecret, - ErrorResult& aRv); - - void - GetEndpoint(nsAString& aEndpoint) const - { - aEndpoint = mEndpoint; - } - - void - GetKey(JSContext* cx, PushEncryptionKeyName aType, - JS::MutableHandle aP256dhKey); - - already_AddRefed - Unsubscribe(ErrorResult& aRv); - - void - ToJSON(PushSubscriptionJSON& aJSON); - -protected: - ~WorkerPushSubscription(); - -private: - nsString mEndpoint; - nsString mScope; - nsTArray mRawP256dhKey; - nsTArray mAuthSecret; -}; - -class WorkerPushManager final : public nsISupports - , public nsWrapperCache -{ -public: - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WorkerPushManager) - - enum SubscriptionAction { - SubscribeAction, - GetSubscriptionAction, - }; - - explicit WorkerPushManager(const nsAString& aScope); - - nsIGlobalObject* - GetParentObject() const - { - return nullptr; - } - - JSObject* - WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; - - already_AddRefed - PerformSubscriptionAction(SubscriptionAction aAction, ErrorResult& aRv); - - already_AddRefed - Subscribe(ErrorResult& aRv); - - already_AddRefed - GetSubscription(ErrorResult& aRv); - - already_AddRefed - PermissionState(ErrorResult& aRv); - -protected: - ~WorkerPushManager(); - -private: + // Only used on the worker thread. nsString mScope; }; } // namespace dom diff --git a/dom/push/PushNotifier.cpp b/dom/push/PushNotifier.cpp index 1f17d45e84..d41295d077 100644 --- a/dom/push/PushNotifier.cpp +++ b/dom/push/PushNotifier.cpp @@ -6,10 +6,11 @@ #include "nsContentUtils.h" #include "nsCOMPtr.h" -#include "nsXPCOM.h" -#include "nsIXULRuntime.h" -#include "ServiceWorkerManager.h" #include "nsICategoryManager.h" +#include "nsIXULRuntime.h" +#include "nsNetUtil.h" +#include "nsXPCOM.h" +#include "ServiceWorkerManager.h" #include "mozilla/Services.h" #include "mozilla/unused.h" @@ -20,6 +21,7 @@ namespace mozilla { namespace dom { +using workers::AssertIsOnMainThread; using workers::ServiceWorkerManager; PushNotifier::PushNotifier() @@ -65,9 +67,6 @@ NS_IMETHODIMP PushNotifier::NotifySubscriptionChange(const nsACString& aScope, nsIPrincipal* aPrincipal) { - if (XRE_IsContentProcess()) { - return NS_ERROR_NOT_IMPLEMENTED; - } nsresult rv; if (ShouldNotifyObservers(aPrincipal)) { rv = NotifySubscriptionChangeObservers(aScope); @@ -84,14 +83,32 @@ PushNotifier::NotifySubscriptionChange(const nsACString& aScope, return NS_OK; } +NS_IMETHODIMP +PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal, + const nsAString& aMessage, uint32_t aFlags) +{ + if (ShouldNotifyWorkers(aPrincipal)) { + // For service worker subscriptions, report the error to all clients. + NotifyErrorWorkers(aScope, aMessage, aFlags); + return NS_OK; + } + // For system subscriptions, log the error directly to the browser console. + return nsContentUtils::ReportToConsoleNonLocalized(aMessage, + aFlags, + NS_LITERAL_CSTRING("Push"), + nullptr, /* aDocument */ + nullptr, /* aURI */ + EmptyString(), /* aLine */ + 0, /* aLineNumber */ + 0, /* aColumnNumber */ + nsContentUtils::eOMIT_LOCATION); +} + nsresult PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal, const nsAString& aMessageId, const Maybe>& aData) { - if (XRE_IsContentProcess()) { - return NS_ERROR_NOT_IMPLEMENTED; - } nsresult rv; if (ShouldNotifyObservers(aPrincipal)) { rv = NotifyPushObservers(aScope, aData); @@ -114,6 +131,7 @@ PushNotifier::NotifyPushWorkers(const nsACString& aScope, const nsAString& aMessageId, const Maybe>& aData) { + AssertIsOnMainThread(); if (!aPrincipal) { return NS_ERROR_INVALID_ARG; } @@ -155,6 +173,7 @@ nsresult PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope, nsIPrincipal* aPrincipal) { + AssertIsOnMainThread(); if (!aPrincipal) { return NS_ERROR_INVALID_ARG; } @@ -184,6 +203,58 @@ PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope, return ok ? NS_OK : NS_ERROR_FAILURE; } +void +PushNotifier::NotifyErrorWorkers(const nsACString& aScope, + const nsAString& aMessage, + uint32_t aFlags) +{ + AssertIsOnMainThread(); + + if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) { + // Content process or e10s disabled. + RefPtr swm = ServiceWorkerManager::GetInstance(); + if (swm) { + swm->ReportToAllClients(PromiseFlatCString(aScope), + PromiseFlatString(aMessage), + NS_ConvertUTF8toUTF16(aScope), /* aFilename */ + EmptyString(), /* aLine */ + 0, /* aLineNumber */ + 0, /* aColumnNumber */ + aFlags); + } + return; + } + + // Parent process, e10s enabled. + nsTArray contentActors; + ContentParent::GetAll(contentActors); + if (!contentActors.IsEmpty()) { + // At least one content process active. + for (uint32_t i = 0; i < contentActors.Length(); ++i) { + Unused << NS_WARN_IF( + !contentActors[i]->SendPushError(PromiseFlatCString(aScope), + PromiseFlatString(aMessage), aFlags)); + } + return; + } + // Report to the console if no content processes are active. + nsCOMPtr scopeURI; + nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + Unused << NS_WARN_IF(NS_FAILED( + nsContentUtils::ReportToConsoleNonLocalized(aMessage, + aFlags, + NS_LITERAL_CSTRING("Push"), + nullptr, /* aDocument */ + scopeURI, /* aURI */ + EmptyString(), /* aLine */ + 0, /* aLineNumber */ + 0, /* aColumnNumber */ + nsContentUtils::eOMIT_LOCATION))); +} + nsresult PushNotifier::NotifyPushObservers(const nsACString& aScope, const Maybe>& aData) diff --git a/dom/push/PushNotifier.h b/dom/push/PushNotifier.h index 454a8039f9..01b8b98b43 100644 --- a/dom/push/PushNotifier.h +++ b/dom/push/PushNotifier.h @@ -29,9 +29,8 @@ namespace dom { * forwards incoming push messages to service workers running in the content * process, and emits XPCOM observer notifications for system subscriptions. * - * The XPCOM service can only be used from the main process. Callers running - * in the content process should use - * `ServiceWorkerManager::SendPush{SubscriptionChange}Event` directly. + * This service exists solely to support `PushService.jsm`. Other callers + * should use `ServiceWorkerManager` directly. */ class PushNotifier final : public nsIPushNotifier { @@ -51,6 +50,8 @@ public: const Maybe>& aData); nsresult NotifySubscriptionChangeWorkers(const nsACString& aScope, nsIPrincipal* aPrincipal); + void NotifyErrorWorkers(const nsACString& aScope, const nsAString& aMessage, + uint32_t aFlags); protected: virtual ~PushNotifier(); diff --git a/dom/push/PushRecord.jsm b/dom/push/PushRecord.jsm index c366ba149e..e1199c8db0 100644 --- a/dom/push/PushRecord.jsm +++ b/dom/push/PushRecord.jsm @@ -43,6 +43,7 @@ function PushRecord(props) { this.p256dhPrivateKey = props.p256dhPrivateKey; this.authenticationSecret = props.authenticationSecret; this.systemRecord = !!props.systemRecord; + this.appServerKey = props.appServerKey; this.setQuota(props.quota); this.ctime = (typeof props.ctime === "number") ? props.ctime : 0; } @@ -240,6 +241,22 @@ PushRecord.prototype = { this.principal.originAttributes, pattern); }, + hasAuthenticationSecret() { + return !!this.authenticationSecret && + this.authenticationSecret.byteLength == 16; + }, + + matchesAppServerKey(key) { + if (!this.appServerKey) { + return !key; + } + if (!key) { + return false; + } + return this.appServerKey.length === key.length && + this.appServerKey.every((value, index) => value === key[index]); + }, + toSubscription() { return { endpoint: this.pushEndpoint, @@ -247,6 +264,7 @@ PushRecord.prototype = { pushCount: this.pushCount, p256dhKey: this.p256dhPublicKey, authenticationSecret: this.authenticationSecret, + appServerKey: this.appServerKey, quota: this.quotaApplies() ? this.quota : -1, }; }, diff --git a/dom/push/PushService.jsm b/dom/push/PushService.jsm index 3fc4e68dae..582c45fbd9 100644 --- a/dom/push/PushService.jsm +++ b/dom/push/PushService.jsm @@ -10,22 +10,26 @@ const Ci = Components.interfaces; const Cu = Components.utils; const Cr = Components.results; -const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Timer.jsm"); +Cu.import("resource://gre/modules/AppConstants.jsm"); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://gre/modules/Promise.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Timer.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -const {PushServiceWebSocket} = Cu.import("resource://gre/modules/PushServiceWebSocket.jsm"); -const {PushServiceHttp2} = Cu.import("resource://gre/modules/PushServiceHttp2.jsm"); const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm"); +const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm"); -// Currently supported protocols: WebSocket. -const CONNECTION_PROTOCOLS = [PushServiceWebSocket, PushServiceHttp2]; - -XPCOMUtils.defineLazyModuleGetter(this, "AlarmService", - "resource://gre/modules/AlarmService.jsm"); +const CONNECTION_PROTOCOLS = (function() { + if ('android' != AppConstants.MOZ_WIDGET_TOOLKIT) { + const {PushServiceWebSocket} = Cu.import("resource://gre/modules/PushServiceWebSocket.jsm"); + const {PushServiceHttp2} = Cu.import("resource://gre/modules/PushServiceHttp2.jsm"); + return [PushServiceWebSocket, PushServiceHttp2]; + } else { + const {PushServiceAndroidGCM} = Cu.import("resource://gre/modules/PushServiceAndroidGCM.jsm"); + return [PushServiceAndroidGCM]; + } +})(); XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager", "@mozilla.org/contentsecuritymanager;1", @@ -35,6 +39,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "gPushNotifier", "@mozilla.org/push/Notifier;1", "nsIPushNotifier"); +XPCOMUtils.defineLazyGetter(this, "gDOMBundle", () => + Services.strings.createBundle("chrome://global/locale/dom/dom.properties")); + this.EXPORTED_SYMBOLS = ["PushService"]; XPCOMUtils.defineLazyGetter(this, "console", () => { @@ -86,6 +93,17 @@ const CHANGING_SERVICE_EVENT = 1; const STOPPING_SERVICE_EVENT = 2; const UNINIT_EVENT = 3; +/** + * Annotates an error with an XPCOM result code. We use this helper + * instead of `Components.Exception` because the latter can assert in + * `nsXPCComponents_Exception::HasInstance` when inspected at shutdown. + */ +function errorWithResult(message, result = Cr.NS_ERROR_FAILURE) { + let error = new Error(message); + error.result = result; + return error; +} + /** * The implementation of the push system. It uses WebSockets * (PushServiceWebSocket) to communicate with the server and PushDB (IndexedDB) @@ -96,13 +114,15 @@ this.PushService = { _state: PUSH_SERVICE_UNINIT, _db: null, _options: null, - _alarmID: null, _visibleNotifications: new Map(), // Callback that is called after attempting to // reduce the quota for a record. Used for testing purposes. _updateQuotaTestCallback: null, + // Set of timeout ID of tasks to reduce quota. + _updateQuotaTimeouts: new Set(), + // When serverURI changes (this is used for testing), db is cleaned up and a // a new db is started. This events must be sequential. _stateChangeProcessQueue: null, @@ -112,8 +132,17 @@ this.PushService = { } this._stateChangeProcessQueue = this._stateChangeProcessQueue - .then(op) - .catch(_ => {}); + .then(op) + .catch(error => { + console.error( + "stateChangeProcessEnqueue: Error transitioning state", error); + return this._shutdownService(); + }) + .catch(error => { + console.error( + "stateChangeProcessEnqueue: Error shutting down service", error); + }); + return this._stateChangeProcessQueue; }, // Pending request. If a worker try to register for the same scope again, do @@ -165,7 +194,6 @@ this.PushService = { if (this._state == PUSH_SERVICE_ACTIVATING) { // It is not important what is the new state as soon as we leave // PUSH_SERVICE_ACTIVATING - this._state = aNewState; if (this._notifyActivated) { if (aNewState < PUSH_SERVICE_ACTIVATING) { this._notifyActivated.reject(new Error("Push service not active")); @@ -185,7 +213,7 @@ this.PushService = { if (this._state < PUSH_SERVICE_ACTIVE_OFFLINE && this._state != PUSH_SERVICE_ACTIVATING && !calledFromConnEnabledEvent) { - return; + return Promise.resolve(); } if (offline) { @@ -193,22 +221,22 @@ this.PushService = { this._service.disconnect(); } this._setState(PUSH_SERVICE_ACTIVE_OFFLINE); - } else { - if (this._state == PUSH_SERVICE_RUNNING) { - // PushService was not in the offline state, but got notification to - // go online (a offline notification has not been sent). - // Disconnect first. - this._service.disconnect(); - } - this._db.getAllUnexpired() - .then(records => { - if (records.length > 0) { - // if there are request waiting - this._service.connect(records); - } - }); - this._setState(PUSH_SERVICE_RUNNING); + return Promise.resolve(); } + + if (this._state == PUSH_SERVICE_RUNNING) { + // PushService was not in the offline state, but got notification to + // go online (a offline notification has not been sent). + // Disconnect first. + this._service.disconnect(); + } + return this.getAllUnexpired().then(records => { + this._setState(PUSH_SERVICE_RUNNING); + if (records.length > 0) { + // if there are request waiting + this._service.connect(records); + } + }); }, _changeStateConnectionEnabledEvent: function(enabled) { @@ -216,24 +244,25 @@ this.PushService = { if (this._state < PUSH_SERVICE_CONNECTION_DISABLE && this._state != PUSH_SERVICE_ACTIVATING) { - return; + return Promise.resolve(); } if (enabled) { - this._changeStateOfflineEvent(Services.io.offline, true); - } else { - if (this._state == PUSH_SERVICE_RUNNING) { - this._service.disconnect(); - } - this._setState(PUSH_SERVICE_CONNECTION_DISABLE); + return this._changeStateOfflineEvent(Services.io.offline, true); } + + if (this._state == PUSH_SERVICE_RUNNING) { + this._service.disconnect(); + } + this._setState(PUSH_SERVICE_CONNECTION_DISABLE); + return Promise.resolve(); }, // Used for testing. changeTestServer(url, options = {}) { console.debug("changeTestServer()"); - this._stateChangeProcessEnqueue(_ => { + return this._stateChangeProcessEnqueue(_ => { if (this._state < PUSH_SERVICE_ACTIVATING) { console.debug("changeTestServer: PushService not activated?"); return Promise.resolve(); @@ -250,7 +279,7 @@ this.PushService = { * aren't very good at automatically cleaning up, so we don't get shutdown * leaks on browser shutdown. */ - case "xpcom-shutdown": + case "quit-application": this.uninit(); break; case "network-active-changed": /* On B2G. */ @@ -277,7 +306,9 @@ this.PushService = { break; case "idle-daily": - this._dropExpiredRegistrations(); + this._dropExpiredRegistrations().catch(error => { + console.error("Failed to drop expired registrations on idle", error); + }); break; case "perm-changed": @@ -307,7 +338,8 @@ this.PushService = { if (!record.matchesOriginAttributes(pattern)) { return false; } - this._backgroundUnregister(record); + this._backgroundUnregister(record, + Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL); return true; }); }, @@ -317,8 +349,10 @@ this.PushService = { * service is not connected, this function is a no-op. * * @param {PushRecord} record The record to unregister. + * @param {Number} reason An `nsIPushErrorReporter` unsubscribe reason, + * indicating why this record was removed. */ - _backgroundUnregister: function(record) { + _backgroundUnregister(record, reason) { console.debug("backgroundUnregister()"); if (!this._service.isConnected() || !record) { @@ -326,7 +360,7 @@ this.PushService = { } console.debug("backgroundUnregister: Notifying server", record); - this._sendUnregister(record).catch(e => { + this._sendUnregister(record, reason).catch(e => { console.error("backgroundUnregister: Error notifying server", e); }); }, @@ -335,11 +369,13 @@ this.PushService = { // stopObservers() getNetworkStateChangeEventName: function() { try { - Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); - return "network-active-changed"; - } catch (e) { - return "network:offline-status-changed"; - } + let networkManager = Cc["@mozilla.org/network/manager;1"]; + if (networkManager) { + networkManager.getService(Ci.nsINetworkManager); + return "network-active-changed"; + } + } catch (e) {} + return "network:offline-status-changed"; }, _findService: function(serverURL) { @@ -389,9 +425,8 @@ this.PushService = { this._setState(PUSH_SERVICE_INIT); return Promise.resolve(); } - return this._startService(service, uri) - .then(_ => this._stateChangeProcessEnqueue(_ => - this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))) + return this._startService(service, uri, options) + .then(_ => this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")) ); } case CHANGING_SERVICE_EVENT: @@ -401,8 +436,7 @@ this.PushService = { this._setState(PUSH_SERVICE_ACTIVATING); // The service has not been running - start it. return this._startService(service, uri, options) - .then(_ => this._stateChangeProcessEnqueue(_ => - this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))) + .then(_ => this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")) ); } else { @@ -414,8 +448,7 @@ this.PushService = { .then(_ => this._startService(service, uri, options) ) - .then(_ => this._stateChangeProcessEnqueue(_ => - this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled"))) + .then(_ => this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")) ); } @@ -429,12 +462,15 @@ this.PushService = { return this._stopService(STOPPING_SERVICE_EVENT); } } + default: + console.error("Unexpected event in _changeServerURL", event); + return Promise.reject(new Error(`Unexpected event ${event}`)); } }, /** * PushService initialization is divided into 4 parts: - * init() - start listening for xpcom-shutdown and serverURL changes. + * init() - start listening for quit-application and serverURL changes. * state is change to PUSH_SERVICE_INIT * startService() - if serverURL is present this function is called. It starts * listening for broadcasted messages, starts db and @@ -456,23 +492,13 @@ this.PushService = { this._setState(PUSH_SERVICE_ACTIVATING); - Services.obs.addObserver(this, "xpcom-shutdown", false); + Services.obs.addObserver(this, "quit-application", false); if (options.serverURI) { // this is use for xpcshell test. - let [service, uri] = this._findService(options.serverURI); - if (!service) { - this._setState(PUSH_SERVICE_INIT); - return; - } - - // Start service. - this._startService(service, uri, options).then(_ => { - // Before completing the activation check prefs. This will first check - // connection.enabled pref and then check offline state. - this._changeStateConnectionEnabledEvent(prefs.get("connection.enabled")); - }).catch(Cu.reportError); + this._stateChangeProcessEnqueue(_ => + this._changeServerURL(options.serverURI, STARTING_SERVICE_EVENT, options)); } else { // This is only used for testing. Different tests require connecting to @@ -525,7 +551,7 @@ this.PushService = { Services.obs.addObserver(this, "perm-changed", false); }, - _startService: function(service, serverURI, options = {}) { + _startService(service, serverURI, options) { console.debug("startService()"); if (this._state != PUSH_SERVICE_ACTIVATING) { @@ -552,23 +578,24 @@ this.PushService = { * stopService() - It stops listening for broadcasted messages, stops db and * PushService connection (WebSocket). * state is changed to PUSH_SERVICE_INIT. - * uninit() - stop listening for xpcom-shutdown and serverURL changes. + * uninit() - stop listening for quit-application and serverURL changes. * state is change to PUSH_SERVICE_UNINIT */ _stopService: function(event) { console.debug("stopService()"); if (this._state < PUSH_SERVICE_ACTIVATING) { - return; + return Promise.resolve(); } - this.stopAlarm(); this._stopObservers(); this._service.disconnect(); this._service.uninit(); this._service = null; - this.stopAlarm(); + + this._updateQuotaTimeouts.forEach((timeoutID) => clearTimeout(timeoutID)); + this._updateQuotaTimeouts.clear(); if (!this._db) { return Promise.resolve(); @@ -605,6 +632,13 @@ this.PushService = { Services.obs.removeObserver(this, "perm-changed"); }, + _shutdownService() { + let promiseChangeURL = this._changeServerURL("", UNINIT_EVENT); + this._setState(PUSH_SERVICE_UNINIT); + console.debug("shutdownService: shutdown complete!"); + return promiseChangeURL; + }, + uninit: function() { console.debug("uninit()"); @@ -613,66 +647,9 @@ this.PushService = { } prefs.ignore("serverURL", this); - Services.obs.removeObserver(this, "xpcom-shutdown"); + Services.obs.removeObserver(this, "quit-application"); - this._stateChangeProcessEnqueue(_ => - { - var p = this._changeServerURL("", UNINIT_EVENT); - this._setState(PUSH_SERVICE_UNINIT); - console.debug("uninit: shutdown complete!"); - return p; - }); - }, - - /** |delay| should be in milliseconds. */ - setAlarm: function(delay) { - if (this._state <= PUSH_SERVICE_ACTIVATING) { - return; - } - - // Bug 909270: Since calls to AlarmService.add() are async, calls must be - // 'queued' to ensure only one alarm is ever active. - if (this._settingAlarm) { - // onSuccess will handle the set. Overwriting the variable enforces the - // last-writer-wins semantics. - this._queuedAlarmDelay = delay; - this._waitingForAlarmSet = true; - return; - } - - // Stop any existing alarm. - this.stopAlarm(); - - this._settingAlarm = true; - AlarmService.add( - { - date: new Date(Date.now() + delay), - ignoreTimezone: true - }, - () => { - if (this._state > PUSH_SERVICE_ACTIVATING) { - this._service.onAlarmFired(); - } - }, (alarmID) => { - this._alarmID = alarmID; - console.debug("setAlarm: Set alarm", delay, "in the future", - this._alarmID); - this._settingAlarm = false; - - if (this._waitingForAlarmSet) { - this._waitingForAlarmSet = false; - this.setAlarm(this._queuedAlarmDelay); - } - } - ); - }, - - stopAlarm: function() { - if (this._alarmID !== null) { - console.debug("stopAlarm: Stopped existing alarm", this._alarmID); - AlarmService.remove(this._alarmID); - this._alarmID = null; - } + this._stateChangeProcessEnqueue(_ => this._shutdownService()); }, /** @@ -754,7 +731,7 @@ this.PushService = { }, ensureCrypto: function(record) { - if (record.authenticationSecret && + if (record.hasAuthenticationSecret() && record.p256dhPublicKey && record.p256dhPrivateKey) { return Promise.resolve(record); @@ -773,7 +750,7 @@ this.PushService = { record.p256dhPublicKey = pubKey; record.p256dhPrivateKey = privKey; } - if (!record.authenticationSecret) { + if (!record.hasAuthenticationSecret()) { record.authenticationSecret = PushCrypto.generateAuthenticationSecret(); } return record; @@ -797,83 +774,117 @@ this.PushService = { * be notified. * * @param {String} keyID The push registration ID. - * @param {String} message The message contents. + * @param {String} messageID The message ID, used to report service worker + * delivery failures. For Web Push messages, this is the version. If empty, + * failures will not be reported. + * @param {ArrayBuffer|Uint8Array} data The encrypted message data. * @param {Object} cryptoParams The message encryption settings. * @param {Function} updateFunc A function that receives the existing * registration record as its argument, and returns a new record. If the * function returns `null` or `undefined`, the record will not be updated. * `PushServiceWebSocket` uses this to drop incoming updates with older * versions. + * @returns {Promise} Resolves with an `nsIPushErrorReporter` ack status + * code, indicating whether the message was delivered successfully. */ - receivedPushMessage: function(keyID, message, cryptoParams, updateFunc) { + receivedPushMessage(keyID, messageID, data, cryptoParams, updateFunc) { console.debug("receivedPushMessage()"); Services.telemetry.getHistogramById("PUSH_API_NOTIFICATION_RECEIVED").add(); - let shouldNotify = false; + return this._updateRecordAfterPush(keyID, updateFunc).then(record => { + if (!record) { + throw new Error("Ignoring update for key ID " + keyID); + } + // Update quota after the delay, at which point + // we check for visible notifications. + let timeoutID = setTimeout(_ => + { + this._updateQuota(keyID); + if (!this._updateQuotaTimeouts.delete(timeoutID)) { + console.debug("receivedPushMessage: quota update timeout missing?"); + } + }, prefs.get("quotaUpdateDelay")); + this._updateQuotaTimeouts.add(timeoutID); + return this._decryptAndNotifyApp(record, messageID, data, cryptoParams); + }).catch(error => { + console.error("receivedPushMessage: Error notifying app", error); + return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED; + }); + }, + + /** + * Updates a registration record after receiving a push message. + * + * @param {String} keyID The push registration ID. + * @param {Function} updateFunc The function passed to `receivedPushMessage`. + * @returns {Promise} Resolves with the updated record, or `null` if the + * record was not updated. + */ + _updateRecordAfterPush(keyID, updateFunc) { return this.getByKeyID(keyID).then(record => { if (!record) { this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_KEY_NOT_FOUND); throw new Error("No record for key ID " + keyID); } - return record.getLastVisit(); - }).then(lastVisit => { - // As a special case, don't notify the service worker if the user - // cleared their history. - shouldNotify = isFinite(lastVisit); - if (!shouldNotify) { + return record.getLastVisit().then(lastVisit => { + // As a special case, don't notify the service worker if the user + // cleared their history. + if (!isFinite(lastVisit)) { this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_HISTORY); - } - return this._db.update(keyID, record => { - let newRecord = updateFunc(record); - if (!newRecord) { - this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_VERSION_INCREMENT); - return null; + throw new Error("Ignoring message sent to unvisited origin"); } - // Because `unregister` is advisory only, we can still receive messages - // for stale Simple Push registrations from the server. To work around - // this, we check if the record has expired before *and* after updating - // the quota. - if (newRecord.isExpired()) { - console.error("receivedPushMessage: Ignoring update for expired key ID", - keyID); - return null; - } - newRecord.receivedPush(lastVisit); - return newRecord; + return lastVisit; + }).then(lastVisit => { + // Update the record, resetting the quota if the user has visited the + // site since the last push. + return this._db.update(keyID, record => { + let newRecord = updateFunc(record); + if (!newRecord) { + this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_VERSION_INCREMENT); + return null; + } + // Because `unregister` is advisory only, we can still receive messages + // for stale Simple Push registrations from the server. To work around + // this, we check if the record has expired before *and* after updating + // the quota. + if (newRecord.isExpired()) { + return null; + } + newRecord.receivedPush(lastVisit); + return newRecord; + }); }); - }).then(record => { - var notified = false; - if (!record) { - return notified; - } - let decodedPromise; - if (cryptoParams) { - decodedPromise = PushCrypto.decodeMsg( - message, - record.p256dhPrivateKey, - record.p256dhPublicKey, - cryptoParams.dh, - cryptoParams.salt, - cryptoParams.rs, - cryptoParams.auth ? record.authenticationSecret : null - ); - } else { - decodedPromise = Promise.resolve(null); - } - return decodedPromise.then(message => { - if (shouldNotify) { - notified = this._notifyApp(record, message); - } - // Update quota after the delay, at which point - // we check for visible notifications. - setTimeout(() => this._updateQuota(keyID), - prefs.get("quotaUpdateDelay")); - return notified; - }, error => { - console.error("receivedPushMessage: Error decrypting message", error); - }); - }).catch(error => { - console.error("receivedPushMessage: Error notifying app", error); + }); + }, + + /** + * Decrypts an incoming message and notifies the associated service worker. + * + * @param {PushRecord} record The receiving registration. + * @param {String} messageID The message ID. + * @param {ArrayBuffer|Uint8Array} data The encrypted message data. + * @param {Object} cryptoParams The message encryption settings. + * @returns {Promise} Resolves with an ack status code. + */ + _decryptAndNotifyApp(record, messageID, data, cryptoParams) { + if (!cryptoParams) { + return this._notifyApp(record, messageID, null); + } + return PushCrypto.decodeMsg( + data, + record.p256dhPrivateKey, + record.p256dhPublicKey, + cryptoParams.dh, + cryptoParams.salt, + cryptoParams.rs, + record.authenticationSecret, + cryptoParams.padSize + ).then(message => this._notifyApp(record, messageID, message), error => { + let message = gDOMBundle.formatStringFromName( + "PushMessageDecryptionFailure", [record.scope, String(error)], 2); + gPushNotifier.notifyError(record.scope, record.principal, message, + Ci.nsIScriptError.errorFlag); + return Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR; }); }, @@ -899,7 +910,8 @@ this.PushService = { // Drop the registration in the background. If the user returns to the // site, the service worker will be notified on the next `idle-daily` // event. - this._backgroundUnregister(record); + this._backgroundUnregister(record, + Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED); } if (this._updateQuotaTestCallback) { // Callback so that test may be notified when the quota update is complete. @@ -937,11 +949,21 @@ this.PushService = { } }, - _notifyApp: function(aPushRecord, message) { + reportDeliveryError(messageID, reason) { + console.debug("reportDeliveryError()", messageID, reason); + if (this._state == PUSH_SERVICE_RUNNING && + this._service.isConnected()) { + + // Only report errors if we're initialized and connected. + this._service.reportDeliveryError(messageID, reason); + } + }, + + _notifyApp(aPushRecord, messageID, message) { if (!aPushRecord || !aPushRecord.scope || aPushRecord.originAttributes === undefined) { console.error("notifyApp: Invalid record", aPushRecord); - return false; + return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED; } console.debug("notifyApp()", aPushRecord.scope); @@ -949,7 +971,7 @@ this.PushService = { // If permission has been revoked, trash the message. if (!aPushRecord.hasPermission()) { console.warn("notifyApp: Missing push permission", aPushRecord); - return false; + return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED; } let payload = ArrayBuffer.isView(message) ? @@ -963,12 +985,13 @@ this.PushService = { if (payload) { gPushNotifier.notifyPushWithData(aPushRecord.scope, aPushRecord.principal, - payload.length, payload); + messageID, payload.length, payload); } else { - gPushNotifier.notifyPush(aPushRecord.scope, aPushRecord.principal); + gPushNotifier.notifyPush(aPushRecord.scope, aPushRecord.principal, + messageID); } - return true; + return Ci.nsIPushErrorReporter.ACK_DELIVERED; }, getByKeyID: function(aKeyID) { @@ -979,7 +1002,7 @@ this.PushService = { return this._db.getAllUnexpired(); }, - _sendRequest: function(action, aRecord) { + _sendRequest(action, ...params) { if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) { return Promise.reject(new Error("Push service disabled")); } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) { @@ -988,7 +1011,18 @@ this.PushService = { } return Promise.reject(new Error("Push service offline")); } - return this._service.request(action, aRecord); + // Ensure the backend is ready. `getByPageRecord` already checks this, but + // we need to check again here in case the service was restarted in the + // meantime. + return this._checkActivated().then(_ => { + switch (action) { + case "register": + return this._service.register(...params); + case "unregister": + return this._service.unregister(...params); + } + return Promise.reject(new Error("Unknown request type: " + action)); + }); }, /** @@ -1011,9 +1045,9 @@ this.PushService = { }); }, - _sendUnregister: function(aRecord) { + _sendUnregister(aRecord, aReason) { Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_ATTEMPT").add(); - return this._sendRequest("unregister", aRecord).then(function(v) { + return this._sendRequest("unregister", aRecord, aReason).then(function(v) { Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_SUCCEEDED").add(); return v; }).catch(function(e) { @@ -1037,7 +1071,8 @@ this.PushService = { .catch(error => { Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add() // Unable to save. Destroy the subscription in the background. - this._backgroundUnregister(aRecord); + this._backgroundUnregister(aRecord, + Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL); throw error; }); }, @@ -1070,23 +1105,45 @@ this.PushService = { register: function(aPageRecord) { console.debug("register()", aPageRecord); - return this._getByPageRecord(aPageRecord) - .then(record => { - if (!record) { - return this._lookupOrPutPendingRequest(aPageRecord); - } - if (record.isExpired()) { - return record.quotaChanged().then(isChanged => { - if (isChanged) { - // If the user revisited the site, drop the expired push - // registration and re-register. - return this.dropRegistrationAndNotifyApp(record.keyID); - } - throw new Error("Push subscription expired"); - }).then(_ => this._lookupOrPutPendingRequest(aPageRecord)); - } - return record.toSubscription(); - }); + let keyPromise; + if (aPageRecord.appServerKey) { + let keyView = new Uint8Array(aPageRecord.appServerKey); + keyPromise = PushCrypto.validateAppServerKey(keyView) + .catch(error => { + // Normalize Web Crypto exceptions. `nsIPushService` will forward the + // error result to the DOM API implementation in `PushManager.cpp` or + // `Push.js`, which will convert it to the correct `DOMException`. + throw errorWithResult("Invalid app server key", + Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR); + }); + } else { + keyPromise = Promise.resolve(null); + } + + return Promise.all([ + keyPromise, + this._getByPageRecord(aPageRecord), + ]).then(([appServerKey, record]) => { + aPageRecord.appServerKey = appServerKey; + if (!record) { + return this._lookupOrPutPendingRequest(aPageRecord); + } + if (!record.matchesAppServerKey(appServerKey)) { + throw errorWithResult("Mismatched app server key", + Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR); + } + if (record.isExpired()) { + return record.quotaChanged().then(isChanged => { + if (isChanged) { + // If the user revisited the site, drop the expired push + // registration and re-register. + return this.dropRegistrationAndNotifyApp(record.keyID); + } + throw new Error("Push subscription expired"); + }).then(_ => this._lookupOrPutPendingRequest(aPageRecord)); + } + return record.toSubscription(); + }); }, /** @@ -1124,7 +1181,8 @@ this.PushService = { } return Promise.all([ - this._sendUnregister(record), + this._sendUnregister(record, + Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL), this._db.delete(record.keyID), ]).then(() => true); }); @@ -1234,7 +1292,8 @@ this.PushService = { if (record.quotaApplies()) { if (!record.isExpired()) { // Drop the registration in the background. - this._backgroundUnregister(record); + this._backgroundUnregister(record, + Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED); } return true; } @@ -1310,7 +1369,8 @@ this.PushService = { return; } // Drop the registration in the background. - this._backgroundUnregister(record); + this._backgroundUnregister(record, + Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED); record.setQuota(0); cursor.update(record); }, diff --git a/dom/push/PushServiceAndroidGCM.jsm b/dom/push/PushServiceAndroidGCM.jsm new file mode 100644 index 0000000000..a08c1fc7aa --- /dev/null +++ b/dom/push/PushServiceAndroidGCM.jsm @@ -0,0 +1,247 @@ +/* jshint moz: true, esnext: true */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm"); +const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm"); +Cu.import("resource://gre/modules/Messaging.jsm"); /*global: Services */ +Cu.import("resource://gre/modules/Services.jsm"); /*global: Services */ +Cu.import("resource://gre/modules/Preferences.jsm"); /*global: Preferences */ +Cu.import("resource://gre/modules/Promise.jsm"); /*global: Promise */ +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global: XPCOMUtils */ + +const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("Push"); + +const { + PushCrypto, + concatArray, + getCryptoParams, +} = Cu.import("resource://gre/modules/PushCrypto.jsm"); + +this.EXPORTED_SYMBOLS = ["PushServiceAndroidGCM"]; + +XPCOMUtils.defineLazyGetter(this, "console", () => { + let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {}); + return new ConsoleAPI({ + dump: Log.i, + maxLogLevelPref: "dom.push.loglevel", + prefix: "PushServiceAndroidGCM", + }); +}); + +const kPUSHANDROIDGCMDB_DB_NAME = "pushAndroidGCM"; +const kPUSHANDROIDGCMDB_DB_VERSION = 5; // Change this if the IndexedDB format changes +const kPUSHANDROIDGCMDB_STORE_NAME = "pushAndroidGCM"; + +const prefs = new Preferences("dom.push."); + +/** + * The implementation of WebPush push backed by Android's GCM + * delivery. + */ +this.PushServiceAndroidGCM = { + _mainPushService: null, + _serverURI: null, + + newPushDB: function() { + return new PushDB(kPUSHANDROIDGCMDB_DB_NAME, + kPUSHANDROIDGCMDB_DB_VERSION, + kPUSHANDROIDGCMDB_STORE_NAME, + "channelID", + PushRecordAndroidGCM); + }, + + serviceType: function() { + return "AndroidGCM"; + }, + + validServerURI: function(serverURI) { + if (!serverURI) { + return false; + } + + if (serverURI.scheme == "https") { + return true; + } + if (prefs.get("debug") && serverURI.scheme == "http") { + // Accept HTTP endpoints when debugging. + return true; + } + console.info("Unsupported Android GCM dom.push.serverURL scheme", serverURI.scheme); + return false; + }, + + observe: function(subject, topic, data) { + if (topic == "nsPref:changed") { + if (data == "dom.push.debug") { + // Reconfigure. + let debug = !!prefs.get("debug"); + console.info("Debug parameter changed; updating configuration with new debug", debug); + this._configure(this._serverURI, debug); + } + return; + } + + if (topic == "PushServiceAndroidGCM:ReceivedPushMessage") { + // TODO: Use Messaging.jsm for this. + if (this._mainPushService == null) { + // Shouldn't ever happen, but let's be careful. + console.error("No main PushService! Dropping message."); + return; + } + if (!data) { + console.error("No data from Java! Dropping message."); + return; + } + data = JSON.parse(data); + console.debug("ReceivedPushMessage with data", data); + + // Default is no data (and no encryption). + let message = null; + let cryptoParams = null; + + if (data.message && data.enc && (data.enckey || data.cryptokey)) { + let headers = { + encryption_key: data.enckey, + crypto_key: data.cryptokey, + encryption: data.enc, + encoding: data.con, + }; + cryptoParams = getCryptoParams(headers); + // Ciphertext is (urlsafe) Base 64 encoded. + message = ChromeUtils.base64URLDecode(data.message, { + // The Push server may append padding. + padding: "ignore", + }); + } + + console.debug("Delivering message to main PushService:", message, cryptoParams); + this._mainPushService.receivedPushMessage( + data.channelID, message, cryptoParams, (record) => { + // Always update the stored record. + return record; + }); + return; + } + }, + + _configure: function(serverURL, debug) { + return Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:Configure", + endpoint: serverURL.spec, + debug: debug, + }); + }, + + init: function(options, mainPushService, serverURL) { + console.debug("init()"); + this._mainPushService = mainPushService; + this._serverURI = serverURL; + + prefs.observe("debug", this); + Services.obs.addObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage", false); + + return this._configure(serverURL, !!prefs.get("debug")); + }, + + uninit: function() { + console.debug("uninit()"); + this._mainPushService = null; + Services.obs.removeObserver(this, "PushServiceAndroidGCM:ReceivedPushMessage"); + prefs.ignore("debug", this); + }, + + onAlarmFired: function() { + // No action required. + }, + + connect: function(records) { + console.debug("connect:", records); + // It's possible for the registration or subscriptions backing the + // PushService to not be registered with the underlying AndroidPushService. + // Expire those that are unrecognized. + return Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:DumpSubscriptions", + }) + .then(subscriptions => { + console.debug("connect:", subscriptions); + // subscriptions maps chid => subscription data. + return Promise.all(records.map(record => { + if (subscriptions.hasOwnProperty(record.keyID)) { + console.debug("connect:", "hasOwnProperty", record.keyID); + return Promise.resolve(); + } + console.debug("connect:", "!hasOwnProperty", record.keyID); + // Subscription is known to PushService.jsm but not to AndroidPushService. Drop it. + return this._mainPushService.dropRegistrationAndNotifyApp(record.keyID) + .catch(error => { + console.error("connect: Error dropping registration", record.keyID, error); + }); + })); + }); + }, + + isConnected: function() { + return this._mainPushService != null; + }, + + disconnect: function() { + console.debug("disconnect"); + }, + + register: function(record) { + console.debug("register:", record); + let ctime = Date.now(); + // Caller handles errors. + return Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:SubscribeChannel", + }).then(data => { + console.debug("Got data:", data); + return PushCrypto.generateKeys() + .then(exportedKeys => + new PushRecordAndroidGCM({ + // Straight from autopush. + channelID: data.channelID, + pushEndpoint: data.endpoint, + // Common to all PushRecord implementations. + scope: record.scope, + originAttributes: record.originAttributes, + ctime: ctime, + // Cryptography! + p256dhPublicKey: exportedKeys[0], + p256dhPrivateKey: exportedKeys[1], + authenticationSecret: PushCrypto.generateAuthenticationSecret(), + }) + ); + }); + }, + + unregister: function(record) { + console.debug("unregister: ", record); + return Messaging.sendRequestForResult({ + type: "PushServiceAndroidGCM:UnsubscribeChannel", + channelID: record.keyID, + }); + }, +}; + +function PushRecordAndroidGCM(record) { + PushRecord.call(this, record); + this.channelID = record.channelID; +} + +PushRecordAndroidGCM.prototype = Object.create(PushRecord.prototype, { + keyID: { + get() { + return this.channelID; + }, + }, +}); diff --git a/dom/push/PushServiceHttp2.jsm b/dom/push/PushServiceHttp2.jsm index fe889c02c5..b9303bf449 100644 --- a/dom/push/PushServiceHttp2.jsm +++ b/dom/push/PushServiceHttp2.jsm @@ -155,6 +155,7 @@ PushChannelListener.prototype = { encryption_key: getHeaderField(aRequest, "Encryption-Key"), crypto_key: getHeaderField(aRequest, "Crypto-Key"), encryption: getHeaderField(aRequest, "Encryption"), + encoding: getHeaderField(aRequest, "Content-Encoding"), }; let cryptoParams = getCryptoParams(headers); let msg = concatArray(this._message); @@ -219,6 +220,7 @@ var SubscriptionListener = function(aSubInfo, aResolve, aReject, this._serverURI = aServerURI; this._service = aPushServiceHttp2; this._ctime = Date.now(); + this._retryTimeoutID = null; }; SubscriptionListener.prototype = { @@ -261,12 +263,17 @@ SubscriptionListener.prototype = { if (this._subInfo.retries < prefs.get("http2.maxRetries")) { this._subInfo.retries++; var retryAfter = retryAfterParser(aRequest); - setTimeout(_ => this._reject( + this._retryTimeoutID = setTimeout(_ => { - retry: true, - subInfo: this._subInfo - }), - retryAfter); + this._reject( + { + retry: true, + subInfo: this._subInfo + }); + this._service.removeListenerPendingRetry(this); + this._retryTimeoutID = null; + }, retryAfter); + this._service.addListenerPendingRetry(this); } else { this._reject(new Error("Unexpected server response: " + statusCode)); } @@ -329,6 +336,15 @@ SubscriptionListener.prototype = { Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_HTTP2_TIME").add(Date.now() - this._ctime); this._resolve(reply); }, + + abortRetry: function() { + if (this._retryTimeoutID != null) { + clearTimeout(this._retryTimeoutID); + this._retryTimeoutID = null; + } else { + console.debug("SubscriptionListener.abortRetry: aborting non-existent retry?"); + } + }, }; function retryAfterParser(aRequest) { @@ -402,6 +418,9 @@ this.PushServiceHttp2 = { _conns: {}, _started: false, + // Set of SubscriptionListeners that are pending a subscription retry attempt. + _listenersPendingRetry: new Set(), + newPushDB: function() { return new PushDB(kPUSHHTTP2DB_DB_NAME, kPUSHHTTP2DB_DB_VERSION, @@ -447,7 +466,7 @@ this.PushServiceHttp2 = { /** * Subscribe new resource. */ - _subscribeResource: function(aRecord) { + register: function(aRecord) { console.debug("subscribeResource()"); return this._subscribeResourceInternal({ @@ -465,7 +484,7 @@ this.PushServiceHttp2 = { listener: null, countUnableToConnect: 0, lastStartListening: 0, - waitingForAlarm: false + retryTimerID: 0, }; this._listenForMsgs(result.subscriptionUri); return result; @@ -583,28 +602,20 @@ this.PushServiceHttp2 = { if (retryAfter !== -1) { // This is a 5xx response. - // To respect RetryAfter header, setTimeout is used. setAlarm sets a - // cumulative alarm so it will not always respect RetryAfter header. this._conns[aSubscriptionUri].countUnableToConnect++; - setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter); + this._conns[aSubscriptionUri].retryTimerID = + setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter); return; } - // we set just one alarm because most probably all connection will go over - // a single TCP connection. retryAfter = prefs.get("http2.retryInterval") * Math.pow(2, this._conns[aSubscriptionUri].countUnableToConnect); retryAfter = retryAfter * (0.8 + Math.random() * 0.4); // add +/-20%. this._conns[aSubscriptionUri].countUnableToConnect++; - - if (retryAfter === 0) { - setTimeout(_ => this._listenForMsgs(aSubscriptionUri), 0); - } else { - this._conns[aSubscriptionUri].waitingForAlarm = true; - this._mainPushService.setAlarm(retryAfter); - } + this._conns[aSubscriptionUri].retryTimerID = + setTimeout(_ => this._listenForMsgs(aSubscriptionUri), retryAfter); console.debug("retryAfterBackoff: Retry in", retryAfter); }, @@ -626,7 +637,11 @@ this.PushServiceHttp2 = { } this._conns[subscriptionUri].listener = null; this._conns[subscriptionUri].channel = null; - this._conns[subscriptionUri].waitingForAlarm = false; + + if (this._conns[subscriptionUri].retryTimerID > 0) { + clearTimeout(this._conns[subscriptionUri].retryTimerID); + } + if (deleteInfo) { delete this._conns[subscriptionUri]; } @@ -655,27 +670,13 @@ this.PushServiceHttp2 = { this._conns[record.subscriptionUri] = {channel: null, listener: null, countUnableToConnect: 0, - waitingForAlarm: false}; + retryTimerID: 0}; } if (!this._conns[record.subscriptionUri].conn) { - this._conns[record.subscriptionUri].waitingForAlarm = false; this._listenForMsgs(record.subscriptionUri); } }, - // Start listening if subscriptions present. - _startConnectionsWaitingForAlarm: function() { - console.debug("startConnectionsWaitingForAlarm()"); - for (let subscriptionUri in this._conns) { - if ((this._conns[subscriptionUri]) && - !this._conns[subscriptionUri].conn && - this._conns[subscriptionUri].waitingForAlarm) { - this._conns[subscriptionUri].waitingForAlarm = false; - this._listenForMsgs(subscriptionUri); - } - } - }, - // Close connection and notify apps that subscription are gone. _shutdownSubscription: function(aSubscriptionUri) { console.debug("shutdownSubscriptions()"); @@ -696,21 +697,19 @@ this.PushServiceHttp2 = { uninit: function() { console.debug("uninit()"); + this._abortPendingSubscriptionRetries(); this._shutdownConnections(true); this._mainPushService = null; }, + _abortPendingSubscriptionRetries: function() { + this._listenersPendingRetry.forEach((listener) => listener.abortRetry()); + this._listenersPendingRetry.clear(); + }, - request: function(action, aRecord) { - switch (action) { - case "register": - return this._subscribeResource(aRecord); - case "unregister": - this._shutdownSubscription(aRecord.subscriptionUri); - return this._unsubscribeResource(aRecord.subscriptionUri); - default: - return Promise.reject(new Error("Unknown request type: " + action)); - } + unregister: function(aRecord) { + this._shutdownSubscription(aRecord.subscriptionUri); + return this._unsubscribeResource(aRecord.subscriptionUri); }, /** Push server has deleted subscription. @@ -721,7 +720,7 @@ this.PushServiceHttp2 = { */ _resubscribe: function(aSubscriptionUri) { this._mainPushService.getByKeyID(aSubscriptionUri) - .then(record => this._subscribeResource(record) + .then(record => this.register(record) .then(recordNew => { if (this._mainPushService) { this._mainPushService @@ -770,11 +769,21 @@ this.PushServiceHttp2 = { } }, + addListenerPendingRetry: function(aListener) { + this._listenersPendingRetry.add(aListener); + }, + + removeListenerPendingRetry: function(aListener) { + if (!this._listenersPendingRetry.remove(aListener)) { + console.debug("removeListenerPendingRetry: listener not in list?"); + } + }, + _pushChannelOnStop: function(aUri, aAckUri, aMessage, cryptoParams) { console.debug("pushChannelOnStop()"); this._mainPushService.receivedPushMessage( - aUri, aMessage, cryptoParams, record => { + aUri, "", aMessage, cryptoParams, record => { // Always update the stored record. return record; } @@ -785,10 +794,6 @@ this.PushServiceHttp2 = { err); }); }, - - onAlarmFired: function() { - this._startConnectionsWaitingForAlarm(); - }, }; function PushRecordHttp2(record) { diff --git a/dom/push/PushServiceWebSocket.jsm b/dom/push/PushServiceWebSocket.jsm index ca2bca0f42..872df01321 100644 --- a/dom/push/PushServiceWebSocket.jsm +++ b/dom/push/PushServiceWebSocket.jsm @@ -21,7 +21,6 @@ const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm"); const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm"); const { PushCrypto, - base64UrlDecode, getCryptoParams, } = Cu.import("resource://gre/modules/PushCrypto.jsm"); @@ -46,6 +45,26 @@ const kUDP_WAKEUP_WS_STATUS_CODE = 4774; // WebSocket Close status code sent // by server to signal that it can // wake client up using UDP. +// Maps ack statuses, unsubscribe reasons, and delivery error reasons to codes +// included in request payloads. +const kACK_STATUS_TO_CODE = { + [Ci.nsIPushErrorReporter.ACK_DELIVERED]: 100, + [Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR]: 101, + [Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED]: 102, +}; + +const kUNREGISTER_REASON_TO_CODE = { + [Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL]: 200, + [Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED]: 201, + [Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED]: 202, +}; + +const kDELIVERY_REASON_TO_CODE = { + [Ci.nsIPushErrorReporter.DELIVERY_UNCAUGHT_EXCEPTION]: 301, + [Ci.nsIPushErrorReporter.DELIVERY_UNHANDLED_REJECTION]: 302, + [Ci.nsIPushErrorReporter.DELIVERY_INTERNAL_ERROR]: 303, +}; + const prefs = new Preferences("dom.push."); this.EXPORTED_SYMBOLS = ["PushServiceWebSocket"]; @@ -140,44 +159,107 @@ this.PushServiceWebSocket = { }, observe: function(aSubject, aTopic, aData) { + if (aTopic == "nsPref:changed" && aData == "dom.push.userAgentID") { + this._onUAIDChanged(); + } else if (aTopic == "timer-callback") { + this._onTimerFired(aSubject); + } + }, - switch (aTopic) { - case "nsPref:changed": - if (aData == "dom.push.userAgentID") { - this._shutdownWS(); - this._reconnectAfterBackoff(); - } - break; - case "timer-callback": - if (aSubject == this._requestTimeoutTimer) { - if (Object.keys(this._registerRequests).length === 0) { - this._requestTimeoutTimer.cancel(); - } + /** + * Handles a UAID change. Unlike reconnects, we cancel all pending requests + * after disconnecting. Existing subscriptions stored in IndexedDB will be + * dropped on reconnect. + */ + _onUAIDChanged() { + console.debug("onUAIDChanged()"); - // Set to true if at least one request timed out. - let requestTimedOut = false; - for (let channelID in this._registerRequests) { - let duration = Date.now() - this._registerRequests[channelID].ctime; - // If any of the registration requests time out, all the ones after it - // also made to fail, since we are going to be disconnecting the - // socket. - if (requestTimedOut || duration > this._requestTimeout) { - requestTimedOut = true; - this._registerRequests[channelID] - .reject(new Error("Register request timed out for channel ID " + - channelID)); + this._shutdownWS(); + this._startBackoffTimer(); + }, - delete this._registerRequests[channelID]; - } - } + /** Handles a ping, backoff, or request timeout timer event. */ + _onTimerFired(timer) { + console.debug("onTimerFired()"); - // The most likely reason for a registration request timing out is - // that the socket has disconnected. Best to reconnect. + if (timer == this._pingTimer) { + this._sendPing(); + return; + } + + if (timer == this._backoffTimer) { + console.debug("onTimerFired: Reconnecting after backoff"); + this._beginWSSetup(); + return; + } + + if (timer == this._requestTimeoutTimer) { + this._timeOutRequests(); + return; + } + }, + + /** + * Sends a ping to the server. Bypasses the request queue, but starts the + * request timeout timer. If the socket is already closed, or the server + * does not respond within the timeout, the client will reconnect. + */ + _sendPing() { + console.debug("sendPing()"); + + this._startRequestTimeoutTimer(); + try { + this._wsSendMessage({}); + this._lastPingTime = Date.now(); + } catch (e) { + console.debug("sendPing: Error sending ping", e); + this._reconnect(); + } + }, + + /** Times out any pending requests. */ + _timeOutRequests() { + console.debug("timeOutRequests()"); + + if (!this._hasPendingRequests()) { + // Cancel the repeating timer and exit early if we aren't waiting for + // pongs or requests. + this._requestTimeoutTimer.cancel(); + return; + } + + let now = Date.now(); + + // Set to true if at least one request timed out, or we're still waiting + // for a pong after the request timeout. + let requestTimedOut = false; + + if (this._lastPingTime > 0 && + now - this._lastPingTime > this._requestTimeout) { + + console.debug("timeOutRequests: Did not receive pong in time"); + requestTimedOut = true; + + } else { + for (let [channelID, request] of this._registerRequests) { + let duration = now - request.ctime; + // If any of the registration requests time out, all the ones after it + // also made to fail, since we are going to be disconnecting the + // socket. + requestTimedOut |= duration > this._requestTimeout; if (requestTimedOut) { - this._reconnect(); + request.reject(new Error( + "Register request timed out for channel ID " + channelID)); + + this._registerRequests.delete(channelID); } } - break; + } + + // The most likely reason for a pong or registration request timing out is + // that the socket has disconnected. Best to reconnect. + if (requestTimedOut) { + this._reconnect(); } }, @@ -200,7 +282,7 @@ this.PushServiceWebSocket = { }, _ws: null, - _registerRequests: {}, + _registerRequests: new Map(), _currentState: STATE_SHUT_DOWN, _requestTimeout: 0, _requestTimeoutTimer: null, @@ -256,6 +338,22 @@ this.PushServiceWebSocket = { /** Indicates whether the server supports Web Push-style message delivery. */ _dataEnabled: false, + /** + * The last time the client sent a ping to the server. If non-zero, keeps the + * request timeout timer active. Reset to zero when the server responds with + * a pong or pending messages. + */ + _lastPingTime: 0, + + /** + * A one-shot timer used to ping the server, to avoid timing out idle + * connections. Reset to the ping interval on each incoming message. + */ + _pingTimer: null, + + /** A one-shot timer fired after the reconnect backoff period. */ + _backoffTimer: null, + /** * Sends a message to the Push Server through an open websocket. * typeof(msg) shall be an object @@ -306,7 +404,7 @@ this.PushServiceWebSocket = { _reconnect: function () { console.debug("reconnect()"); this._shutdownWS(false); - this._reconnectAfterBackoff(); + this._startBackoffTimer(); }, _shutdownWS: function(shouldCancelPending = true) { @@ -324,11 +422,10 @@ this.PushServiceWebSocket = { } catch (e) {} this._ws = null; - this._waitingForPong = false; - if (this._mainPushService) { - this._mainPushService.stopAlarm(); - } else { - console.error("shutdownWS: Uninitialized push service"); + this._lastPingTime = 0; + + if (this._pingTimer) { + this._pingTimer.cancel(); } if (shouldCancelPending) { @@ -352,6 +449,9 @@ this.PushServiceWebSocket = { // or receiving notifications. this._shutdownWS(); + if (this._backoffTimer) { + this._backoffTimer.cancel(); + } if (this._requestTimeoutTimer) { this._requestTimeoutTimer.cancel(); } @@ -365,12 +465,9 @@ this.PushServiceWebSocket = { * How retries work: The goal is to ensure websocket is always up on * networks not supporting UDP. So the websocket should only be shutdown if * onServerClose indicates UDP wakeup. If WS is closed due to socket error, - * _reconnectAfterBackoff() is called. The retry alarm is started and when + * _startBackoffTimer() is called. The retry timer is started and when * it times out, beginWSSetup() is called again. * - * On a successful connection, the alarm is cancelled in - * wsOnMessageAvailable() when the ping alarm is started. - * * If we are in the middle of a timeout (i.e. waiting), but * a register/unregister is called, we don't want to wait around anymore. * _sendRequest will automatically call beginWSSetup(), which will cancel the @@ -378,8 +475,8 @@ this.PushServiceWebSocket = { * timer event comes in (because the timer fired the event before it was * cancelled), so the connection won't be reset. */ - _reconnectAfterBackoff: function() { - console.debug("reconnectAfterBackoff()"); + _startBackoffTimer() { + console.debug("startBackoffTimer()"); //Calculate new ping interval this._calculateAdaptivePing(true /* wsWentDown */); @@ -390,13 +487,46 @@ this.PushServiceWebSocket = { this._retryFailCount++; - console.debug("reconnectAfterBackoff: Retry in", retryTimeout, + console.debug("startBackoffTimer: Retry in", retryTimeout, "Try number", this._retryFailCount); - if (this._mainPushService) { - this._mainPushService.setAlarm(retryTimeout); - } else { - console.error("reconnectAfterBackoff: Uninitialized push service"); + + if (!this._backoffTimer) { + this._backoffTimer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); } + this._backoffTimer.init(this, retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT); + }, + + /** Indicates whether we're waiting for pongs or requests. */ + _hasPendingRequests() { + return this._lastPingTime > 0 || this._registerRequests.size > 0; + }, + + /** + * Starts the request timeout timer unless we're already waiting for a pong + * or register request. + */ + _startRequestTimeoutTimer() { + if (this._hasPendingRequests()) { + return; + } + if (!this._requestTimeoutTimer) { + this._requestTimeoutTimer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + } + this._requestTimeoutTimer.init(this, + this._requestTimeout, + Ci.nsITimer.TYPE_REPEATING_SLACK); + }, + + /** Starts or resets the ping timer. */ + _startPingTimer() { + if (!this._pingTimer) { + this._pingTimer = Cc["@mozilla.org/timer;1"] + .createInstance(Ci.nsITimer); + } + this._pingTimer.init(this, prefs.get("pingInterval"), + Ci.nsITimer.TYPE_ONE_SHOT); }, /** @@ -580,8 +710,8 @@ this.PushServiceWebSocket = { } // Stop any pending reconnects scheduled for the near future. - if (this._mainPushService) { - this._mainPushService.stopAlarm(); + if (this._backoffTimer) { + this._backoffTimer.cancel(); } let uri = this._serverURI; @@ -623,74 +753,6 @@ this.PushServiceWebSocket = { return !!this._ws; }, - /** - * There is only one alarm active at any time. This alarm has 3 intervals - * corresponding to 3 tasks. - * - * 1) Reconnect on ping timeout. - * If we haven't received any messages from the server by the time this - * alarm fires, the connection is closed and PushService tries to - * reconnect, repurposing the alarm for (3). - * - * 2) Send a ping. - * The protocol sends a ping ({}) on the wire every pingInterval ms. Once - * it sends the ping, the alarm goes to task (1) which is waiting for - * a pong. If data is received after the ping is sent, - * _wsOnMessageAvailable() will reset the ping alarm (which cancels - * waiting for the pong). So as long as the connection is fine, pong alarm - * never fires. - * - * 3) Reconnect after backoff. - * The alarm is set by _reconnectAfterBackoff() and increases in duration - * every time we try and fail to connect. When it triggers, websocket - * setup begins again. On successful socket setup, the socket starts - * receiving messages. The alarm now goes to (2) where it monitors the - * WebSocket by sending a ping. Since incoming data is a sign of the - * connection being up, the ping alarm is reset every time data is - * received. - */ - onAlarmFired: function() { - // Conditions are arranged in decreasing specificity. - // i.e. when _waitingForPong is true, other conditions are also true. - if (this._waitingForPong) { - console.debug("onAlarmFired: Did not receive pong in time.", - "Reconnecting WebSocket"); - this._reconnect(); - } - else if (this._currentState == STATE_READY) { - // Send a ping. - // Bypass the queue; we don't want this to be kept pending. - // Watch out for exception in case the socket has disconnected. - // When this happens, we pretend the ping was sent and don't specially - // handle the exception, as the lack of a pong will lead to the socket - // being reset. - try { - this._wsSendMessage({}); - } catch (e) { - } - - this._waitingForPong = true; - this._mainPushService.setAlarm(prefs.get("requestTimeout")); - } - else if (this._mainPushService && this._mainPushService._alarmID !== null) { - console.debug("onAlarmFired: reconnect alarm fired"); - // Reconnect after back-off. - // The check for a non-null _alarmID prevents a situation where the alarm - // fires, but _shutdownWS() is called from another code-path (e.g. - // network state change) and we don't want to reconnect. - // - // It also handles the case where _beginWSSetup() is called from another - // code-path. - // - // alarmID will be non-null only when no shutdown/connect is - // called between _reconnectAfterBackoff() setting the alarm and the - // alarm firing. - - // Websocket is shut down. Backoff interval expired, try to connect. - this._beginWSSetup(); - } - }, - _acquireWakeLock: function() { if (!AppConstants.MOZ_B2G) { return; @@ -818,14 +880,13 @@ this.PushServiceWebSocket = { _handleRegisterReply: function(reply) { console.debug("handleRegisterReply()"); if (typeof reply.channelID !== "string" || - typeof this._registerRequests[reply.channelID] !== "object") { + !this._registerRequests.has(reply.channelID)) { return; } - let tmp = this._registerRequests[reply.channelID]; - delete this._registerRequests[reply.channelID]; - if (Object.keys(this._registerRequests).length === 0 && - this._requestTimeoutTimer) { + let tmp = this._registerRequests.get(reply.channelID); + this._registerRequests.delete(reply.channelID); + if (!this._hasPendingRequests()) { this._requestTimeoutTimer.cancel(); } @@ -863,39 +924,41 @@ this.PushServiceWebSocket = { update); return; } - // Unconditionally ack the update. This is important because the Push - // server requires the client to ack all outstanding updates before - // resuming delivery. However, the server doesn't verify the encryption - // params, and can't ensure that an update is encrypted correctly because - // it doesn't have the private key. Thus, if we only acked valid updates, - // it would be possible for a single invalid one to block delivery of all - // subsequent updates. A nack would be more appropriate for this case, but - // the protocol doesn't currently support them. - this._sendAck(update.channelID, update.version); if (typeof update.data != "string") { promise = this._mainPushService.receivedPushMessage( update.channelID, + update.version, null, null, record => record ); } else { let params = getCryptoParams(update.headers); - if (!params) { - console.warn("handleDataUpdate: Discarding invalid encrypted message", - update); - return; + if (params) { + let message = ChromeUtils.base64URLDecode(update.data, { + // The Push server may append padding. + padding: "ignore", + }); + promise = this._mainPushService.receivedPushMessage( + update.channelID, + update.version, + message, + params, + record => record + ); + } else { + promise = Promise.reject(new Error("Invalid crypto headers")); } - let message = base64UrlDecode(update.data); - promise = this._mainPushService.receivedPushMessage( - update.channelID, - message, - params, - record => record - ); } - promise.catch(err => { - console.error("handleDataUpdate: Error delivering message", err); + promise.then(status => { + this._sendAck(update.channelID, update.version, status); + }, err => { + console.error("handleDataUpdate: Error delivering message", update, err); + this._sendAck(update.channelID, update.version, + Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR); + }).catch(err => { + console.error("handleDataUpdate: Error acknowledging message", update, + err); }); }, @@ -939,18 +1002,32 @@ this.PushServiceWebSocket = { // FIXME(nsm): this relies on app update notification being infallible! // eventually fix this this._receivedUpdate(update.channelID, version); - this._sendAck(update.channelID, version); } } }, - // FIXME(nsm): batch acks for efficiency reasons. - _sendAck: function(channelID, version) { + reportDeliveryError(messageID, reason) { + console.debug("reportDeliveryError()"); + let code = kDELIVERY_REASON_TO_CODE[reason]; + if (!code) { + throw new Error('Invalid delivery error reason'); + } + let data = {messageType: 'nack', + version: messageID, + code: code}; + this._queueRequest(data); + }, + + _sendAck(channelID, version, status) { console.debug("sendAck()"); - var data = {messageType: 'ack', + let code = kACK_STATUS_TO_CODE[status]; + if (!code) { + throw new Error('Invalid ack status'); + } + let data = {messageType: 'ack', updates: [{channelID: channelID, - version: version}] - }; + version: version, + code: code}]}; this._queueRequest(data); }, @@ -961,47 +1038,48 @@ this.PushServiceWebSocket = { return uuidGenerator.generateUUID().toString().slice(1, -1); }, - request: function(action, record) { - console.debug("request() ", action); + register(record) { + console.debug("register() ", record); - if (Object.keys(this._registerRequests).length === 0) { - // start the timer since we now have at least one request - if (!this._requestTimeoutTimer) { - this._requestTimeoutTimer = Cc["@mozilla.org/timer;1"] - .createInstance(Ci.nsITimer); - } - this._requestTimeoutTimer.init(this, - this._requestTimeout, - Ci.nsITimer.TYPE_REPEATING_SLACK); - } + // start the timer since we now have at least one request + this._startRequestTimeoutTimer(); - if (action == "register") { - let data = {channelID: this._generateID(), - messageType: action}; + let data = {channelID: this._generateID(), + messageType: "register"}; - return new Promise((resolve, reject) => { - this._registerRequests[data.channelID] = {record: record, - resolve: resolve, - reject: reject, - ctime: Date.now() - }; - this._queueRequest(data); - }).then(record => { - if (!this._dataEnabled) { - return record; - } - return PushCrypto.generateKeys() - .then(([publicKey, privateKey]) => { - record.p256dhPublicKey = publicKey; - record.p256dhPrivateKey = privateKey; - record.authenticationSecret = PushCrypto.generateAuthenticationSecret(); - return record; - }); + return new Promise((resolve, reject) => { + this._registerRequests.set(data.channelID, { + record: record, + resolve: resolve, + reject: reject, + ctime: Date.now(), }); - } + this._queueRequest(data); + }).then(record => { + if (!this._dataEnabled) { + return record; + } + return PushCrypto.generateKeys() + .then(([publicKey, privateKey]) => { + record.p256dhPublicKey = publicKey; + record.p256dhPrivateKey = privateKey; + record.authenticationSecret = PushCrypto.generateAuthenticationSecret(); + return record; + }); + }); + }, - this._queueRequest({channelID: record.channelID, - messageType: action}); + unregister(record, reason) { + console.debug("unregister() ", record, reason); + + let code = kUNREGISTER_REASON_TO_CODE[reason]; + if (!code) { + return Promise.reject(new Error('Invalid unregister reason')); + } + let data = {channelID: record.channelID, + messageType: "unregister", + code: code}; + this._queueRequest(data); return Promise.resolve(); }, @@ -1021,7 +1099,7 @@ this.PushServiceWebSocket = { _send(data) { if (this._currentState == STATE_READY) { if (data.messageType != "register" || - typeof this._registerRequests[data.channelID] == "object") { + this._registerRequests.has(data.channelID)) { // check if request has not been cancelled this._wsSendMessage(data); @@ -1030,12 +1108,14 @@ this.PushServiceWebSocket = { }, _sendRegisterRequests() { - this._enqueue(_ => Promise.all(Object.keys(this._registerRequests).map(channelID => - this._send({ - messageType: "register", - channelID: channelID, - }) - ))); + this._enqueue(_ => { + for (let channelID of this._registerRequests.keys()) { + this._send({ + messageType: "register", + channelID: channelID, + }); + } + }); }, _queueRequest(data) { @@ -1067,7 +1147,7 @@ this.PushServiceWebSocket = { _receivedUpdate: function(aChannelID, aLatestVersion) { console.debug("receivedUpdate: Updating", aChannelID, "->", aLatestVersion); - this._mainPushService.receivedPushMessage(aChannelID, null, null, record => { + this._mainPushService.receivedPushMessage(aChannelID, "", null, null, record => { if (record.version === null || record.version < aLatestVersion) { console.debug("receivedUpdate: Version changed for", aChannelID, @@ -1078,6 +1158,11 @@ this.PushServiceWebSocket = { console.debug("receivedUpdate: No significant version change for", aChannelID, aLatestVersion); return null; + }).then(status => { + this._sendAck(aChannelID, aLatestVersion, status); + }).catch(err => { + console.error("receivedUpdate: Error acknowledging message", aChannelID, + aLatestVersion, err); }); }, @@ -1148,7 +1233,8 @@ this.PushServiceWebSocket = { _wsOnMessageAvailable: function(context, message) { console.debug("wsOnMessageAvailable()", message); - this._waitingForPong = false; + // Clearing the last ping time indicates we're no longer waiting for a pong. + this._lastPingTime = 0; let reply; try { @@ -1174,8 +1260,8 @@ this.PushServiceWebSocket = { } // Reset the ping timer. Note: This path is executed at every step of the - // handshake, so this alarm does not need to be set explicitly at startup. - this._mainPushService.setAlarm(prefs.get("pingInterval")); + // handshake, so this timer does not need to be set explicitly at startup. + this._startPingTimer(); // If it is a ping, do not handle the message. if (doNotHandle) { @@ -1232,11 +1318,10 @@ this.PushServiceWebSocket = { * Rejects all pending register requests with errors. */ _cancelRegisterRequests: function() { - for (let channelID in this._registerRequests) { - let request = this._registerRequests[channelID]; - delete this._registerRequests[channelID]; + for (let request of this._registerRequests.values()) { request.reject(new Error("Register request aborted")); } + this._registerRequests.clear(); }, _makeUDPSocket: function() { diff --git a/dom/push/PushSubscription.cpp b/dom/push/PushSubscription.cpp new file mode 100644 index 0000000000..d590458eca --- /dev/null +++ b/dom/push/PushSubscription.cpp @@ -0,0 +1,388 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/PushSubscription.h" + +#include "nsIPushService.h" +#include "nsIScriptObjectPrincipal.h" + +#include "mozilla/Base64.h" +#include "mozilla/unused.h" + +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseWorkerProxy.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerScope.h" +#include "mozilla/dom/workers/Workers.h" + +namespace mozilla { +namespace dom { + +using namespace workers; + +namespace { + +class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback +{ +public: + NS_DECL_ISUPPORTS + + explicit UnsubscribeResultCallback(Promise* aPromise) + : mPromise(aPromise) + { + AssertIsOnMainThread(); + } + + NS_IMETHOD + OnUnsubscribe(nsresult aStatus, bool aSuccess) override + { + if (NS_SUCCEEDED(aStatus)) { + mPromise->MaybeResolve(aSuccess); + } else { + mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); + } + + return NS_OK; + } + +private: + ~UnsubscribeResultCallback() + {} + + RefPtr mPromise; +}; + +NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback) + +class UnsubscribeResultRunnable final : public WorkerRunnable +{ +public: + UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate, + already_AddRefed&& aProxy, + nsresult aStatus, + bool aSuccess) + : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount) + , mProxy(Move(aProxy)) + , mStatus(aStatus) + , mSuccess(aSuccess) + { + AssertIsOnMainThread(); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + RefPtr promise = mProxy->WorkerPromise(); + if (NS_SUCCEEDED(mStatus)) { + promise->MaybeResolve(mSuccess); + } else { + promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); + } + + mProxy->CleanUp(); + + return true; + } +private: + ~UnsubscribeResultRunnable() + {} + + RefPtr mProxy; + nsresult mStatus; + bool mSuccess; +}; + +class WorkerUnsubscribeResultCallback final : public nsIUnsubscribeResultCallback +{ +public: + NS_DECL_ISUPPORTS + + explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy) + : mProxy(aProxy) + { + AssertIsOnMainThread(); + } + + NS_IMETHOD + OnUnsubscribe(nsresult aStatus, bool aSuccess) override + { + AssertIsOnMainThread(); + MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?"); + + MutexAutoLock lock(mProxy->Lock()); + if (mProxy->CleanedUp()) { + return NS_OK; + } + + WorkerPrivate* worker = mProxy->GetWorkerPrivate(); + RefPtr r = + new UnsubscribeResultRunnable(worker, mProxy.forget(), aStatus, aSuccess); + MOZ_ALWAYS_TRUE(r->Dispatch()); + + return NS_OK; + } + +private: + ~WorkerUnsubscribeResultCallback() + { + } + + RefPtr mProxy; +}; + +NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback) + +class UnsubscribeRunnable final : public nsRunnable +{ +public: + UnsubscribeRunnable(PromiseWorkerProxy* aProxy, + const nsAString& aScope) + : mProxy(aProxy) + , mScope(aScope) + { + MOZ_ASSERT(aProxy); + MOZ_ASSERT(!aScope.IsEmpty()); + } + + NS_IMETHOD + Run() override + { + AssertIsOnMainThread(); + + nsCOMPtr principal; + + { + MutexAutoLock lock(mProxy->Lock()); + if (mProxy->CleanedUp()) { + return NS_OK; + } + principal = mProxy->GetWorkerPrivate()->GetPrincipal(); + } + + MOZ_ASSERT(principal); + + RefPtr callback = + new WorkerUnsubscribeResultCallback(mProxy); + + nsCOMPtr service = + do_GetService("@mozilla.org/push/Service;1"); + if (NS_WARN_IF(!service)) { + callback->OnUnsubscribe(NS_ERROR_FAILURE, false); + return NS_OK; + } + + if (NS_WARN_IF(NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) { + callback->OnUnsubscribe(NS_ERROR_FAILURE, false); + return NS_OK; + } + + return NS_OK; + } + +private: + ~UnsubscribeRunnable() + {} + + RefPtr mProxy; + nsString mScope; +}; + +bool +CopyArrayBufferToArray(const ArrayBuffer& aBuffer, + nsTArray& aArray) +{ + aBuffer.ComputeLengthAndData(); + if (!aArray.SetLength(aBuffer.Length(), fallible) || + !aArray.ReplaceElementsAt(0, aBuffer.Length(), aBuffer.Data(), + aBuffer.Length(), fallible)) { + return false; + } + return true; +} + +} // anonymous namespace + +PushSubscription::PushSubscription(nsIGlobalObject* aGlobal, + const nsAString& aEndpoint, + const nsAString& aScope, + nsTArray&& aRawP256dhKey, + nsTArray&& aAuthSecret) + : mEndpoint(aEndpoint) + , mScope(aScope) + , mRawP256dhKey(Move(aRawP256dhKey)) + , mAuthSecret(Move(aAuthSecret)) +{ + if (NS_IsMainThread()) { + mGlobal = aGlobal; + } else { +#ifdef DEBUG + // There's only one global on a worker, so we don't need to pass a global + // object to the constructor. + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); +#endif + } +} + +PushSubscription::~PushSubscription() +{} + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription) +NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* +PushSubscription::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return PushSubscriptionBinding::Wrap(aCx, this, aGivenProto); +} + +// static +already_AddRefed +PushSubscription::Constructor(GlobalObject& aGlobal, + const nsAString& aEndpoint, + const nsAString& aScope, + const Nullable& aP256dhKey, + const Nullable& aAuthSecret, + ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); + + nsTArray rawKey, authSecret; + if ((!aP256dhKey.IsNull() && !CopyArrayBufferToArray(aP256dhKey.Value(), + rawKey)) || + (!aAuthSecret.IsNull() && !CopyArrayBufferToArray(aAuthSecret.Value(), + authSecret))) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + RefPtr sub = new PushSubscription(global, + aEndpoint, + aScope, + Move(rawKey), + Move(authSecret)); + + return sub.forget(); +} + +already_AddRefed +PushSubscription::Unsubscribe(ErrorResult& aRv) +{ + if (!NS_IsMainThread()) { + RefPtr p = UnsubscribeFromWorker(aRv); + return p.forget(); + } + + MOZ_ASSERT(mGlobal); + + nsCOMPtr service = + do_GetService("@mozilla.org/push/Service;1"); + if (NS_WARN_IF(!service)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr sop = do_QueryInterface(mGlobal); + if (!sop) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr p = Promise::Create(mGlobal, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr callback = + new UnsubscribeResultCallback(p); + Unused << NS_WARN_IF(NS_FAILED( + service->Unsubscribe(mScope, sop->GetPrincipal(), callback))); + + return p.forget(); +} + +void +PushSubscription::GetKey(JSContext* aCx, + PushEncryptionKeyName aType, + JS::MutableHandle aKey) +{ + if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) { + aKey.set(ArrayBuffer::Create(aCx, + mRawP256dhKey.Length(), + mRawP256dhKey.Elements())); + } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) { + aKey.set(ArrayBuffer::Create(aCx, + mAuthSecret.Length(), + mAuthSecret.Elements())); + } else { + aKey.set(nullptr); + } +} + +void +PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv) +{ + aJSON.mEndpoint.Construct(); + aJSON.mEndpoint.Value() = mEndpoint; + + Base64URLEncodeOptions encodeOptions; + encodeOptions.mPad = false; + + aJSON.mKeys.mP256dh.Construct(); + nsresult rv = Base64URLEncode(mRawP256dhKey.Length(), + mRawP256dhKey.Elements(), + encodeOptions, + aJSON.mKeys.mP256dh.Value()); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + aJSON.mKeys.mAuth.Construct(); + rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(), + encodeOptions, aJSON.mKeys.mAuth.Value()); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } +} + +already_AddRefed +PushSubscription::UnsubscribeFromWorker(ErrorResult& aRv) +{ + WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); + + nsCOMPtr global = worker->GlobalScope(); + RefPtr p = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + RefPtr proxy = PromiseWorkerProxy::Create(worker, p); + if (!proxy) { + p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); + return p.forget(); + } + + RefPtr r = + new UnsubscribeRunnable(proxy, mScope); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); + + return p.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/push/PushSubscription.h b/dom/push/PushSubscription.h new file mode 100644 index 0000000000..8ca60b7e20 --- /dev/null +++ b/dom/push/PushSubscription.h @@ -0,0 +1,95 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_PushSubscription_h +#define mozilla_dom_PushSubscription_h + +#include "jsapi.h" +#include "nsCOMPtr.h" +#include "nsWrapperCache.h" + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/RefPtr.h" + +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/PushSubscriptionBinding.h" +#include "mozilla/dom/TypedArray.h" + +class nsIGlobalObject; + +namespace mozilla { +namespace dom { + +namespace workers { +class WorkerPrivate; +} + +class Promise; + +class PushSubscription final : public nsISupports + , public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscription) + + PushSubscription(nsIGlobalObject* aGlobal, + const nsAString& aEndpoint, + const nsAString& aScope, + nsTArray&& aP256dhKey, + nsTArray&& aAuthSecret); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + nsIGlobalObject* + GetParentObject() const + { + return mGlobal; + } + + void + GetEndpoint(nsAString& aEndpoint) const + { + aEndpoint = mEndpoint; + } + + void + GetKey(JSContext* cx, + PushEncryptionKeyName aType, + JS::MutableHandle aKey); + + static already_AddRefed + Constructor(GlobalObject& aGlobal, + const nsAString& aEndpoint, + const nsAString& aScope, + const Nullable& aP256dhKey, + const Nullable& aAuthSecret, + ErrorResult& aRv); + + already_AddRefed + Unsubscribe(ErrorResult& aRv); + + void + ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv); + +protected: + ~PushSubscription(); + +private: + already_AddRefed + UnsubscribeFromWorker(ErrorResult& aRv); + + nsString mEndpoint; + nsString mScope; + nsTArray mRawP256dhKey; + nsTArray mAuthSecret; + nsCOMPtr mGlobal; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PushSubscription_h diff --git a/dom/push/moz.build b/dom/push/moz.build index 845f4a8349..06830e6b72 100644 --- a/dom/push/moz.build +++ b/dom/push/moz.build @@ -14,10 +14,20 @@ EXTRA_JS_MODULES += [ 'PushDB.jsm', 'PushRecord.jsm', 'PushService.jsm', - 'PushServiceHttp2.jsm', - 'PushServiceWebSocket.jsm', ] +if CONFIG['MOZ_BUILD_APP'] != 'mobile/android': + # Everything but Fennec. + EXTRA_JS_MODULES += [ + 'PushServiceHttp2.jsm', + 'PushServiceWebSocket.jsm', + ] +else: + # Fennec only. + EXTRA_JS_MODULES += [ + 'PushServiceAndroidGCM.jsm', + ] + MOCHITEST_MANIFESTS += [ 'test/mochitest.ini', ] @@ -29,11 +39,13 @@ XPCSHELL_TESTS_MANIFESTS += [ EXPORTS.mozilla.dom += [ 'PushManager.h', 'PushNotifier.h', + 'PushSubscription.h', ] UNIFIED_SOURCES += [ 'PushManager.cpp', 'PushNotifier.cpp', + 'PushSubscription.cpp', ] TEST_DIRS += ['test/xpcshell'] diff --git a/dom/push/test/error_worker.js b/dom/push/test/error_worker.js new file mode 100644 index 0000000000..a50f838045 --- /dev/null +++ b/dom/push/test/error_worker.js @@ -0,0 +1,10 @@ +this.onpush = function(event) { + var request = event.data.json(); + if (request.type == "exception") { + throw new Error("Uncaught exception"); + } + if (request.type == "rejection") { + event.waitUntil(Promise.reject( + new Error("Unhandled rejection"))); + } +}; diff --git a/dom/push/test/mochitest.ini b/dom/push/test/mochitest.ini index d9339aa4bd..1ee91ce42b 100644 --- a/dom/push/test/mochitest.ini +++ b/dom/push/test/mochitest.ini @@ -1,5 +1,5 @@ [DEFAULT] -subsuite = push +skip-if = os == "android" || toolkit == "gonk" support-files = worker.js frame.html @@ -7,7 +7,7 @@ support-files = lifetime_worker.js test_utils.js mockpushserviceparent.js -skip-if = os == "android" || toolkit == "gonk" + error_worker.js [test_has_permissions.html] [test_permissions.html] @@ -20,3 +20,4 @@ skip-if = os == "android" || toolkit == "gonk" [test_data.html] [test_try_registering_offline_disabled.html] [test_serviceworker_lifetime.html] +[test_error_reporting.html] diff --git a/dom/push/test/mockpushserviceparent.js b/dom/push/test/mockpushserviceparent.js index 344dbf249a..8bd73d7c22 100644 --- a/dom/push/test/mockpushserviceparent.js +++ b/dom/push/test/mockpushserviceparent.js @@ -48,7 +48,7 @@ MockWebSocketParent.prototype = { }, sendMsg(msg) { - sendAsyncMessage("client-msg", msg); + sendAsyncMessage("socket-client-msg", msg); }, close() { @@ -81,35 +81,105 @@ var pushService = Cc["@mozilla.org/push/Service;1"]. getService(Ci.nsIPushService). wrappedJSObject; -var mockWebSocket; +var mockSocket; +var serverMsgs = []; -addMessageListener("setup", function () { - mockWebSocket = new Promise((resolve, reject) => { - var mockSocket = null; - pushService.replaceServiceBackend({ - serverURI: "wss://push.example.org/", - networkInfo: new MockNetworkInfo(), - makeWebSocket(uri) { - if (!mockSocket) { - mockSocket = new MockWebSocketParent(uri); - resolve(mockSocket); - } - - return mockSocket; +addMessageListener("socket-setup", function () { + pushService.replaceServiceBackend({ + serverURI: "wss://push.example.org/", + networkInfo: new MockNetworkInfo(), + makeWebSocket(uri) { + mockSocket = new MockWebSocketParent(uri); + while (serverMsgs.length > 0) { + let msg = serverMsgs.shift(); + mockSocket.serverSendMsg(msg); } + return mockSocket; + } + }); +}); + +addMessageListener("socket-teardown", function (msg) { + pushService.restoreServiceBackend().then(_ => { + serverMsgs.length = 0; + if (mockSocket) { + mockSocket.close(); + mockSocket = null; + } + sendAsyncMessage("socket-server-teardown"); + }).catch(error => { + Cu.reportError(`Error restoring service backend: ${error}`); + }) +}); + +addMessageListener("socket-server-msg", function (msg) { + if (mockSocket) { + mockSocket.serverSendMsg(msg); + } else { + serverMsgs.push(msg); + } +}); + +var MockService = { + requestID: 1, + resolvers: new Map(), + + sendRequest(name, params) { + return new Promise((resolve, reject) => { + let id = this.requestID++; + this.resolvers.set(id, { resolve, reject }); + sendAsyncMessage("service-request", { + name: name, + id: id, + params: params, + }); }); - }); + }, + + handleResponse(response) { + if (!this.resolvers.has(response.id)) { + Cu.reportError(`Unexpected response for request ${response.id}`); + return; + } + let resolver = this.resolvers.get(response.id); + this.resolvers.delete(response.id); + if (response.error) { + resolver.reject(response.error); + } else { + resolver.resolve(response.result); + } + }, + + init() {}, + + register(pageRecord) { + return this.sendRequest("register", pageRecord); + }, + + registration(pageRecord) { + return this.sendRequest("registration", pageRecord); + }, + + unregister(pageRecord) { + return this.sendRequest("unregister", pageRecord); + }, + + reportDeliveryError(messageId, reason) { + sendAsyncMessage("service-delivery-error", { + messageId: messageId, + reason: reason, + }); + }, +}; + +addMessageListener("service-replace", function () { + pushService.service = MockService; }); -addMessageListener("teardown", function () { - mockWebSocket.then(socket => { - socket.close(); - pushService.restoreServiceBackend(); - }); +addMessageListener("service-restore", function () { + pushService.service = null; }); -addMessageListener("server-msg", function (msg) { - mockWebSocket.then(socket => { - socket.serverSendMsg(msg); - }); +addMessageListener("service-response", function (response) { + MockService.handleResponse(response); }); diff --git a/dom/push/test/test_data.html b/dom/push/test/test_data.html index 0bbb816956..7dff75e5eb 100644 --- a/dom/push/test/test_data.html +++ b/dom/push/test/test_data.html @@ -42,7 +42,7 @@ http://creativecommons.org/licenses/publicdomain/ var registration; add_task(function* start() { - yield setupPrefsAndMock(mockSocket); + yield setupPrefsAndMockSocket(mockSocket); yield setPushPermission(true); var url = "worker.js" + "?" + (Math.random()); @@ -103,14 +103,14 @@ http://creativecommons.org/licenses/publicdomain/ add_task(function* comparePublicKey() { var data = yield sendRequestToWorker({ type: "publicKey" }); var p256dhKey = new Uint8Array(pushSubscription.getKey("p256dh")); - ok(p256dhKey.length > 0, "Missing key share"); + is(p256dhKey.length, 65, "Key share should be 65 octets"); isDeeply( p256dhKey, new Uint8Array(data.p256dh), "Mismatched key share" ); var authSecret = new Uint8Array(pushSubscription.getKey("auth")); - ok(authSecret.length > 0, "Missing auth secret"); + ok(authSecret.length, 16, "Auth secret should be 16 octets"); isDeeply( authSecret, new Uint8Array(data.auth), diff --git a/dom/push/test/test_error_reporting.html b/dom/push/test/test_error_reporting.html new file mode 100644 index 0000000000..f7ce7252dd --- /dev/null +++ b/dom/push/test/test_error_reporting.html @@ -0,0 +1,129 @@ + + + + + Test for Bug 1246341 + + + + + + +Mozilla Bug 1246341 +

+ +
+
+ + + + + diff --git a/dom/push/test/test_multiple_register.html b/dom/push/test/test_multiple_register.html index fb14660ef1..f5513ae89f 100644 --- a/dom/push/test/test_multiple_register.html +++ b/dom/push/test/test_multiple_register.html @@ -119,7 +119,7 @@ http://creativecommons.org/licenses/publicdomain/ }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission('push', true, document); SimpleTest.waitForExplicitFinish(); diff --git a/dom/push/test/test_multiple_register_different_scope.html b/dom/push/test/test_multiple_register_different_scope.html index c626ed5608..6d303c0c65 100644 --- a/dom/push/test/test_multiple_register_different_scope.html +++ b/dom/push/test/test_multiple_register_different_scope.html @@ -114,7 +114,7 @@ http://creativecommons.org/licenses/publicdomain/ }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission('push', true, document); SimpleTest.waitForExplicitFinish(); diff --git a/dom/push/test/test_multiple_register_during_service_activation.html b/dom/push/test/test_multiple_register_during_service_activation.html index efbfcdd281..fe165883b8 100644 --- a/dom/push/test/test_multiple_register_during_service_activation.html +++ b/dom/push/test/test_multiple_register_during_service_activation.html @@ -57,15 +57,19 @@ http://creativecommons.org/licenses/publicdomain/ function setupMultipleSubscriptions(swr) { // We need to do this to restart service so that a queue will be formed. - teardownMockPushService(); - setupMockPushService(new MockWebSocket()); + let promiseTeardown = teardownMockPushSocket(); + setupMockPushSocket(new MockWebSocket()); + var pushSubscription; return Promise.all([ subscribe(swr), subscribe(swr) ]).then(a => { ok(a[0].endpoint == a[1].endpoint, "setupMultipleSubscriptions - Got the same endpoint back."); - return a[0]; + pushSubscription = a[0]; + return promiseTeardown; + }).then(_ => { + return pushSubscription; }, err => { ok(false, "could not register for push notification"); throw err; @@ -99,7 +103,7 @@ http://creativecommons.org/licenses/publicdomain/ }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission('push', true, document); SimpleTest.waitForExplicitFinish(); diff --git a/dom/push/test/test_register.html b/dom/push/test/test_register.html index 31925dfee4..a6680fb1ec 100644 --- a/dom/push/test/test_register.html +++ b/dom/push/test/test_register.html @@ -46,7 +46,7 @@ http://creativecommons.org/licenses/publicdomain/ var registration; add_task(function* start() { - yield setupPrefsAndMock(mockSocket); + yield setupPrefsAndMockSocket(mockSocket); yield setPushPermission(true); var url = "worker.js" + "?" + (Math.random()); diff --git a/dom/push/test/test_serviceworker_lifetime.html b/dom/push/test/test_serviceworker_lifetime.html index 3219680dc6..ce3288118a 100644 --- a/dom/push/test/test_serviceworker_lifetime.html +++ b/dom/push/test/test_serviceworker_lifetime.html @@ -332,7 +332,7 @@ }).then(SimpleTest.finish); } - setupPrefsAndMock(mockSocket).then(_ => runTest()); + setupPrefsAndMockSocket(mockSocket).then(_ => runTest()); SpecialPowers.addPermission('desktop-notification', true, document); SimpleTest.waitForExplicitFinish(); diff --git a/dom/push/test/test_subscription_change.html b/dom/push/test/test_subscription_change.html index a5814c25cf..9b72ce6108 100644 --- a/dom/push/test/test_subscription_change.html +++ b/dom/push/test/test_subscription_change.html @@ -27,7 +27,7 @@ http://creativecommons.org/licenses/publicdomain/ var registration; add_task(function* start() { - yield setupPrefsAndMock(new MockWebSocket()); + yield setupPrefsAndMockSocket(new MockWebSocket()); yield setPushPermission(true); var url = "worker.js" + "?" + (Math.random()); diff --git a/dom/push/test/test_try_registering_offline_disabled.html b/dom/push/test/test_try_registering_offline_disabled.html index f77675d7d2..87c94317da 100644 --- a/dom/push/test/test_try_registering_offline_disabled.html +++ b/dom/push/test/test_try_registering_offline_disabled.html @@ -296,7 +296,7 @@ http://creativecommons.org/licenses/publicdomain/ .then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission('push', true, document); SimpleTest.waitForExplicitFinish(); diff --git a/dom/push/test/test_unregister.html b/dom/push/test/test_unregister.html index ad00da297a..0f03d3e443 100644 --- a/dom/push/test/test_unregister.html +++ b/dom/push/test/test_unregister.html @@ -80,7 +80,7 @@ http://creativecommons.org/licenses/publicdomain/ }).then(SimpleTest.finish); } - setupPrefsAndMock(new MockWebSocket()).then(_ => runTest()); + setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest()); SpecialPowers.addPermission('push', true, document); SimpleTest.waitForExplicitFinish(); diff --git a/dom/push/test/test_utils.js b/dom/push/test/test_utils.js index b109ec8818..b2a805dd0b 100644 --- a/dom/push/test/test_utils.js +++ b/dom/push/test/test_utils.js @@ -4,24 +4,70 @@ let url = SimpleTest.getTestFileURL("mockpushserviceparent.js"); let chromeScript = SpecialPowers.loadChromeScript(url); + /** + * Replaces `PushService.jsm` with a mock implementation that handles requests + * from the DOM API. This allows tests to simulate local errors and error + * reporting, bypassing the `PushService.jsm` machinery. + */ + function replacePushService(mockService) { + chromeScript.sendSyncMessage("service-replace"); + chromeScript.addMessageListener("service-delivery-error", function(msg) { + mockService.reportDeliveryError(msg.messageId, msg.reason); + }); + chromeScript.addMessageListener("service-request", function(msg) { + let promise; + try { + let handler = mockService[msg.name]; + promise = Promise.resolve(handler(msg.params)); + } catch (error) { + promise = Promise.reject(error); + } + promise.then(result => { + chromeScript.sendAsyncMessage("service-response", { + id: msg.id, + result: result, + }); + }, error => { + chromeScript.sendAsyncMessage("service-response", { + id: msg.id, + error: error, + }); + }); + }); + } + + function restorePushService() { + chromeScript.sendSyncMessage("service-restore"); + } + let userAgentID = "8e1c93a9-139b-419c-b200-e715bb1e8ce8"; let currentMockSocket = null; - function setupMockPushService(mockWebSocket) { + /** + * Sets up a mock connection for the WebSocket backend. This only replaces + * the transport layer; `PushService.jsm` still handles DOM API requests, + * observes permission changes, writes to IndexedDB, and notifies service + * workers of incoming push messages. + */ + function setupMockPushSocket(mockWebSocket) { currentMockSocket = mockWebSocket; currentMockSocket._isActive = true; - chromeScript.sendSyncMessage("setup"); - chromeScript.addMessageListener("client-msg", function(msg) { + chromeScript.sendSyncMessage("socket-setup"); + chromeScript.addMessageListener("socket-client-msg", function(msg) { mockWebSocket.handleMessage(msg); }); } - function teardownMockPushService() { + function teardownMockPushSocket() { if (currentMockSocket) { - currentMockSocket._isActive = false; - chromeScript.sendSyncMessage("teardown"); + return new Promise(resolve => { + currentMockSocket._isActive = false; + chromeScript.addMessageListener("socket-server-teardown", resolve); + chromeScript.sendSyncMessage("socket-teardown"); + }); } + return Promise.resolve(); } /** @@ -90,24 +136,27 @@ serverSendMsg(msg) { if (this._isActive) { - chromeScript.sendAsyncMessage("server-msg", msg); + chromeScript.sendAsyncMessage("socket-server-msg", msg); } }, }; g.MockWebSocket = MockWebSocket; - g.setupMockPushService = setupMockPushService; - g.teardownMockPushService = teardownMockPushService; + g.setupMockPushSocket = setupMockPushSocket; + g.teardownMockPushSocket = teardownMockPushSocket; + g.replacePushService = replacePushService; + g.restorePushService = restorePushService; }(this)); // Remove permissions and prefs when the test finishes. SimpleTest.registerCleanupFunction(() => { - new Promise(resolve => { + return new Promise(resolve => { SpecialPowers.flushPermissions(_ => { SpecialPowers.flushPrefEnv(resolve); }); }).then(_ => { - teardownMockPushService(); + restorePushService(); + return teardownMockPushSocket(); }); }); @@ -119,9 +168,8 @@ function setPushPermission(allow) { }); } -function setupPrefsAndMock(mockSocket) { +function setupPrefs() { return new Promise(resolve => { - setupMockPushService(mockSocket); SpecialPowers.pushPrefEnv({"set": [ ["dom.push.enabled", true], ["dom.push.connection.enabled", true], @@ -132,6 +180,16 @@ function setupPrefsAndMock(mockSocket) { }); } +function setupPrefsAndReplaceService(mockService) { + replacePushService(mockService); + return setupPrefs(); +} + +function setupPrefsAndMockSocket(mockSocket) { + setupMockPushSocket(mockSocket); + return setupPrefs(); +} + function injectControlledFrame(target = document.body) { return new Promise(function(res, rej) { var iframe = document.createElement("iframe"); @@ -146,6 +204,10 @@ function injectControlledFrame(target = document.body) { return iframe ? iframe.contentWindow.waitOnWorkerMessage(type) : Promise.reject(new Error("Frame removed from document")); }, + innerWindowId() { + var utils = SpecialPowers.getDOMWindowUtils(iframe.contentWindow); + return utils.currentInnerWindowID; + }, }; iframe.onload = () => res(controlledFrame); diff --git a/dom/push/test/xpcshell/head.js b/dom/push/test/xpcshell/head.js index a7c6a602ba..bb978d7710 100644 --- a/dom/push/test/xpcshell/head.js +++ b/dom/push/test/xpcshell/head.js @@ -22,8 +22,6 @@ XPCOMUtils.defineLazyServiceGetter(this, 'PushServiceComponent', const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {}); const servicePrefs = new Preferences('dom.push.'); -const DEFAULT_TIMEOUT = 5000; - const WEBSOCKET_CLOSE_GOING_AWAY = 1001; var isParent = Cc['@mozilla.org/xre/runtime;1'] @@ -117,31 +115,6 @@ function promiseObserverNotification(topic, matchFunc) { }); } -/** - * Waits for a promise to settle. Returns a rejected promise if the promise - * is not resolved or rejected within the given delay. - * - * @param {Promise} promise The pending promise. - * @param {Number} delay The time to wait before rejecting the promise. - * @param {String} [message] The rejection message if the promise times out. - * @returns {Promise} A promise that settles with the value of the pending - * promise, or rejects if the pending promise times out. - */ -function waitForPromise(promise, delay, message = 'Timed out waiting on promise') { - let timeoutDefer = Promise.defer(); - let id = setTimeout(() => timeoutDefer.reject(new Error(message)), delay); - return Promise.race([ - promise.then(value => { - clearTimeout(id); - return value; - }, error => { - clearTimeout(id); - throw error; - }), - timeoutDefer.promise - ]); -} - /** * Wraps an object in a proxy that traps property gets and returns stubs. If * the stub is a function, the original value will be passed as the first diff --git a/dom/push/test/xpcshell/test_clear_origin_data.js b/dom/push/test/xpcshell/test_clear_origin_data.js index 475ef3cea9..28dd14f757 100644 --- a/dom/push/test/xpcshell/test_clear_origin_data.js +++ b/dom/push/test/xpcshell/test_clear_origin_data.js @@ -108,6 +108,7 @@ add_task(function* test_webapps_cleardata() { })); }, onUnregister(data) { + equal(data.code, 200, 'Expected manual unregister reason'); unregisterDone(); }, }); @@ -137,6 +138,5 @@ add_task(function* test_webapps_cleardata() { yield clearForPattern(testRecords, { inBrowser: true }); equal(testRecords.length, 0, 'Should remove all test records'); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister'); + yield unregisterPromise; }); diff --git a/dom/push/test/xpcshell/test_crypto.js b/dom/push/test/xpcshell/test_crypto.js new file mode 100644 index 0000000000..70be7df127 --- /dev/null +++ b/dom/push/test/xpcshell/test_crypto.js @@ -0,0 +1,245 @@ +'use strict'; + +const { + getCryptoParams, + PushCrypto, +} = Cu.import('resource://gre/modules/PushCrypto.jsm', {}); + +function run_test() { + run_next_test(); +} + +add_task(function* test_crypto_getCryptoParams() { + let testData = [ + // These headers should parse correctly. + { + desc: 'aesgcm with multiple keys', + headers: { + encoding: 'aesgcm', + crypto_key: 'keyid=p256dh;dh=Iy1Je2Kv11A,p256ecdsa=o2M8QfiEKuI', + encryption: 'keyid=p256dh;salt=upk1yFkp1xI', + }, + params: { + dh: 'Iy1Je2Kv11A', + salt: 'upk1yFkp1xI', + rs: 4096, + padSize: 2, + }, + }, { + desc: 'aesgcm with quoted key param', + headers: { + encoding: 'aesgcm', + crypto_key: 'dh="byfHbUffc-k"', + encryption: 'salt=C11AvAsp6Gc', + }, + params: { + dh: 'byfHbUffc-k', + salt: 'C11AvAsp6Gc', + rs: 4096, + padSize: 2, + }, + }, { + desc: 'aesgcm with Crypto-Key and rs = 24', + headers: { + encoding: 'aesgcm', + crypto_key: 'dh="ybuT4VDz-Bg"', + encryption: 'salt=H7U7wcIoIKs; rs=24', + }, + params: { + dh: 'ybuT4VDz-Bg', + salt: 'H7U7wcIoIKs', + rs: 24, + padSize: 2, + }, + }, { + desc: 'aesgcm128 with Encryption-Key and rs = 2', + headers: { + encoding: 'aesgcm128', + encryption_key: 'keyid=legacy; dh=LqrDQuVl9lY', + encryption: 'keyid=legacy; salt=YngI8B7YapM; rs=2', + }, + params: { + dh: 'LqrDQuVl9lY', + salt: 'YngI8B7YapM', + rs: 2, + padSize: 1, + }, + }, { + desc: 'aesgcm128 with Encryption-Key', + headers: { + encoding: 'aesgcm128', + encryption_key: 'keyid=v2; dh=VA6wmY1IpiE', + encryption: 'keyid=v2; salt=khtpyXhpDKM', + }, + params: { + dh: 'VA6wmY1IpiE', + salt: 'khtpyXhpDKM', + rs: 4096, + padSize: 1, + } + }, + + // These headers should be rejected. + { + desc: 'aesgcm128 with Crypto-Key', + headers: { + encoding: 'aesgcm128', + crypto_key: 'keyid=v2; dh=VA6wmY1IpiE', + encryption: 'keyid=v2; salt=F0Im7RtGgNY', + }, + params: null, + }, + { + desc: 'Invalid encoding', + headers: { + encoding: 'nonexistent', + }, + params: null, + }, { + desc: 'Invalid record size', + headers: { + encoding: 'aesgcm', + crypto_key: 'dh=pbmv1QkcEDY', + encryption: 'dh=Esao8aTBfIk;rs=bad', + }, + params: null, + }, { + desc: 'Insufficiently large record size', + headers: { + encoding: 'aesgcm', + crypto_key: 'dh=fK0EXaw5IU8', + encryption: 'salt=orbLLmlbJfM;rs=1', + }, + params: null, + }, { + desc: 'aesgcm with Encryption-Key', + headers: { + encoding: 'aesgcm', + encryption_key: 'dh=FplK5KkvUF0', + encryption: 'salt=p6YHhFF3BQY', + }, + params: null, + }]; + + for (let test of testData) { + let params = getCryptoParams(test.headers); + deepEqual(params, test.params, test.desc); + } +}); + +add_task(function* test_crypto_decodeMsg() { + let privateKey = { + crv: 'P-256', + d: '4h23G_KkXC9TvBSK2v0Q7ImpS2YAuRd8hQyN0rFAwBg', + ext: true, + key_ops: ['deriveBits'], + kty: 'EC', + x: 'sd85ZCbEG6dEkGMCmDyGBIt454Qy-Yo-1xhbaT2Jlk4', + y: 'vr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs', + }; + let publicKey = ChromeUtils.base64URLDecode('BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs', { + padding: "reject", + }); + + let expectedSuccesses = [{ + desc: 'padSize = 2, rs = 24, pad = 0', + result: 'Some message', + data: 'Oo34w2F9VVnTMFfKtdx48AZWQ9Li9M6DauWJVgXU', + senderPublicKey: 'BCHFVrflyxibGLlgztLwKelsRZp4gqX3tNfAKFaxAcBhpvYeN1yIUMrxaDKiLh4LNKPtj0BOXGdr-IQ-QP82Wjo', + salt: 'zCU18Rw3A5aB_Xi-vfixmA', + rs: 24, + authSecret: 'aTDc6JebzR6eScy2oLo4RQ', + padSize: 2, + }, { + desc: 'padSize = 2, rs = 8, pad = 16', + result: 'Yet another message', + data: 'uEC5B_tR-fuQ3delQcrzrDCp40W6ipMZjGZ78USDJ5sMj-6bAOVG3AK6JqFl9E6AoWiBYYvMZfwThVxmDnw6RHtVeLKFM5DWgl1EwkOohwH2EhiDD0gM3io-d79WKzOPZE9rDWUSv64JstImSfX_ADQfABrvbZkeaWxh53EG59QMOElFJqHue4dMURpsMXg', + senderPublicKey: 'BEaA4gzA3i0JDuirGhiLgymS4hfFX7TNTdEhSk_HBlLpkjgCpjPL5c-GL9uBGIfa_fhGNKKFhXz1k9Kyens2ZpQ', + salt: 'ZFhzj0S-n29g9P2p4-I7tA', + rs: 8, + authSecret: '6plwZnSpVUbF7APDXus3UQ', + padSize: 2, + }, { + desc: 'padSize = 1, rs = 4096, pad = 2', + result: 'aesgcm128 encrypted message', + data: 'ljBJ44NPzJFH9EuyT5xWMU4vpZ90MdAqaq1TC1kOLRoPNHtNFXeJ0GtuSaE', + senderPublicKey: 'BOmnfg02vNd6RZ7kXWWrCGFF92bI-rQ-bV0Pku3-KmlHwbGv4ejWqgasEdLGle5Rhmp6SKJunZw2l2HxKvrIjfI', + salt: 'btxxUtclbmgcc30b9rT3Bg', + rs: 4096, + padSize: 1, + }, { + desc: 'padSize = 2, rs = 3, pad = 0', + result: 'Small record size', + data: 'oY4e5eDatDVt2fpQylxbPJM-3vrfhDasfPc8Q1PWt4tPfMVbz_sDNL_cvr0DXXkdFzS1lxsJsj550USx4MMl01ihjImXCjrw9R5xFgFrCAqJD3GwXA1vzS4T5yvGVbUp3SndMDdT1OCcEofTn7VC6xZ-zP8rzSQfDCBBxmPU7OISzr8Z4HyzFCGJeBfqiZ7yUfNlKF1x5UaZ4X6iU_TXx5KlQy_toV1dXZ2eEAMHJUcSdArvB6zRpFdEIxdcHcJyo1BIYgAYTDdAIy__IJVCPY_b2CE5W_6ohlYKB7xDyH8giNuWWXAgBozUfScLUVjPC38yJTpAUi6w6pXgXUWffende5FreQpnMFL1L4G-38wsI_-ISIOzdO8QIrXHxmtc1S5xzYu8bMqSgCinvCEwdeGFCmighRjj8t1zRWo0D14rHbQLPR_b1P5SvEeJTtS9Nm3iibM', + senderPublicKey: 'BCg6ZIGuE2ZNm2ti6Arf4CDVD_8--aLXAGLYhpghwjl1xxVjTLLpb7zihuEOGGbyt8Qj0_fYHBP4ObxwJNl56bk', + salt: '5LIDBXbvkBvvb7ZdD-T4PQ', + rs: 3, + authSecret: 'g2rWVHUCpUxgcL9Tz7vyeQ', + padSize: 2, + }]; + for (let test of expectedSuccesses) { + let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, { + padding: "reject", + }) : null; + let data = ChromeUtils.base64URLDecode(test.data, { + padding: "reject", + }); + let result = yield PushCrypto.decodeMsg(data, + privateKey, publicKey, + test.senderPublicKey, test.salt, + test.rs, authSecret, test.padSize); + let decoder = new TextDecoder('utf-8'); + equal(decoder.decode(new Uint8Array(result)), test.result, test.desc); + } + + let expectedFailures = [{ + desc: 'padSize = 1, rs = 4096, auth secret, pad = 8', + data: 'h0FmyldY8aT5EQ6CJrbfRn_IdDvytoLeHb9_q5CjtdFRfgDRknxLmOzavLaVG4oOiS0r', + senderPublicKey: 'BCXHk7O8CE-9AOp6xx7g7c-NCaNpns1PyyHpdcmDaijLbT6IdGq0ezGatBwtFc34BBfscFxdk4Tjksa2Mx5rRCM', + salt: 'aGBpoKklLtrLcAUCcCr7JQ', + rs: 4096, + authSecret: 'Sxb6u0gJIhGEogyLawjmCw', + padSize: 1, + }, { + desc: 'Missing padding', + data: 'anvsHj7oBQTPMhv7XSJEsvyMS4-8EtbC7HgFZsKaTg', + senderPublicKey: 'BMSqfc3ohqw2DDgu3nsMESagYGWubswQPGxrW1bAbYKD18dIHQBUmD3ul_lu7MyQiT5gNdzn5JTXQvCcpf-oZE4', + salt: 'Czx2i18rar8XWOXAVDnUuw', + rs: 4096, + padSize: 1, + }, { + desc: 'padSize > rs', + data: 'Ct_h1g7O55e6GvuhmpjLsGnv8Rmwvxgw8iDESNKGxk_8E99iHKDzdV8wJPyHA-6b2E6kzuVa5UWiQ7s4Zms1xzJ4FKgoxvBObXkc_r_d4mnb-j245z3AcvRmcYGk5_HZ0ci26SfhAN3lCgxGzTHS4nuHBRkGwOb4Tj4SFyBRlLoTh2jyVK2jYugNjH9tTrGOBg7lP5lajLTQlxOi91-RYZSfFhsLX3LrAkXuRoN7G1CdiI7Y3_eTgbPIPabDcLCnGzmFBTvoJSaQF17huMl_UnWoCj2WovA4BwK_TvWSbdgElNnQ4CbArJ1h9OqhDOphVu5GUGr94iitXRQR-fqKPMad0ULLjKQWZOnjuIdV1RYEZ873r62Yyd31HoveJcSDb1T8l_QK2zVF8V4k0xmK9hGuC0rF5YJPYPHgl5__usknzxMBnRrfV5_MOL5uPZwUEFsu', + senderPublicKey: 'BAcMdWLJRGx-kPpeFtwqR3GE1LWzd1TYh2rg6CEFu53O-y3DNLkNe_BtGtKRR4f7ZqpBMVS6NgfE2NwNPm3Ndls', + salt: 'NQVTKhB0rpL7ZzKkotTGlA', + rs: 1, + authSecret: '6plwZnSpVUbF7APDXus3UQ', + padSize: 2, + }, { + desc: 'Encrypted with padSize = 1, decrypted with padSize = 2 and auth secret', + data: 'fwkuwTTChcLnrzsbDI78Y2EoQzfnbMI8Ax9Z27_rwX8', + senderPublicKey: 'BCHn-I-J3dfPRLJBlNZ3xFoAqaBLZ6qdhpaz9W7Q00JW1oD-hTxyEECn6KYJNK8AxKUyIDwn6Icx_PYWJiEYjQ0', + salt: 'c6JQl9eJ0VvwrUVCQDxY7Q', + rs: 4096, + authSecret: 'BhbpNTWyO5wVJmVKTV6XaA', + padSize: 2, + }, { + desc: 'Truncated input', + data: 'AlDjj6NvT5HGyrHbT8M5D6XBFSra6xrWS9B2ROaCIjwSu3RyZ1iyuv0', + rs: 25, + }]; + for (let test of expectedFailures) { + let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, { + padding: "reject", + }) : null; + let data = ChromeUtils.base64URLDecode(test.data, { + padding: "reject", + }); + yield rejects( + PushCrypto.decodeMsg(data, privateKey, publicKey, + test.senderPublicKey, test.salt, test.rs, + authSecret, test.padSize), + test.desc + ); + } +}); diff --git a/dom/push/test/xpcshell/test_drop_expired.js b/dom/push/test/xpcshell/test_drop_expired.js index 6f7414c218..d2a6ff06fd 100644 --- a/dom/push/test/xpcshell/test_drop_expired.js +++ b/dom/push/test/xpcshell/test_drop_expired.js @@ -122,8 +122,7 @@ add_task(function* setUp() { }, }); - yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for subscription change event on startup'); + yield subChangePromise; }); add_task(function* test_site_visited() { @@ -135,8 +134,7 @@ add_task(function* test_site_visited() { yield visitURI(quotaURI, Date.now()); PushService.observe(null, 'idle-daily', ''); - yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for subscription change event after visit'); + yield subChangePromise; }); add_task(function* test_perm_restored() { @@ -148,6 +146,5 @@ add_task(function* test_perm_restored() { Services.perms.add(permURI, 'desktop-notification', Ci.nsIPermissionManager.ALLOW_ACTION); - yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for subscription change event after permission'); + yield subChangePromise; }); diff --git a/dom/push/test/xpcshell/test_notification_ack.js b/dom/push/test/xpcshell/test_notification_ack.js index aa9a9d958f..b005922089 100644 --- a/dom/push/test/xpcshell/test_notification_ack.js +++ b/dom/push/test/xpcshell/test_notification_ack.js @@ -77,12 +77,13 @@ add_task(function* test_notification_ack() { onACK(request) { equal(request.messageType, 'ack', 'Should send acknowledgements'); let updates = request.updates; - ok(Array.isArray(updates), - 'Should send an array of acknowledged updates'); - equal(updates.length, 1, - 'Should send one acknowledged update per packet'); switch (++acks) { case 1: + deepEqual([{ + channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c', + version: 2, + code: 100, + }], updates, 'Wrong updates for acknowledgement 1'); this.serverSendMsg(JSON.stringify({ messageType: 'notification', updates: [{ @@ -98,14 +99,16 @@ add_task(function* test_notification_ack() { case 2: deepEqual([{ channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305', - version: 4 + version: 4, + code: 100, }], updates, 'Wrong updates for acknowledgement 2'); break; case 3: deepEqual([{ channelID: '5477bfda-22db-45d4-9614-fee369630260', - version: 6 + version: 6, + code: 100, }], updates, 'Wrong updates for acknowledgement 3'); ackDone(); break; @@ -118,8 +121,6 @@ add_task(function* test_notification_ack() { } }); - yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications'); - yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for multiple acknowledgements'); + yield notifyPromise; + yield ackPromise; }); diff --git a/dom/push/test/xpcshell/test_notification_data.js b/dom/push/test/xpcshell/test_notification_data.js index 74b678488a..0982b78cde 100644 --- a/dom/push/test/xpcshell/test_notification_data.js +++ b/dom/push/test/xpcshell/test_notification_data.js @@ -4,7 +4,6 @@ 'use strict'; const {PushDB, PushService, PushServiceWebSocket} = serviceExports; -const {base64UrlDecode} = Cu.import('resource://gre/modules/PushCrypto.jsm', {}); let db; let userAgentID = 'f5b47f8d-771f-4ea3-b999-91c135f8766d'; @@ -27,9 +26,13 @@ function putRecord(channelID, scope, publicKey, privateKey, authSecret) { originAttributes: '', quota: Infinity, systemRecord: true, - p256dhPublicKey: base64UrlDecode(publicKey), + p256dhPublicKey: ChromeUtils.base64URLDecode(publicKey, { + padding: "reject", + }), p256dhPrivateKey: privateKey, - authenticationSecret: base64UrlDecode(authSecret), + authenticationSecret: ChromeUtils.base64URLDecode(authSecret, { + padding: "reject", + }), }); } @@ -108,14 +111,13 @@ add_task(function* test_notification_ack_data_setup() { }, onACK(request) { if (ackDone) { - ackDone(request.updates); + ackDone(request); } } }); } }); - yield waitForPromise(setupDonePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications'); + yield setupDonePromise; }); add_task(function* test_notification_ack_data() { @@ -127,10 +129,12 @@ add_task(function* test_notification_ack_data() { headers: { encryption_key: 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"', encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"', + encoding: 'aesgcm128', }, data: 'NwrrOWPxLE8Sv5Rr0Kep7n0-r_j3rsYrUw_CXPo', version: 'v1', }, + ackCode: 100, receive: { scope: 'https://example.com/page/1', data: 'Some message' @@ -143,9 +147,11 @@ add_task(function* test_notification_ack_data() { headers: { encryption_key: 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"', encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"', + encoding: 'aesgcm128', }, data: 'Zt9dEdqgHlyAL_l83385aEtb98ZBilz5tgnGgmwEsl5AOCNgesUUJ4p9qUU', }, + ackCode: 100, receive: { scope: 'https://example.com/page/2', data: 'Some message' @@ -158,29 +164,32 @@ add_task(function* test_notification_ack_data() { headers: { encryption_key: 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"', encryption: 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24', + encoding: 'aesgcm128', }, data: 'LKru3ZzxBZuAxYtsaCfaj_fehkrIvqbVd1iSwnwAUgnL-cTeDD-83blxHXTq7r0z9ydTdMtC3UjAcWi8LMnfY-BFzi0qJAjGYIikDA', }, + ackCode: 100, receive: { scope: 'https://example.com/page/3', data: 'Some message' } }, - // This message uses the newer, authenticated form based on the crypto-key - // header field. No padding or record size changes. + // A message encoded with `aesgcm` (2 bytes of padding, authenticated). { channelID: 'subscription1', - version: 'v4', + version: 'v5', send: { headers: { - crypto_key: 'keyid=v4;dh="BHqG01j7rOfp12BEDzxWXxlCaU4cdOx2DZAwCt3QuzEsnXN9lCna9QmZCkVpXsx7sAlaEmtl_VfF1lHlFS7XWcA"', - encryption: 'keyid="v4";salt="X5-iy5rzhm4naNmMHdSYJw"', + crypto_key: 'keyid=v4;dh="BMh_vsnqu79ZZkMTYkxl4gWDLdPSGE72Lr4w2hksSFW398xCMJszjzdblAWXyhSwakRNEU_GopAm4UGzyMVR83w"', + encryption: 'keyid="v4";salt="C14Wb7rQTlXzrgcPHtaUzw"', + encoding: 'aesgcm', }, - data: '7YlxyNlZsNX4UNknHxzTqFrcrzz58W95uXBa0iY', + data: 'pus4kUaBWzraH34M-d_oN8e0LPpF_X6acx695AMXovDe', }, + ackCode: 100, receive: { scope: 'https://example.com/page/1', - data: 'Some message' + data: 'Another message' } }, // A message with 17 bytes of padding and rs of 24 @@ -189,11 +198,13 @@ add_task(function* test_notification_ack_data() { version: 'v5', send: { headers: { - crypto_key: 'keyid="v5"; dh="BJhyKIH5P30YUKn1bolj_LMnael1-KZT_aGXgD2CRspBfv9gcUhVAmpxToZrw7QQEKl9K83b3zcqNY6G_dFhEsI"', - encryption: 'keyid=v5;salt="bLmqCy550eK1Ao41tD7orA";rs=24', + crypto_key: 'keyid="v5"; dh="BOp-DpyR9eLY5Ci11_loIFqeHzWfc_0evJmq7N8NKzgp60UAMMM06XIi2VZp2_TSdw1omk7E19SyeCCwRp76E-U"', + encryption: 'keyid=v5;salt="TvjOou1TqJOQY_ZsOYV3Ww";rs=24', + encoding: 'aesgcm', }, - data: 'SQDlDg1ftLkM_ruZlmyB2bk9L78HYtkcbA-y4-uAxwL-G4KtOA-J-A_rJ007Vi6NUkQe9K4kSZeIBrIUpmGv', + data: 'rG9WYQ2ZwUgfj_tMlZ0vcIaNpBN05FW-9RUBZAM-UUZf0_9eGpuENBpUDAw3mFmd2XJpmvPvAtLVs54l3rGwg1o', }, + ackCode: 100, receive: { scope: 'https://example.com/page/2', data: 'Some message' @@ -205,34 +216,54 @@ add_task(function* test_notification_ack_data() { version: 'v6', send: { headers: { - crypto_key: 'dh="BEgnDmVw9Gcn1fWA5t53Jtpsgfewk_pzsjSc_PBPpPmROWGQA2v8ESrSsQgosNXx0o-uMMhi9tDAUeks3380kd8"', - encryption: 'salt=T9DM8bNxuMHRVTn4LzkJDQ', + crypto_key: 'dh="BEEjwWbF5jZKCgW0kmUWgG-wNcRvaa9_3zZElHAF8przHwd4cp5_kQsc-IMNZcVA0iUix31jxuMOytU-5DwWtyQ"', + encryption: 'salt=aAQcr2khAksgNspPiFEqiQ', + encoding: 'aesgcm', }, - data: '7KUCi0dBBJbWmsYTqEqhFrgTv4ZOo_BmQRQ_2kY', + data: 'pEYgefdI-7L46CYn5dR9TIy2AXGxe07zxclbhstY', }, + ackCode: 100, receive: { scope: 'https://example.com/page/3', data: 'Some message' } }, + // A malformed encrypted message. + { + channelID: 'subscription3', + version: 'v7', + send: { + headers: { + crypto_key: 'dh=AAAAAAAA', + encryption: 'salt=AAAAAAAA', + }, + data: 'AAAAAAAA', + }, + ackCode: 101, + receive: null, + }, ]; let sendAndReceive = testData => { - let messageReceived = promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => { + let messageReceived = testData.receive ? promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => { let notification = subject.QueryInterface(Ci.nsIPushMessage); equal(notification.text(), testData.receive.data, 'Check data for notification ' + testData.version); equal(data, testData.receive.scope, 'Check scope for notification ' + testData.version); return true; - }); + }) : Promise.resolve(); let ackReceived = new Promise(resolve => ackDone = resolve) .then(ackData => { - deepEqual([{ - channelID: testData.channelID, - version: testData.version - }], ackData, 'Check updates for acknowledgment ' + testData.version); + deepEqual({ + messageType: 'ack', + updates: [{ + channelID: testData.channelID, + version: testData.version, + code: testData.ackCode, + }], + }, ackData, 'Check updates for acknowledgment ' + testData.version); }); let msg = JSON.parse(JSON.stringify(testData.send)); @@ -244,10 +275,7 @@ add_task(function* test_notification_ack_data() { return Promise.all([messageReceived, ackReceived]); }; - yield waitForPromise( - allTestData.reduce((p, testData) => { - return p.then(_ => sendAndReceive(testData)); - }, Promise.resolve()), - DEFAULT_TIMEOUT, - 'Timed out waiting for message exchange to complete'); + yield allTestData.reduce((p, testData) => { + return p.then(_ => sendAndReceive(testData)); + }, Promise.resolve()); }); diff --git a/dom/push/test/xpcshell/test_notification_duplicate.js b/dom/push/test/xpcshell/test_notification_duplicate.js index 07b984341c..83178d55b0 100644 --- a/dom/push/test/xpcshell/test_notification_duplicate.js +++ b/dom/push/test/xpcshell/test_notification_duplicate.js @@ -73,10 +73,8 @@ add_task(function* test_notification_duplicate() { } }); - yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications'); - yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for stale acknowledgement'); + yield notifyPromise; + yield ackPromise; let staleRecord = yield db.getByKeyID( '8d2d9400-3597-4c5a-8a38-c546b0043bcc'); diff --git a/dom/push/test/xpcshell/test_notification_error.js b/dom/push/test/xpcshell/test_notification_error.js index d0cd12fbf9..4a82c41527 100644 --- a/dom/push/test/xpcshell/test_notification_error.js +++ b/dom/push/test/xpcshell/test_notification_error.js @@ -87,18 +87,13 @@ add_task(function* test_notification_error() { } }); - yield waitForPromise( - notifyPromise, - DEFAULT_TIMEOUT, - 'Timed out waiting for notifications' - ); + yield notifyPromise; ok(scopes.includes('https://example.com/a'), 'Missing scope for notification A'); ok(scopes.includes('https://example.com/c'), 'Missing scope for notification C'); - yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for acknowledgements'); + yield ackPromise; let aRecord = yield db.getByIdentifiers({scope: 'https://example.com/a', originAttributes: originAttributes }); diff --git a/dom/push/test/xpcshell/test_notification_http2.js b/dom/push/test/xpcshell/test_notification_http2.js index 4517cae2a2..eda17bf4b9 100644 --- a/dom/push/test/xpcshell/test_notification_http2.js +++ b/dom/push/test/xpcshell/test_notification_http2.js @@ -56,6 +56,8 @@ add_task(function* test_pushNotifications() { // length 16. // /pushNotifications/subscription3 will send a message with rs equal 24 and // padding length 16. + // /pushNotifications/subscription4 will send a message with no rs and padding + // length 256. let db = PushServiceHttp2.newPushDB(); do_register_cleanup(() => { @@ -121,6 +123,30 @@ add_task(function* test_pushNotifications() { { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), quota: Infinity, systemRecord: true, + }, { + subscriptionUri: serverURL + '/pushNotifications/subscription4', + pushEndpoint: serverURL + '/pushEndpoint4', + pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint4', + scope: 'https://example.com/page/4', + p256dhPublicKey: ChromeUtils.base64URLDecode('BEcvDzkWCrUtjU_wygL98sbQCQrW1lY9irtgGnlCc4B0JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU', { + padding: "reject", + }), + p256dhPrivateKey: { + crv: 'P-256', + d: 'fWi7tZaX0Pk6WnLrjQ3kiRq_g5XStL5pdH4pllNCqXw', + ext: true, + key_ops: ["deriveBits"], + kty: 'EC', + x: 'Ry8PORYKtS2NT_DKAv3yxtAJCtbWVj2Ku2AaeUJzgHQ', + y: 'JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU' + }, + authenticationSecret: ChromeUtils.base64URLDecode('cwDVC1iwAn8E37mkR3tMSg', { + padding: "reject", + }), + originAttributes: ChromeUtils.originAttributesToSuffix( + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }), + quota: Infinity, + systemRecord: true, }]; for (let record of records) { @@ -148,7 +174,14 @@ add_task(function* test_pushNotifications() { equal(message.text(), "Some message", "decoded message is incorrect"); return true; } - }) + }), + promiseObserverNotification(PushServiceComponent.pushTopic, function(subject, data) { + var message = subject.QueryInterface(Ci.nsIPushMessage); + if (message && (data == "https://example.com/page/4")){ + equal(message.text(), "Yet another message", "decoded message is incorrect"); + return true; + } + }), ]); PushService.init({ @@ -156,8 +189,7 @@ add_task(function* test_pushNotifications() { db }); - yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications'); + yield notifyPromise; }); add_task(function* test_complete() { diff --git a/dom/push/test/xpcshell/test_notification_incomplete.js b/dom/push/test/xpcshell/test_notification_incomplete.js index 9f2c8dc1a5..3b73d0d8d9 100644 --- a/dom/push/test/xpcshell/test_notification_incomplete.js +++ b/dom/push/test/xpcshell/test_notification_incomplete.js @@ -108,8 +108,7 @@ add_task(function* test_notification_incomplete() { } }); - yield waitForPromise(notificationPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for incomplete notifications'); + yield notificationPromise; let storeRecords = yield db.getAllKeyIDs(); storeRecords.sort(({pushEndpoint: a}, {pushEndpoint: b}) => diff --git a/dom/push/test/xpcshell/test_notification_version_string.js b/dom/push/test/xpcshell/test_notification_version_string.js index 196a7d9f86..fa02d6d006 100644 --- a/dom/push/test/xpcshell/test_notification_version_string.js +++ b/dom/push/test/xpcshell/test_notification_version_string.js @@ -57,15 +57,10 @@ add_task(function* test_notification_version_string() { } }); - let {subject: notification, data: scope} = yield waitForPromise( - notifyPromise, - DEFAULT_TIMEOUT, - 'Timed out waiting for string notification' - ); + let {subject: notification, data: scope} = yield notifyPromise; equal(notification, null, 'Unexpected data for Simple Push message'); - yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for string acknowledgement'); + yield ackPromise; let storeRecord = yield db.getByKeyID( '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b'); diff --git a/dom/push/test/xpcshell/test_permissions.js b/dom/push/test/xpcshell/test_permissions.js index 3f9137add4..24ddd80bd3 100644 --- a/dom/push/test/xpcshell/test_permissions.js +++ b/dom/push/test/xpcshell/test_permissions.js @@ -118,14 +118,15 @@ add_task(function* setUp() { equal(typeof resolve, 'function', 'Dropped unexpected channel ID ' + request.channelID); delete unregisterDefers[request.channelID]; + equal(request.code, 202, + 'Expected permission revoked unregister reason'); resolve(); }, onACK(request) {}, }); } }); - yield waitForPromise(handshakePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for handshake'); + yield handshakePromise; }); add_task(function* test_permissions_allow_added() { @@ -135,8 +136,7 @@ add_task(function* test_permissions_allow_added() { makePushPermission('https://example.info', 'ALLOW_ACTION'), 'added' ); - let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications after adding allow'); + let notifiedScopes = yield subChangePromise; deepEqual(notifiedScopes, [ 'https://example.info/page/2', @@ -159,8 +159,7 @@ add_task(function* test_permissions_allow_deleted() { 'deleted' ); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister after deleting allow'); + yield unregisterPromise; let record = yield db.getByKeyID('active-allow'); ok(record.isExpired(), @@ -179,8 +178,7 @@ add_task(function* test_permissions_deny_added() { makePushPermission('https://example.net', 'DENY_ACTION'), 'added' ); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications after adding deny'); + yield unregisterPromise; let isExpired = yield allExpired( 'active-deny-added-1', @@ -210,8 +208,7 @@ add_task(function* test_permissions_allow_changed() { 'changed' ); - let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications after changing to allow'); + let notifiedScopes = yield subChangePromise; deepEqual(notifiedScopes, [ 'https://example.net/eggs', @@ -237,8 +234,7 @@ add_task(function* test_permissions_deny_changed() { 'changed' ); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister after changing to deny'); + yield unregisterPromise; let record = yield db.getByKeyID('active-deny-changed'); ok(record.isExpired(), @@ -259,8 +255,7 @@ add_task(function* test_permissions_clear() { yield PushService._onPermissionChange(null, 'cleared'); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister requests after clearing permissions'); + yield unregisterPromise; records = yield db.getAllKeyIDs(); deepEqual(records.map(record => record.keyID).sort(), [ diff --git a/dom/push/test/xpcshell/test_quota_exceeded.js b/dom/push/test/xpcshell/test_quota_exceeded.js index 999cf1feca..3342f7ae58 100644 --- a/dom/push/test/xpcshell/test_quota_exceeded.js +++ b/dom/push/test/xpcshell/test_quota_exceeded.js @@ -126,6 +126,7 @@ add_task(function* test_expiration_origin_threshold() { }, onUnregister(request) { equal(request.channelID, 'eb33fc90-c883-4267-b5cb-613969e8e349', 'Unregistered wrong channel ID'); + equal(request.code, 201, 'Expected quota exceeded unregister reason'); unregisterDone(); }, // We expect to receive acks, but don't care about their @@ -135,11 +136,9 @@ add_task(function* test_expiration_origin_threshold() { }, }); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister request'); + yield unregisterPromise; - yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications'); + yield notifyPromise; let expiredRecord = yield db.getByKeyID('eb33fc90-c883-4267-b5cb-613969e8e349'); strictEqual(expiredRecord.quota, 0, 'Expired record not updated'); diff --git a/dom/push/test/xpcshell/test_quota_observer.js b/dom/push/test/xpcshell/test_quota_observer.js index f0e46b87ba..7f49dc2bf5 100644 --- a/dom/push/test/xpcshell/test_quota_observer.js +++ b/dom/push/test/xpcshell/test_quota_observer.js @@ -93,6 +93,7 @@ add_task(function* test_expiration_history_observer() { }, onUnregister(request) { equal(request.channelID, '379c0668-8323-44d2-a315-4ee83f1a9ee9', 'Dropped wrong channel ID'); + equal(request.code, 201, 'Expected quota exceeded unregister reason'); unregisterDone(); }, onACK(request) {}, @@ -100,10 +101,8 @@ add_task(function* test_expiration_history_observer() { } }); - yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for subscription change event on startup'); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister request'); + yield subChangePromise; + yield unregisterPromise; let expiredRecord = yield db.getByKeyID('379c0668-8323-44d2-a315-4ee83f1a9ee9'); strictEqual(expiredRecord.quota, 0, 'Expired record not updated'); @@ -138,8 +137,7 @@ add_task(function* test_expiration_history_observer() { Services.obs.notifyObservers(null, 'idle-daily', ''); // And we should receive notifications for both scopes. - yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, - 'Timed out waiting for subscription change events'); + yield subChangePromise; deepEqual(notifiedScopes.sort(), [ 'https://example.com/auctions', 'https://example.com/deals' diff --git a/dom/push/test/xpcshell/test_quota_with_notification.js b/dom/push/test/xpcshell/test_quota_with_notification.js index 3f49c9ec88..c62e554bec 100644 --- a/dom/push/test/xpcshell/test_quota_with_notification.js +++ b/dom/push/test/xpcshell/test_quota_with_notification.js @@ -105,11 +105,9 @@ add_task(function* test_expiration_origin_threshold() { }, }); - yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications'); + yield notifyPromise; - yield waitForPromise(updateQuotaPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for quota to be updated'); + yield updateQuotaPromise; let expiredRecord = yield db.getByKeyID('f56645a9-1f32-4655-92ad-ddc37f6d54fb'); notStrictEqual(expiredRecord.quota, 0, 'Expired record not updated'); diff --git a/dom/push/test/xpcshell/test_reconnect_retry.js b/dom/push/test/xpcshell/test_reconnect_retry.js index 87d2dd1516..9141ff47c0 100644 --- a/dom/push/test/xpcshell/test_reconnect_retry.js +++ b/dom/push/test/xpcshell/test_reconnect_retry.js @@ -68,7 +68,7 @@ add_task(function* test_reconnect_retry() { originAttributes: ChromeUtils.originAttributesToSuffix( { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), }); - notEqual(registration.endpoint, retryEndpoint, 'Wrong endpoint for new request') + notEqual(registration.endpoint, retryEndpoint, 'Wrong endpoint for new request'); equal(registers, 3, 'Wrong registration count'); }); diff --git a/dom/push/test/xpcshell/test_register_case.js b/dom/push/test/xpcshell/test_register_case.js index 343192259c..af915d8d88 100644 --- a/dom/push/test/xpcshell/test_register_case.js +++ b/dom/push/test/xpcshell/test_register_case.js @@ -43,15 +43,11 @@ add_task(function* test_register_case() { } }); - let newRecord = yield waitForPromise( - PushService.register({ - scope: 'https://example.net/case', - originAttributes: ChromeUtils.originAttributesToSuffix( - { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), - }), - DEFAULT_TIMEOUT, - 'Mixed-case register response timed out' - ); + let newRecord = yield PushService.register({ + scope: 'https://example.net/case', + originAttributes: ChromeUtils.originAttributesToSuffix( + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }), + }); equal(newRecord.endpoint, 'https://example.com/update/case', 'Wrong push endpoint in registration record'); diff --git a/dom/push/test/xpcshell/test_register_flush.js b/dom/push/test/xpcshell/test_register_flush.js index 8ff7fe8e2e..8c0db44941 100644 --- a/dom/push/test/xpcshell/test_register_flush.js +++ b/dom/push/test/xpcshell/test_register_flush.js @@ -80,12 +80,10 @@ add_task(function* test_register_flush() { equal(newRecord.endpoint, 'https://example.org/update/2', 'Wrong push endpoint in record'); - let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notification'); + let {data: scope} = yield notifyPromise; equal(scope, 'https://example.com/page/1', 'Wrong notification scope'); - yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for acknowledgements'); + yield ackPromise; let prevRecord = yield db.getByKeyID( '9bcc7efb-86c7-4457-93ea-e24e6eb59b74'); diff --git a/dom/push/test/xpcshell/test_register_invalid_json.js b/dom/push/test/xpcshell/test_register_invalid_json.js index aa3b5c055b..9bf6ca2159 100644 --- a/dom/push/test/xpcshell/test_register_invalid_json.js +++ b/dom/push/test/xpcshell/test_register_invalid_json.js @@ -54,7 +54,6 @@ add_task(function* test_register_invalid_json() { 'Expected error for invalid JSON response' ); - yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, - 'Reconnect after invalid JSON response timed out'); + yield helloPromise; equal(registers, 1, 'Wrong register count'); }); diff --git a/dom/push/test/xpcshell/test_register_no_id.js b/dom/push/test/xpcshell/test_register_no_id.js index a0688c20c1..3224528acf 100644 --- a/dom/push/test/xpcshell/test_register_no_id.js +++ b/dom/push/test/xpcshell/test_register_no_id.js @@ -58,7 +58,6 @@ add_task(function* test_register_no_id() { 'Expected error for incomplete register response' ); - yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, - 'Reconnect after incomplete register response timed out'); + yield helloPromise; equal(registers, 1, 'Wrong register count'); }); diff --git a/dom/push/test/xpcshell/test_register_request_queue.js b/dom/push/test/xpcshell/test_register_request_queue.js index 047846372b..b95917a2da 100644 --- a/dom/push/test/xpcshell/test_register_request_queue.js +++ b/dom/push/test/xpcshell/test_register_request_queue.js @@ -53,11 +53,10 @@ add_task(function* test_register_request_queue() { { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), }); - yield waitForPromise(Promise.all([ + yield Promise.all([ rejects(firstRegister, 'Should time out the first request'), rejects(secondRegister, 'Should time out the second request') - ]), DEFAULT_TIMEOUT, 'Queued requests did not time out'); + ]); - yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for reconnect'); + yield helloPromise; }); diff --git a/dom/push/test/xpcshell/test_register_rollback.js b/dom/push/test/xpcshell/test_register_rollback.js index 35809a8189..0e0baeaccb 100644 --- a/dom/push/test/xpcshell/test_register_rollback.js +++ b/dom/push/test/xpcshell/test_register_rollback.js @@ -59,6 +59,7 @@ add_task(function* test_register_rollback() { }, onUnregister(request) { equal(request.channelID, channelID, 'Unregister: wrong channel ID'); + equal(request.code, 200, 'Expected manual unregister reason'); this.serverSendMsg(JSON.stringify({ messageType: 'unregister', status: 200, @@ -81,8 +82,7 @@ add_task(function* test_register_rollback() { ); // Should send an out-of-band unregister request. - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Unregister request timed out'); + yield unregisterPromise; equal(handshakes, 1, 'Wrong handshake count'); equal(registers, 1, 'Wrong register count'); }); diff --git a/dom/push/test/xpcshell/test_register_timeout.js b/dom/push/test/xpcshell/test_register_timeout.js index 812077c447..fd5cd7688c 100644 --- a/dom/push/test/xpcshell/test_register_timeout.js +++ b/dom/push/test/xpcshell/test_register_timeout.js @@ -83,10 +83,6 @@ add_task(function* test_register_timeout() { let record = yield db.getByKeyID(channelID); ok(!record, 'Should not store records for timed-out responses'); - yield waitForPromise( - timeoutPromise, - DEFAULT_TIMEOUT, - 'Reconnect timed out' - ); + yield timeoutPromise; equal(registers, 1, 'Should not handle timed-out register requests'); }); diff --git a/dom/push/test/xpcshell/test_register_wrong_id.js b/dom/push/test/xpcshell/test_register_wrong_id.js index 5b51adc4da..fecab5e70a 100644 --- a/dom/push/test/xpcshell/test_register_wrong_id.js +++ b/dom/push/test/xpcshell/test_register_wrong_id.js @@ -64,7 +64,6 @@ add_task(function* test_register_wrong_id() { 'Expected error for mismatched register reply' ); - yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, - 'Reconnect after mismatched register reply timed out'); + yield helloPromise; equal(registers, 1, 'Wrong register count'); }); diff --git a/dom/push/test/xpcshell/test_register_wrong_type.js b/dom/push/test/xpcshell/test_register_wrong_type.js index 9ab06e849f..12f6d9687d 100644 --- a/dom/push/test/xpcshell/test_register_wrong_type.js +++ b/dom/push/test/xpcshell/test_register_wrong_type.js @@ -58,7 +58,6 @@ add_task(function* test_register_wrong_type() { 'Expected error for non-string channel ID' ); - yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, - 'Reconnect after sending non-string channel ID timed out'); + yield helloPromise; equal(registers, 1, 'Wrong register count'); }); diff --git a/dom/push/test/xpcshell/test_registration_success.js b/dom/push/test/xpcshell/test_registration_success.js index d12ee6119e..6f91261d33 100644 --- a/dom/push/test/xpcshell/test_registration_success.js +++ b/dom/push/test/xpcshell/test_registration_success.js @@ -64,11 +64,7 @@ add_task(function* test_registration_success() { } }); - yield waitForPromise( - handshakePromise, - DEFAULT_TIMEOUT, - 'Timed out waiting for handshake' - ); + yield handshakePromise; let registration = yield PushService.registration({ scope: 'https://example.net/a', diff --git a/dom/push/test/xpcshell/test_retry_ws.js b/dom/push/test/xpcshell/test_retry_ws.js index 9287bd2ebb..e81e54c7dd 100644 --- a/dom/push/test/xpcshell/test_retry_ws.js +++ b/dom/push/test/xpcshell/test_retry_ws.js @@ -11,7 +11,7 @@ function run_test() { do_get_profile(); setPrefs({ userAgentID: userAgentID, - pingInterval: 10000, + pingInterval: 2000, retryBaseInterval: 25, }); run_next_test(); @@ -30,11 +30,17 @@ add_task(function* test_ws_retry() { quota: Infinity, }); - let alarmDelays = []; - let setAlarm = PushService.setAlarm; - PushService.setAlarm = function(delay) { - alarmDelays.push(delay); - setAlarm.apply(this, arguments); + // Use a mock timer to avoid waiting for the backoff interval. + let reconnects = 0; + PushServiceWebSocket._backoffTimer = { + init(observer, delay, type) { + reconnects++; + ok(delay >= 5 && delay <= 2000, `Backoff delay ${ + delay} out of range for attempt ${reconnects}`); + observer.observe(this, "timer-callback", null); + }, + + cancel() {}, }; let handshakeDone; @@ -45,8 +51,7 @@ add_task(function* test_ws_retry() { makeWebSocket(uri) { return new MockWebSocket(uri, { onHello(request) { - if (alarmDelays.length == 10) { - PushService.setAlarm = setAlarm; + if (reconnects == 10) { this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, @@ -61,11 +66,5 @@ add_task(function* test_ws_retry() { }, }); - yield waitForPromise( - handshakePromise, - 45000, - 'Timed out waiting for successful handshake' - ); - deepEqual(alarmDelays, [25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 10000], - 'Wrong reconnect alarm delays'); + yield handshakePromise; }); diff --git a/dom/push/test/xpcshell/test_startup_error.js b/dom/push/test/xpcshell/test_startup_error.js new file mode 100644 index 0000000000..f042cade36 --- /dev/null +++ b/dom/push/test/xpcshell/test_startup_error.js @@ -0,0 +1,73 @@ +'use strict'; + +const {PushService, PushServiceWebSocket} = serviceExports; + +function run_test() { + setPrefs(); + do_get_profile(); + run_next_test(); +} + +add_task(function* test_startup_error() { + let db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + PushService.init({ + serverURI: 'wss://push.example.org/', + networkInfo: new MockDesktopNetworkInfo(), + db: makeStub(db, { + getAllExpired(prev) { + return Promise.reject('database corruption on startup'); + }, + }), + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(request) { + ok(false, 'Unexpected handshake'); + }, + onRegister(request) { + ok(false, 'Unexpected register request'); + }, + }); + }, + }); + + yield rejects( + PushService.register({ + scope: `https://example.net/1`, + originAttributes: ChromeUtils.originAttributesToSuffix( + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }), + }), + 'Should not register if startup failed' + ); + + PushService.uninit(); + + PushService.init({ + serverURI: 'wss://push.example.org/', + networkInfo: new MockDesktopNetworkInfo(), + db: makeStub(db, { + getAllUnexpired(prev) { + return Promise.reject('database corruption on connect'); + }, + }), + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(request) { + ok(false, 'Unexpected handshake'); + }, + onRegister(request) { + ok(false, 'Unexpected register request'); + }, + }); + }, + }); + yield rejects( + PushService.registration({ + scope: `https://example.net/1`, + originAttributes: ChromeUtils.originAttributesToSuffix( + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }), + }), + 'Should not return registration if connection failed' + ); +}); diff --git a/dom/push/test/xpcshell/test_unregister_error.js b/dom/push/test/xpcshell/test_unregister_error.js index c0fb29c39a..58be1cbde6 100644 --- a/dom/push/test/xpcshell/test_unregister_error.js +++ b/dom/push/test/xpcshell/test_unregister_error.js @@ -65,6 +65,5 @@ add_task(function* test_unregister_error() { ok(!result, 'Deleted push record exists'); // Make sure we send a request to the server. - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister'); + yield unregisterPromise; }); diff --git a/dom/push/test/xpcshell/test_unregister_invalid_json.js b/dom/push/test/xpcshell/test_unregister_invalid_json.js index 2ef141a619..f777c1f657 100644 --- a/dom/push/test/xpcshell/test_unregister_invalid_json.js +++ b/dom/push/test/xpcshell/test_unregister_invalid_json.js @@ -82,6 +82,5 @@ add_task(function* test_unregister_invalid_json() { ok(!record, 'Failed to delete unregistered record after receiving invalid JSON'); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister'); + yield unregisterPromise; }); diff --git a/dom/push/test/xpcshell/test_unregister_success.js b/dom/push/test/xpcshell/test_unregister_success.js index 886ca84e1a..00567742ea 100644 --- a/dom/push/test/xpcshell/test_unregister_success.js +++ b/dom/push/test/xpcshell/test_unregister_success.js @@ -42,6 +42,7 @@ add_task(function* test_unregister_success() { }, onUnregister(request) { equal(request.channelID, channelID, 'Should include the channel ID'); + equal(request.code, 200, 'Expected manual unregister reason'); this.serverSendMsg(JSON.stringify({ messageType: 'unregister', status: 200, @@ -60,6 +61,5 @@ add_task(function* test_unregister_success() { let record = yield db.getByKeyID(channelID); ok(!record, 'Unregister did not remove record'); - yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for unregister'); + yield unregisterPromise; }); diff --git a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js index f81644d9e5..84923aa787 100644 --- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js +++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js @@ -67,8 +67,7 @@ add_task(function* test1() { db }); - yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, - 'Timed out waiting for notifications'); + yield notifyPromise; let aRecord = yield db.getByKeyID(serverURL + '/subscriptionNoKey'); ok(aRecord, 'The record should still be there'); diff --git a/dom/push/test/xpcshell/xpcshell.ini b/dom/push/test/xpcshell/xpcshell.ini index 5338be3ff7..7b4125dacf 100644 --- a/dom/push/test/xpcshell/xpcshell.ini +++ b/dom/push/test/xpcshell/xpcshell.ini @@ -5,6 +5,7 @@ tail = skip-if = toolkit == 'android' [test_clear_origin_data.js] +[test_crypto.js] [test_drop_expired.js] [test_handler_service.js] support-files = PushServiceHandler.js PushServiceHandler.manifest @@ -47,6 +48,7 @@ run-sequentially = This will delete all existing push subscriptions. [test_retry_ws.js] [test_service_parent.js] [test_service_child.js] +[test_startup_error.js] #http2 test [test_resubscribe_4xxCode_http2.js] diff --git a/dom/tests/mochitest/fetch/fetch_test_framework.js b/dom/tests/mochitest/fetch/fetch_test_framework.js index 5e29b01959..06a29cbd81 100644 --- a/dom/tests/mochitest/fetch/fetch_test_framework.js +++ b/dom/tests/mochitest/fetch/fetch_test_framework.js @@ -7,8 +7,7 @@ function testScript(script) { function setupPrefs() { return new Promise(function(resolve, reject) { SpecialPowers.pushPrefEnv({ - "set": [["dom.requestcache.enabled", true], - ["dom.requestcontext.enabled", true], + "set": [["dom.requestcontext.enabled", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true]] diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index 988c3b3f1f..72044e6d4c 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -60,8 +60,6 @@ skip-if = (e10s && debug && os == 'win') skip-if = buildapp == 'b2g' || (e10s && debug && os == 'win') # Bug 1137683 [test_request.html] skip-if = (e10s && debug && os == 'win') -[test_request_cache.html] -skip-if = (e10s && debug && os == 'win') [test_request_context.html] skip-if = (e10s && debug && os == 'win') [test_request_sw_reroute.html] diff --git a/dom/tests/mochitest/fetch/sw_reroute.js b/dom/tests/mochitest/fetch/sw_reroute.js index 631ec2b6d1..11ad7b76e4 100644 --- a/dom/tests/mochitest/fetch/sw_reroute.js +++ b/dom/tests/mochitest/fetch/sw_reroute.js @@ -10,8 +10,7 @@ function testScript(script) { } SpecialPowers.pushPrefEnv({ - "set": [["dom.requestcache.enabled", true], - ["dom.serviceWorkers.enabled", true], + "set": [["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true]] }, function() { diff --git a/dom/tests/mochitest/fetch/test_request_cache.html b/dom/tests/mochitest/fetch/test_request_cache.html deleted file mode 100644 index 3f48a690cb..0000000000 --- a/dom/tests/mochitest/fetch/test_request_cache.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - Make sure that Request.cache is not exposed by default - - - - - - - - diff --git a/dom/webidl/Directory.webidl b/dom/webidl/Directory.webidl index 6504dd02e0..da18a53721 100644 --- a/dom/webidl/Directory.webidl +++ b/dom/webidl/Directory.webidl @@ -15,7 +15,7 @@ * http://w3c.github.io/filesystem-api/#idl-def-Directory * https://microsoftedge.github.io/directory-upload/proposal.html#directory-interface */ -[Exposed=Window] +[Exposed=(Window,Worker)] interface Directory { /* * The leaf name of the directory. @@ -39,7 +39,7 @@ interface Directory { * @return If succeeds, the promise is resolved with the new created * File object. Otherwise, rejected with a DOM error. */ - [Pref="device.storage.enabled", NewObject] + [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject] Promise createFile(DOMString path, optional CreateFileOptions options); /* @@ -51,7 +51,7 @@ interface Directory { * @return If succeeds, the promise is resolved with the new created * Directory object. Otherwise, rejected with a DOM error. */ - [Pref="device.storage.enabled", NewObject] + [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject] Promise createDirectory(DOMString path); /* @@ -62,7 +62,7 @@ interface Directory { * with a File or Directory object, depending on the entry's type. Otherwise, * rejected with a DOM error. */ - [Pref="device.storage.enabled", NewObject] + [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject] Promise<(File or Directory)> get(DOMString path); /* @@ -76,7 +76,7 @@ interface Directory { * exist, the promise is resolved with boolean false. If the target did exist * and was successfully deleted, the promise is resolved with boolean true. */ - [Pref="device.storage.enabled", NewObject] + [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject] Promise remove((DOMString or File or Directory) path); /* @@ -90,11 +90,11 @@ interface Directory { * resolved with boolean false. If the target did exist and was successfully * deleted, the promise is resolved with boolean true. */ - [Pref="device.storage.enabled", NewObject] + [Func="mozilla::dom::Directory::DeviceStorageEnabled", NewObject] Promise removeDeep((DOMString or File or Directory) path); }; -[Exposed=Window] +[Exposed=(Window,Worker)] partial interface Directory { // Already defined in the main interface declaration: //readonly attribute DOMString name; diff --git a/dom/webidl/PushManager.webidl b/dom/webidl/PushManager.webidl index 2a28fb6fc4..7e3404db74 100644 --- a/dom/webidl/PushManager.webidl +++ b/dom/webidl/PushManager.webidl @@ -7,30 +7,23 @@ * https://w3c.github.io/push-api/ */ -// Please see comments in dom/push/PushManager.h for the split between -// PushManagerImpl and PushManager. +// The main thread JS implementation. Please see comments in +// dom/push/PushManager.h for the split between PushManagerImpl and PushManager. [JSImplementation="@mozilla.org/push/PushManager;1", - NoInterfaceObject] + ChromeOnly, Constructor(DOMString scope)] interface PushManagerImpl { - Promise subscribe(); - Promise getSubscription(); - Promise permissionState(); - - // We need a setter in the bindings so that the C++ can use it, - // but we don't want it exposed to client JS. WebPushMethodHider - // always returns false. - [Func="ServiceWorkerRegistration::WebPushMethodHider"] void setScope(DOMString scope); + Promise subscribe(); + Promise getSubscription(); + Promise permissionState(); }; -[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"] +[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled", + ChromeConstructor(DOMString scope)] interface PushManager { - [ChromeOnly, Throws, Exposed=Window] - void setPushManagerImpl(PushManagerImpl store); - [Throws, UseCounter] - Promise subscribe(); + Promise subscribe(); [Throws] - Promise getSubscription(); + Promise getSubscription(); [Throws] Promise permissionState(); }; diff --git a/dom/webidl/PushSubscription.webidl b/dom/webidl/PushSubscription.webidl index e8536a58f1..3aa8460cb8 100644 --- a/dom/webidl/PushSubscription.webidl +++ b/dom/webidl/PushSubscription.webidl @@ -38,9 +38,6 @@ interface PushSubscription Promise unsubscribe(); // Implements the custom serializer specified in Push API, section 9. + [Throws] PushSubscriptionJSON toJSON(); - - // Used to set the principal from the JS implemented PushManager. - [Exposed=Window,ChromeOnly] - void setPrincipal(Principal principal); }; diff --git a/dom/webidl/Request.webidl b/dom/webidl/Request.webidl index a7c05bc55c..02ebece9ba 100644 --- a/dom/webidl/Request.webidl +++ b/dom/webidl/Request.webidl @@ -23,7 +23,6 @@ interface Request { readonly attribute ReferrerPolicy referrerPolicy; readonly attribute RequestMode mode; readonly attribute RequestCredentials credentials; - [Func="mozilla::dom::Request::RequestCacheEnabled"] readonly attribute RequestCache cache; readonly attribute RequestRedirect redirect; diff --git a/dom/webidl/ThreadSafeChromeUtils.webidl b/dom/webidl/ThreadSafeChromeUtils.webidl index e34fe2f406..8d0f21734c 100644 --- a/dom/webidl/ThreadSafeChromeUtils.webidl +++ b/dom/webidl/ThreadSafeChromeUtils.webidl @@ -55,6 +55,28 @@ interface ThreadSafeChromeUtils { */ [Throws, NewObject] static any nondeterministicGetWeakSetKeys(any aSet); + + /** + * Converts a buffer to a Base64 URL-encoded string per RFC 4648. + * + * @param source The buffer to encode. + * @param options Additional encoding options. + * @returns The encoded string. + */ + [Throws] + static ByteString base64URLEncode(BufferSource source, + Base64URLEncodeOptions options); + + /** + * Decodes a Base64 URL-encoded string per RFC 4648. + * + * @param string The string to decode. + * @param options Additional decoding options. + * @returns The decoded buffer. + */ + [Throws, NewObject] + static ArrayBuffer base64URLDecode(ByteString string, + Base64URLDecodeOptions options); }; /** @@ -88,3 +110,31 @@ dictionary HeapSnapshotBoundaries { object debugger; boolean runtime; }; + +dictionary Base64URLEncodeOptions { + /** Specifies whether the output should be padded with "=" characters. */ + required boolean pad; +}; + +enum Base64URLDecodePadding { + /** + * Fails decoding if the input is unpadded. RFC 4648, section 3.2 requires + * padding, unless the referring specification prohibits it. + */ + "require", + + /** Tolerates padded and unpadded input. */ + "ignore", + + /** + * Fails decoding if the input is padded. This follows the strict base64url + * variant used in JWS (RFC 7515, Appendix C) and HTTP Encrypted + * Content-Encoding (draft-ietf-httpbis-encryption-encoding-01). + */ + "reject" +}; + +dictionary Base64URLDecodeOptions { + /** Specifies the padding mode for decoding the input. */ + required Base64URLDecodePadding padding; +}; diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index bdcffd7d53..72560413bb 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -2736,7 +2736,8 @@ ServiceWorkerManager::ReportToAllClients(const nsCString& aScope, uri, aLine, aLineNumber, - aColumnNumber); + aColumnNumber, + nsContentUtils::eOMIT_LOCATION); } // Report to any documents that have called .register() for this scope. They @@ -2768,7 +2769,8 @@ ServiceWorkerManager::ReportToAllClients(const nsCString& aScope, uri, aLine, aLineNumber, - aColumnNumber); + aColumnNumber, + nsContentUtils::eOMIT_LOCATION); } if (regList->IsEmpty()) { @@ -2839,7 +2841,8 @@ ServiceWorkerManager::ReportToAllClients(const nsCString& aScope, uri, aLine, aLineNumber, - aColumnNumber); + aColumnNumber, + nsContentUtils::eOMIT_LOCATION); return; } } diff --git a/dom/workers/ServiceWorkerRegistration.cpp b/dom/workers/ServiceWorkerRegistration.cpp index 61b8eb1d58..02f9c5c835 100644 --- a/dom/workers/ServiceWorkerRegistration.cpp +++ b/dom/workers/ServiceWorkerRegistration.cpp @@ -753,7 +753,8 @@ ServiceWorkerRegistrationMainThread::GetNotifications(const GetNotificationOptio } already_AddRefed -ServiceWorkerRegistrationMainThread::GetPushManager(ErrorResult& aRv) +ServiceWorkerRegistrationMainThread::GetPushManager(JSContext* aCx, + ErrorResult& aRv) { AssertIsOnMainThread(); @@ -769,32 +770,17 @@ ServiceWorkerRegistrationMainThread::GetPushManager(ErrorResult& aRv) return nullptr; } - // TODO: bug 1148117. This will fail when swr is exposed on workers - JS::Rooted jsImplObj(nsContentUtils::RootingCxForThread()); - ConstructJSImplementation("@mozilla.org/push/PushManager;1", - globalObject, &jsImplObj, aRv); + GlobalObject global(aCx, globalObject->GetGlobalJSObject()); + mPushManager = PushManager::Constructor(global, mScope, aRv); if (aRv.Failed()) { return nullptr; } - mPushManager = new PushManager(globalObject, mScope); - - RefPtr impl = new PushManagerImpl(jsImplObj, globalObject); - impl->SetScope(mScope, aRv); - if (aRv.Failed()) { - mPushManager = nullptr; - return nullptr; - } - mPushManager->SetPushManagerImpl(*impl, aRv); - if (aRv.Failed()) { - mPushManager = nullptr; - return nullptr; - } } RefPtr ret = mPushManager; return ret.forget(); - #endif /* ! MOZ_SIMPLEPUSH */ +#endif /* ! MOZ_SIMPLEPUSH */ } //////////////////////////////////////////////////// @@ -1210,7 +1196,7 @@ ServiceWorkerRegistrationWorkerThread::GetNotifications(const GetNotificationOpt return Notification::WorkerGet(mWorkerPrivate, aOptions, mScope, aRv); } -already_AddRefed +already_AddRefed ServiceWorkerRegistrationWorkerThread::GetPushManager(ErrorResult& aRv) { #ifdef MOZ_SIMPLEPUSH @@ -1218,13 +1204,13 @@ ServiceWorkerRegistrationWorkerThread::GetPushManager(ErrorResult& aRv) #else if (!mPushManager) { - mPushManager = new WorkerPushManager(mScope); + mPushManager = new PushManager(mScope); } - RefPtr ret = mPushManager; + RefPtr ret = mPushManager; return ret.forget(); - #endif /* ! MOZ_SIMPLEPUSH */ +#endif /* ! MOZ_SIMPLEPUSH */ } } // dom namespace diff --git a/dom/workers/ServiceWorkerRegistration.h b/dom/workers/ServiceWorkerRegistration.h index c509574190..888a3d87f0 100644 --- a/dom/workers/ServiceWorkerRegistration.h +++ b/dom/workers/ServiceWorkerRegistration.h @@ -22,7 +22,6 @@ namespace dom { class Promise; class PushManager; -class WorkerPushManager; class WorkerListener; namespace workers { @@ -36,21 +35,6 @@ ServiceWorkerRegistrationVisible(JSContext* aCx, JSObject* aObj); bool ServiceWorkerNotificationAPIVisible(JSContext* aCx, JSObject* aObj); -// This class exists solely so that we can satisfy some WebIDL Func= attribute -// constraints. Func= converts the function name to a header file to include, in -// this case "ServiceWorkerRegistration.h". -class ServiceWorkerRegistration final -{ -public: - // Something that we can feed into the Func webidl property to ensure that - // SetScope is never exposed to the user. - static bool - WebPushMethodHider(JSContext* unusedContext, JSObject* unusedObject) { - return false; - } - -}; - // Used by ServiceWorkerManager to notify ServiceWorkerRegistrations of // updatefound event and invalidating ServiceWorker instances. class ServiceWorkerRegistrationListener @@ -138,7 +122,7 @@ public: GetActive() override; already_AddRefed - GetPushManager(ErrorResult& aRv); + GetPushManager(JSContext* aCx, ErrorResult& aRv); // DOMEventTargethelper void DisconnectFromOwner() override @@ -241,7 +225,7 @@ public: bool Notify(workers::Status aStatus) override; - already_AddRefed + already_AddRefed GetPushManager(ErrorResult& aRv); private: @@ -263,7 +247,7 @@ private: RefPtr mListener; #ifndef MOZ_SIMPLEPUSH - RefPtr mPushManager; + RefPtr mPushManager; #endif }; diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h index 3d2e662ac8..6049b39f28 100644 --- a/dom/workers/WorkerPrefs.h +++ b/dom/workers/WorkerPrefs.h @@ -33,7 +33,6 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.enabled", ServiceWorkersEnabled, SERVICEW WORKER_SIMPLE_PREF("dom.serviceWorkers.testing.enabled", ServiceWorkersTestingEnabled, SERVICEWORKERS_TESTING_ENABLED) WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, OPEN_WINDOW_ENABLED) WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED) -WORKER_SIMPLE_PREF("dom.requestcache.enabled", RequestCacheEnabled, REQUESTCACHE_ENABLED) WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED) WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED) WORKER_PREF("dom.workers.latestJSVersion", JSVersionChanged) diff --git a/dom/workers/test/gtest/TestReadWrite.cpp b/dom/workers/test/gtest/TestReadWrite.cpp index d8a8a8376e..196c502973 100644 --- a/dom/workers/test/gtest/TestReadWrite.cpp +++ b/dom/workers/test/gtest/TestReadWrite.cpp @@ -113,7 +113,7 @@ TEST(ServiceWorkerRegistrar, TestEmptyFile) TEST(ServiceWorkerRegistrar, TestRightVersionFile) { - ASSERT_TRUE(CreateFile(nsAutoCString(SERVICEWORKERREGISTRAR_VERSION "\n"))) << "CreateFile should not fail"; + ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING(SERVICEWORKERREGISTRAR_VERSION "\n"))) << "CreateFile should not fail"; RefPtr swr = new ServiceWorkerRegistrarTest; @@ -126,7 +126,7 @@ TEST(ServiceWorkerRegistrar, TestRightVersionFile) TEST(ServiceWorkerRegistrar, TestWrongVersionFile) { - ASSERT_TRUE(CreateFile(nsAutoCString(SERVICEWORKERREGISTRAR_VERSION "bla\n"))) << "CreateFile should not fail"; + ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING(SERVICEWORKERREGISTRAR_VERSION "bla\n"))) << "CreateFile should not fail"; RefPtr swr = new ServiceWorkerRegistrarTest; @@ -188,7 +188,7 @@ TEST(ServiceWorkerRegistrar, TestReadData) TEST(ServiceWorkerRegistrar, TestDeleteData) { - ASSERT_TRUE(CreateFile(nsAutoCString("Foobar"))) << "CreateFile should not fail"; + ASSERT_TRUE(CreateFile(NS_LITERAL_CSTRING("Foobar"))) << "CreateFile should not fail"; RefPtr swr = new ServiceWorkerRegistrarTest; diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js index 12ffa42e0b..c3bdda40cb 100644 --- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js +++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js @@ -94,6 +94,8 @@ var interfaceNamesInGlobalScope = { name: "DataStore", b2g: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "DataStoreCursor", b2g: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + "Directory", // IMPORTANT: Do not change this list without review from a DOM peer! "DOMCursor", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js index 0adc1ccccf..a577b6ef7f 100644 --- a/dom/workers/test/test_worker_interfaces.js +++ b/dom/workers/test/test_worker_interfaces.js @@ -92,6 +92,8 @@ var interfaceNamesInGlobalScope = { name: "DataStore", b2g: true }, // IMPORTANT: Do not change this list without review from a DOM peer! { name: "DataStoreCursor", b2g: true }, +// IMPORTANT: Do not change this list without review from a DOM peer! + "Directory", // IMPORTANT: Do not change this list without review from a DOM peer! "DOMCursor", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/xbl/nsXBLEventHandler.cpp b/dom/xbl/nsXBLEventHandler.cpp index 064d672e4b..6a873cf3ba 100644 --- a/dom/xbl/nsXBLEventHandler.cpp +++ b/dom/xbl/nsXBLEventHandler.cpp @@ -97,7 +97,7 @@ nsXBLKeyEventHandler::ExecuteMatchedHandlers( for (uint32_t i = 0; i < mProtoHandlers.Length(); ++i) { nsXBLPrototypeHandler* handler = mProtoHandlers[i]; bool hasAllowUntrustedAttr = handler->HasAllowUntrustedAttr(); - if ((event->mFlags.mIsTrusted || + if ((event->IsTrusted() || (hasAllowUntrustedAttr && handler->AllowUntrustedEvents()) || (!hasAllowUntrustedAttr && !mIsBoundToChrome && !mUsingContentXBLScope)) && handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) { @@ -140,18 +140,21 @@ nsXBLKeyEventHandler::HandleEvent(nsIDOMEvent* aEvent) if (!key) return NS_OK; - AutoTArray accessKeys; - nsContentUtils::GetAccelKeyCandidates(key, accessKeys); + WidgetKeyboardEvent* nativeKeyboardEvent = + aEvent->WidgetEventPtr()->AsKeyboardEvent(); + MOZ_ASSERT(nativeKeyboardEvent); + AutoShortcutKeyCandidateArray shortcutKeys; + nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys); - if (accessKeys.IsEmpty()) { + if (shortcutKeys.IsEmpty()) { ExecuteMatchedHandlers(key, 0, IgnoreModifierState()); return NS_OK; } - for (uint32_t i = 0; i < accessKeys.Length(); ++i) { + for (uint32_t i = 0; i < shortcutKeys.Length(); ++i) { IgnoreModifierState ignoreModifierState; - ignoreModifierState.mShift = accessKeys[i].mIgnoreShift; - if (ExecuteMatchedHandlers(key, accessKeys[i].mCharCode, + ignoreModifierState.mShift = shortcutKeys[i].mIgnoreShift; + if (ExecuteMatchedHandlers(key, shortcutKeys[i].mCharCode, ignoreModifierState)) { return NS_OK; } diff --git a/dom/xbl/nsXBLPrototypeHandler.h b/dom/xbl/nsXBLPrototypeHandler.h index 1e550fd427..6898b73ede 100644 --- a/dom/xbl/nsXBLPrototypeHandler.h +++ b/dom/xbl/nsXBLPrototypeHandler.h @@ -90,28 +90,23 @@ public: ~nsXBLPrototypeHandler(); + bool EventTypeEquals(nsIAtom* aEventType) const + { + return mEventName == aEventType; + } + // if aCharCode is not zero, it is used instead of the charCode of aKeyEvent. bool KeyEventMatched(nsIDOMKeyEvent* aKeyEvent, uint32_t aCharCode, const IgnoreModifierState& aIgnoreModifierState); - inline bool KeyEventMatched(nsIAtom* aEventType, - nsIDOMKeyEvent* aEvent, - uint32_t aCharCode, - const IgnoreModifierState& aIgnoreModifierState) - { - if (aEventType != mEventName) - return false; - - return KeyEventMatched(aEvent, aCharCode, aIgnoreModifierState); - } bool MouseEventMatched(nsIDOMMouseEvent* aMouseEvent); inline bool MouseEventMatched(nsIAtom* aEventType, nsIDOMMouseEvent* aEvent) { - if (aEventType != mEventName) + if (!EventTypeEquals(aEventType)) { return false; - + } return MouseEventMatched(aEvent); } diff --git a/dom/xbl/nsXBLService.cpp b/dom/xbl/nsXBLService.cpp index f4623d0eeb..7382bc0907 100644 --- a/dom/xbl/nsXBLService.cpp +++ b/dom/xbl/nsXBLService.cpp @@ -556,22 +556,7 @@ nsXBLService::AttachGlobalKeyHandler(EventTarget* aTarget) RefPtr handler = NS_NewXBLWindowKeyHandler(elt, piTarget); - // listen to these events - manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"), - TrustedEventsAtSystemGroupBubble()); - manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"), - TrustedEventsAtSystemGroupBubble()); - manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"), - TrustedEventsAtSystemGroupBubble()); - - // The capturing listener is only used for XUL keysets to properly handle - // shortcut keys in a multi-process environment. - manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keydown"), - TrustedEventsAtSystemGroupCapture()); - manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keyup"), - TrustedEventsAtSystemGroupCapture()); - manager->AddEventListenerByType(handler, NS_LITERAL_STRING("keypress"), - TrustedEventsAtSystemGroupCapture()); + handler->InstallKeyboardEventListenersTo(manager); if (contentNode) return contentNode->SetProperty(nsGkAtoms::listener, @@ -611,19 +596,8 @@ nsXBLService::DetachGlobalKeyHandler(EventTarget* aTarget) if (!handler) return NS_ERROR_FAILURE; - manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"), - TrustedEventsAtSystemGroupBubble()); - manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"), - TrustedEventsAtSystemGroupBubble()); - manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"), - TrustedEventsAtSystemGroupBubble()); - - manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keydown"), - TrustedEventsAtSystemGroupCapture()); - manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keyup"), - TrustedEventsAtSystemGroupCapture()); - manager->RemoveEventListenerByType(handler, NS_LITERAL_STRING("keypress"), - TrustedEventsAtSystemGroupCapture()); + static_cast(handler)-> + RemoveKeyboardEventListenersFrom(manager); contentNode->DeleteProperty(nsGkAtoms::listener); diff --git a/dom/xbl/nsXBLWindowKeyHandler.cpp b/dom/xbl/nsXBLWindowKeyHandler.cpp index 388c3d20fd..4262dc7f8c 100644 --- a/dom/xbl/nsXBLWindowKeyHandler.cpp +++ b/dom/xbl/nsXBLWindowKeyHandler.cpp @@ -23,6 +23,7 @@ #include "nsPIDOMWindow.h" #include "nsIDocShell.h" #include "nsIPresShell.h" +#include "mozilla/EventListenerManager.h" #include "mozilla/EventStateManager.h" #include "nsISelectionController.h" #include "mozilla/Preferences.h" @@ -297,6 +298,82 @@ nsXBLWindowKeyHandler::WalkHandlers(nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventTy return NS_OK; } +void +nsXBLWindowKeyHandler::InstallKeyboardEventListenersTo( + EventListenerManager* aEventListenerManager) +{ + // For marking each keyboard event as if it's reserved by chrome, + // nsXBLWindowKeyHandlers need to listen each keyboard events before + // web contents. + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keydown"), + TrustedEventsAtCapture()); + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keyup"), + TrustedEventsAtCapture()); + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keypress"), + TrustedEventsAtCapture()); + + // For reducing the IPC cost, preventing to dispatch reserved keyboard + // events into the content process. + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keydown"), + TrustedEventsAtSystemGroupCapture()); + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keyup"), + TrustedEventsAtSystemGroupCapture()); + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keypress"), + TrustedEventsAtSystemGroupCapture()); + + // Handle keyboard events in bubbling phase of the system event group. + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keydown"), + TrustedEventsAtSystemGroupBubble()); + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keyup"), + TrustedEventsAtSystemGroupBubble()); + aEventListenerManager->AddEventListenerByType( + this, NS_LITERAL_STRING("keypress"), + TrustedEventsAtSystemGroupBubble()); +} + +void +nsXBLWindowKeyHandler::RemoveKeyboardEventListenersFrom( + EventListenerManager* aEventListenerManager) +{ + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keydown"), + TrustedEventsAtCapture()); + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keyup"), + TrustedEventsAtCapture()); + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keypress"), + TrustedEventsAtCapture()); + + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keydown"), + TrustedEventsAtSystemGroupCapture()); + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keyup"), + TrustedEventsAtSystemGroupCapture()); + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keypress"), + TrustedEventsAtSystemGroupCapture()); + + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keydown"), + TrustedEventsAtSystemGroupBubble()); + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keyup"), + TrustedEventsAtSystemGroupBubble()); + aEventListenerManager->RemoveEventListenerByType( + this, NS_LITERAL_STRING("keypress"), + TrustedEventsAtSystemGroupBubble()); +} + NS_IMETHODIMP nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent) { @@ -306,7 +383,11 @@ nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent) uint16_t eventPhase; aEvent->GetEventPhase(&eventPhase); if (eventPhase == nsIDOMEvent::CAPTURING_PHASE) { - HandleEventOnCapture(keyEvent); + if (aEvent->WidgetEventPtr()->mFlags.mInSystemGroup) { + HandleEventOnCaptureInSystemEventGroup(keyEvent); + } else { + HandleEventOnCaptureInDefaultEventGroup(keyEvent); + } return NS_OK; } @@ -319,12 +400,41 @@ nsXBLWindowKeyHandler::HandleEvent(nsIDOMEvent* aEvent) } void -nsXBLWindowKeyHandler::HandleEventOnCapture(nsIDOMKeyEvent* aEvent) +nsXBLWindowKeyHandler::HandleEventOnCaptureInDefaultEventGroup( + nsIDOMKeyEvent* aEvent) +{ + WidgetKeyboardEvent* widgetKeyboardEvent = + aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); + + if (widgetKeyboardEvent->mFlags.mOnlySystemGroupDispatchInContent) { + MOZ_RELEASE_ASSERT( + widgetKeyboardEvent->mFlags.mNoCrossProcessBoundaryForwarding); + return; + } + + bool isReserved = false; + if (HasHandlerForEvent(aEvent, &isReserved) && isReserved) { + // For reserved commands (such as Open New Tab), we don't to wait for + // the content to answer (so mWantReplyFromContentProcess remains false), + // neither to give a chance for content to override its behavior. + widgetKeyboardEvent->StopCrossProcessForwarding(); + // If the key combination is reserved by chrome, we shouldn't expose the + // keyboard event to web contents because such keyboard events shouldn't be + // cancelable. So, it's not good behavior to fire keyboard events but + // to ignore the defaultPrevented attribute value in chrome. + widgetKeyboardEvent->mFlags.mOnlySystemGroupDispatchInContent = true; + } +} + +void +nsXBLWindowKeyHandler::HandleEventOnCaptureInSystemEventGroup( + nsIDOMKeyEvent* aEvent) { WidgetKeyboardEvent* widgetEvent = aEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); - if (widgetEvent->mFlags.mNoCrossProcessBoundaryForwarding) { + if (widgetEvent->mFlags.mNoCrossProcessBoundaryForwarding || + widgetEvent->mFlags.mOnlySystemGroupDispatchInContent) { return; } @@ -334,44 +444,19 @@ nsXBLWindowKeyHandler::HandleEventOnCapture(nsIDOMKeyEvent* aEvent) return; } - bool aReservedForChrome = false; - if (!HasHandlerForEvent(aEvent, &aReservedForChrome)) { + if (!HasHandlerForEvent(aEvent)) { return; } - if (aReservedForChrome) { - // For reserved commands (such as Open New Tab), we don't to wait for - // the content to answer (so mWantReplyFromContentProcess remains false), - // neither to give a chance for content to override its behavior. - widgetEvent->mFlags.mNoCrossProcessBoundaryForwarding = true; - } else { - // Inform the child process that this is a event that we want a reply - // from. - widgetEvent->mFlags.mWantReplyFromContentProcess = true; - - // If this event hadn't been marked as mNoCrossProcessBoundaryForwarding - // yet, it means it wasn't processed by content. We'll not call any - // of the handlers at this moment, and will wait for the event to be - // redispatched with mNoCrossProcessBoundaryForwarding = 1 to process it. - aEvent->AsEvent()->StopPropagation(); - } -} - -// -// EventMatched -// -// See if the given handler cares about this particular key event -// -bool -nsXBLWindowKeyHandler::EventMatched( - nsXBLPrototypeHandler* aHandler, - nsIAtom* aEventType, - nsIDOMKeyEvent* aEvent, - uint32_t aCharCode, - const IgnoreModifierState& aIgnoreModifierState) -{ - return aHandler->KeyEventMatched(aEventType, aEvent, aCharCode, - aIgnoreModifierState); + // Inform the child process that this is a event that we want a reply + // from. + widgetEvent->mFlags.mWantReplyFromContentProcess = true; + // If this event hadn't been marked as mNoCrossProcessBoundaryForwarding + // yet, it means it wasn't processed by content. We'll not call any + // of the handlers at this moment, and will wait for the event to be + // redispatched with mNoCrossProcessBoundaryForwarding = 1 to process it. + // XXX Why not StopImmediatePropagation()? + aEvent->AsEvent()->StopPropagation(); } bool @@ -441,17 +526,21 @@ nsXBLWindowKeyHandler::WalkHandlersInternal(nsIDOMKeyEvent* aKeyEvent, bool aExecute, bool* aOutReservedForChrome) { - AutoTArray accessKeys; - nsContentUtils::GetAccelKeyCandidates(aKeyEvent, accessKeys); + WidgetKeyboardEvent* nativeKeyboardEvent = + aKeyEvent->AsEvent()->WidgetEventPtr()->AsKeyboardEvent(); + MOZ_ASSERT(nativeKeyboardEvent); - if (accessKeys.IsEmpty()) { + AutoShortcutKeyCandidateArray shortcutKeys; + nativeKeyboardEvent->GetShortcutKeyCandidates(shortcutKeys); + + if (shortcutKeys.IsEmpty()) { return WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler, 0, IgnoreModifierState(), aExecute, aOutReservedForChrome); } - for (uint32_t i = 0; i < accessKeys.Length(); ++i) { - nsShortcutCandidate &key = accessKeys[i]; + for (uint32_t i = 0; i < shortcutKeys.Length(); ++i) { + ShortcutKeyCandidate& key = shortcutKeys[i]; IgnoreModifierState ignoreModifierState; ignoreModifierState.mShift = key.mIgnoreShift; if (WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler, @@ -467,92 +556,101 @@ bool nsXBLWindowKeyHandler::WalkHandlersAndExecute( nsIDOMKeyEvent* aKeyEvent, nsIAtom* aEventType, - nsXBLPrototypeHandler* aHandler, + nsXBLPrototypeHandler* aFirstHandler, uint32_t aCharCode, const IgnoreModifierState& aIgnoreModifierState, bool aExecute, bool* aOutReservedForChrome) { - nsresult rv; - // Try all of the handlers until we find one that matches the event. - for (nsXBLPrototypeHandler *currHandler = aHandler; currHandler; - currHandler = currHandler->GetNextHandler()) { + for (nsXBLPrototypeHandler* handler = aFirstHandler; + handler; + handler = handler->GetNextHandler()) { bool stopped = aKeyEvent->AsEvent()->IsDispatchStopped(); if (stopped) { // The event is finished, don't execute any more handlers return false; } - if (!EventMatched(currHandler, aEventType, aKeyEvent, - aCharCode, aIgnoreModifierState)) { + if (aExecute) { + // If it's executing matched handlers, the event type should exactly be + // matched. + if (!handler->EventTypeEquals(aEventType)) { + continue; + } + } else { + if (handler->EventTypeEquals(nsGkAtoms::keypress)) { + // If the handler is a keypress event handler, we also need to check + // if coming keydown event is a preceding event of reserved key + // combination because if default action of a keydown event is + // prevented, following keypress event won't be fired. However, if + // following keypress event is reserved, we shouldn't allow web + // contents to prevent the default of the preceding keydown event. + if (aEventType != nsGkAtoms::keydown && + aEventType != nsGkAtoms::keypress) { + continue; + } + } else if (!handler->EventTypeEquals(aEventType)) { + // Otherwise, aEventType should exactly be matched. + continue; + } + } + + // Check if the keyboard event *may* execute the handler. + if (!handler->KeyEventMatched(aKeyEvent, aCharCode, aIgnoreModifierState)) { continue; // try the next one } // Before executing this handler, check that it's not disabled, // and that it has something to do (oncommand of the or its // is non-empty). - nsCOMPtr elt = currHandler->GetHandlerElement(); - nsCOMPtr commandElt; - - // See if we're in a XUL doc. - nsCOMPtr el = GetElement(); - if (el && elt) { - // We are. Obtain our command attribute. - nsAutoString command; - elt->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); - if (!command.IsEmpty()) { - // Locate the command element in question. Note that we - // know "elt" is in a doc if we're dealing with it here. - NS_ASSERTION(elt->IsInDoc(), "elt must be in document"); - nsIDocument *doc = elt->GetCurrentDoc(); - if (doc) - commandElt = do_QueryInterface(doc->GetElementById(command)); - - if (!commandElt) { - NS_ERROR("A XUL is observing a command that doesn't exist. Unable to execute key binding!"); - continue; - } - } + nsCOMPtr commandElement; + if (!GetElementForHandler(handler, getter_AddRefs(commandElement))) { + continue; } - if (!commandElt) { - commandElt = do_QueryInterface(elt); - } - - if (commandElt) { - nsAutoString value; - commandElt->GetAttribute(NS_LITERAL_STRING("disabled"), value); - if (value.EqualsLiteral("true")) { - continue; // this handler is disabled, try the next one - } - - // Check that there is an oncommand handler - commandElt->GetAttribute(NS_LITERAL_STRING("oncommand"), value); - if (value.IsEmpty()) { - continue; // nothing to do + bool isReserved = false; + if (commandElement) { + if (!IsExecutableElement(commandElement)) { + continue; } + isReserved = + commandElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::reserved, + nsGkAtoms::_true, eCaseMatters); if (aOutReservedForChrome) { - // The caller wants to know if this is a reserved command - commandElt->GetAttribute(NS_LITERAL_STRING("reserved"), value); - *aOutReservedForChrome = value.EqualsLiteral("true"); + *aOutReservedForChrome = isReserved; } } - nsCOMPtr piTarget; - nsCOMPtr element = GetElement(); - if (element) { - piTarget = commandElt; - } else { - piTarget = mTarget; - } - if (!aExecute) { - return true; + if (handler->EventTypeEquals(aEventType)) { + return true; + } + // If the command is reserved and the event is keydown, check also if + // the handler is for keypress because if following keypress event is + // reserved, we shouldn't dispatch the event into web contents. + if (isReserved && + aEventType == nsGkAtoms::keydown && + handler->EventTypeEquals(nsGkAtoms::keypress)) { + return true; + } + // Otherwise, we've not found a handler for the event yet. + continue; } - rv = currHandler->ExecuteHandler(piTarget, aKeyEvent->AsEvent()); + nsCOMPtr target; + nsCOMPtr chromeHandlerElement = GetElement(); + if (chromeHandlerElement) { + // XXX commandElement may be nullptr... + target = commandElement; + } else { + target = mTarget; + } + + // XXX Do we execute only one handler even if the handler neither stops + // propagation nor prevents default of the event? + nsresult rv = handler->ExecuteHandler(target, aKeyEvent->AsEvent()); if (NS_SUCCEEDED(rv)) { return true; } @@ -569,8 +667,8 @@ nsXBLWindowKeyHandler::WalkHandlersAndExecute( if (keyEvent && keyEvent->IsOS()) { IgnoreModifierState ignoreModifierState(aIgnoreModifierState); ignoreModifierState.mOS = true; - return WalkHandlersAndExecute(aKeyEvent, aEventType, aHandler, aCharCode, - ignoreModifierState, aExecute); + return WalkHandlersAndExecute(aKeyEvent, aEventType, aFirstHandler, + aCharCode, ignoreModifierState, aExecute); } } #endif @@ -615,6 +713,76 @@ nsXBLWindowKeyHandler::GetElement(bool* aIsDisabled) return element.forget(); } +bool +nsXBLWindowKeyHandler::GetElementForHandler(nsXBLPrototypeHandler* aHandler, + Element** aElementForHandler) +{ + MOZ_ASSERT(aElementForHandler); + *aElementForHandler = nullptr; + + nsCOMPtr keyContent = aHandler->GetHandlerElement(); + if (!keyContent) { + return true; // XXX Even though no key element? + } + + nsCOMPtr chromeHandlerElement = GetElement(); + if (!chromeHandlerElement) { + NS_WARN_IF(!keyContent->IsInDoc()); + nsCOMPtr keyElement = do_QueryInterface(keyContent); + keyElement.swap(*aElementForHandler); + return true; + } + + // We are in a XUL doc. Obtain our command attribute. + nsAutoString command; + keyContent->GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); + if (command.IsEmpty()) { + // There is no command element associated with the key element. + NS_WARN_IF(!keyContent->IsInDoc()); + nsCOMPtr keyElement = do_QueryInterface(keyContent); + keyElement.swap(*aElementForHandler); + return true; + } + + // XXX Shouldn't we check this earlier? + nsIDocument* doc = keyContent->GetCurrentDoc(); + if (NS_WARN_IF(!doc)) { + return false; + } + + nsCOMPtr commandElement = + do_QueryInterface(doc->GetElementById(command)); + if (!commandElement) { + NS_ERROR("A XUL is observing a command that doesn't exist. " + "Unable to execute key binding!"); + return false; + } + + commandElement.swap(*aElementForHandler); + return true; +} + +bool +nsXBLWindowKeyHandler::IsExecutableElement(Element* aElement) const +{ + if (!aElement) { + return false; + } + + nsAutoString value; + aElement->GetAttribute(NS_LITERAL_STRING("disabled"), value); + if (value.EqualsLiteral("true")) { + return false; + } + + aElement->GetAttribute(NS_LITERAL_STRING("oncommand"), value); + if (value.IsEmpty()) { + return false; + } + + return true; +} + /////////////////////////////////////////////////////////////////////////////////// already_AddRefed diff --git a/dom/xbl/nsXBLWindowKeyHandler.h b/dom/xbl/nsXBLWindowKeyHandler.h index dcbf4adb53..66191a20d8 100644 --- a/dom/xbl/nsXBLWindowKeyHandler.h +++ b/dom/xbl/nsXBLWindowKeyHandler.h @@ -17,6 +17,7 @@ class nsXBLSpecialDocInfo; class nsXBLPrototypeHandler; namespace mozilla { +class EventListenerManager; namespace dom { class Element; class EventTarget; @@ -27,10 +28,16 @@ struct IgnoreModifierState; class nsXBLWindowKeyHandler : public nsIDOMEventListener { typedef mozilla::dom::IgnoreModifierState IgnoreModifierState; + typedef mozilla::EventListenerManager EventListenerManager; public: nsXBLWindowKeyHandler(nsIDOMElement* aElement, mozilla::dom::EventTarget* aTarget); + void InstallKeyboardEventListenersTo( + EventListenerManager* aEventListenerManager); + void RemoveKeyboardEventListenersFrom( + EventListenerManager* aEventListenerManager); + NS_DECL_ISUPPORTS NS_DECL_NSIDOMEVENTLISTENER @@ -55,8 +62,10 @@ protected: bool aExecute, bool* aOutReservedForChrome = nullptr); - // HandleEvent function for the capturing phase. - void HandleEventOnCapture(nsIDOMKeyEvent* aEvent); + // HandleEvent function for the capturing phase in the default event group. + void HandleEventOnCaptureInDefaultEventGroup(nsIDOMKeyEvent* aEvent); + // HandleEvent function for the capturing phase in the system event group. + void HandleEventOnCaptureInSystemEventGroup(nsIDOMKeyEvent* aEvent); // Check if any handler would handle the given event. Optionally returns // whether the command handler for the event is marked with the "reserved" @@ -68,11 +77,6 @@ protected: // to a particular element rather than the document nsresult EnsureHandlers(); - // check if the given handler cares about the given key event - bool EventMatched(nsXBLPrototypeHandler* aHandler, nsIAtom* aEventType, - nsIDOMKeyEvent* aEvent, uint32_t aCharCode, - const IgnoreModifierState& aIgnoreModifierState); - // Is an HTML editable element focused bool IsHTMLEditableFieldFocused(); @@ -81,6 +85,25 @@ protected: // whether the disabled attribute is set on the element (assuming the element // is non-null). already_AddRefed GetElement(bool* aIsDisabled = nullptr); + + /** + * GetElementForHandler() retrieves an element for the handler. The element + * may be a command element or a key element. + * + * @param aHandler The handler. + * @param aElementForHandler Must not be nullptr. The element is returned to + * this. + * @return true if the handler is valid. Otherwise, false. + */ + bool GetElementForHandler(nsXBLPrototypeHandler* aHandler, + mozilla::dom::Element** aElementForHandler); + + /** + * IsExecutableElement() returns true if aElement is executable. + * Otherwise, false. aElement should be a command element or a key element. + */ + bool IsExecutableElement(mozilla::dom::Element* aElement) const; + // Using weak pointer to the DOM Element. nsWeakPtr mWeakPtrForElement; mozilla::dom::EventTarget* mTarget; // weak ref diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp index a7d089a8a8..2a7cf70036 100644 --- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -1331,7 +1331,7 @@ nsXULElement::PreHandleEvent(EventChainPreVisitor& aVisitor) WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent(); nsContentUtils::DispatchXULCommand( commandContent, - aVisitor.mEvent->mFlags.mIsTrusted, + aVisitor.mEvent->IsTrusted(), aVisitor.mDOMEvent, nullptr, orig->IsControl(), diff --git a/editor/libeditor/nsEditor.cpp b/editor/libeditor/nsEditor.cpp index 992f8cb095..0a87c18a34 100644 --- a/editor/libeditor/nsEditor.cpp +++ b/editor/libeditor/nsEditor.cpp @@ -5106,7 +5106,7 @@ nsEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent) } // Accept all trusted events. - if (widgetEvent->mFlags.mIsTrusted) { + if (widgetEvent->IsTrusted()) { return true; } diff --git a/gfx/gl/GLLibraryEGL.cpp b/gfx/gl/GLLibraryEGL.cpp index 998141d6df..f344be15e2 100644 --- a/gfx/gl/GLLibraryEGL.cpp +++ b/gfx/gl/GLLibraryEGL.cpp @@ -134,8 +134,10 @@ static bool IsAccelAngleSupported(const nsCOMPtr& gfxInfo) { int32_t angleSupport; + nsCString discardFailureId; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_ANGLE, + discardFailureId, &angleSupport); return (angleSupport == nsIGfxInfo::FEATURE_STATUS_OK); } diff --git a/gfx/ipc/GfxMessageUtils.h b/gfx/ipc/GfxMessageUtils.h index 3c7bcb5068..02ecfcb5aa 100644 --- a/gfx/ipc/GfxMessageUtils.h +++ b/gfx/ipc/GfxMessageUtils.h @@ -669,6 +669,14 @@ struct ParamTraits : RegionParamTraits {}; +template<> +struct ParamTraits + : public ContiguousEnumSerializer< + mozilla::layers::FrameMetrics::ScrollOffsetUpdateType, + mozilla::layers::FrameMetrics::ScrollOffsetUpdateType::eNone, + mozilla::layers::FrameMetrics::ScrollOffsetUpdateType::eSentinel> +{}; + template <> struct ParamTraits { @@ -698,18 +706,17 @@ struct ParamTraits WriteParam(aMsg, aParam.GetContentDescription()); WriteParam(aMsg, aParam.mLineScrollAmount); WriteParam(aMsg, aParam.mPageScrollAmount); - WriteParam(aMsg, aParam.mClipRect); - WriteParam(aMsg, aParam.mMaskLayerIndex); WriteParam(aMsg, aParam.mPaintRequestTime); + WriteParam(aMsg, aParam.mScrollUpdateType); WriteParam(aMsg, aParam.mIsRootContent); WriteParam(aMsg, aParam.mHasScrollgrab); - WriteParam(aMsg, aParam.mUpdateScrollOffset); WriteParam(aMsg, aParam.mDoSmoothScroll); WriteParam(aMsg, aParam.mUseDisplayPortMargins); WriteParam(aMsg, aParam.mAllowVerticalScrollWithWheel); WriteParam(aMsg, aParam.mIsLayersIdRoot); WriteParam(aMsg, aParam.mUsesContainerScrolling); WriteParam(aMsg, aParam.mIsScrollInfoLayer); + WriteParam(aMsg, aParam.mForceDisableApz); } static bool ReadContentDescription(const Message* aMsg, void** aIter, paramType* aResult) @@ -760,18 +767,65 @@ struct ParamTraits ReadContentDescription(aMsg, aIter, aResult) && ReadParam(aMsg, aIter, &aResult->mLineScrollAmount) && ReadParam(aMsg, aIter, &aResult->mPageScrollAmount) && - ReadParam(aMsg, aIter, &aResult->mClipRect) && - ReadParam(aMsg, aIter, &aResult->mMaskLayerIndex) && ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) && + ReadParam(aMsg, aIter, &aResult->mScrollUpdateType) && ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetIsRootContent) && ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetHasScrollgrab) && - ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetUpdateScrollOffset) && ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetDoSmoothScroll) && ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetUseDisplayPortMargins) && ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetAllowVerticalScrollWithWheel) && ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetIsLayersIdRoot) && ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetUsesContainerScrolling) && - ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetIsScrollInfoLayer)); + ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetIsScrollInfoLayer) && + ReadBoolForBitfield(aMsg, aIter, aResult, ¶mType::SetForceDisableApz)); + } +}; + +template <> +struct ParamTraits +{ + typedef mozilla::layers::ScrollSnapInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mScrollSnapTypeX); + WriteParam(aMsg, aParam.mScrollSnapTypeY); + WriteParam(aMsg, aParam.mScrollSnapIntervalX); + WriteParam(aMsg, aParam.mScrollSnapIntervalY); + WriteParam(aMsg, aParam.mScrollSnapDestination); + WriteParam(aMsg, aParam.mScrollSnapCoordinates); + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + return (ReadParam(aMsg, aIter, &aResult->mScrollSnapTypeX) && + ReadParam(aMsg, aIter, &aResult->mScrollSnapTypeY) && + ReadParam(aMsg, aIter, &aResult->mScrollSnapIntervalX) && + ReadParam(aMsg, aIter, &aResult->mScrollSnapIntervalY) && + ReadParam(aMsg, aIter, &aResult->mScrollSnapDestination) && + ReadParam(aMsg, aIter, &aResult->mScrollSnapCoordinates)); + } +}; + +template <> +struct ParamTraits +{ + typedef mozilla::layers::ScrollMetadata paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mMetrics); + WriteParam(aMsg, aParam.mSnapInfo); + WriteParam(aMsg, aParam.mMaskLayerIndex); + WriteParam(aMsg, aParam.mClipRect); + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + return (ReadParam(aMsg, aIter, &aResult->mMetrics) && + ReadParam(aMsg, aIter, &aResult->mSnapInfo) && + ReadParam(aMsg, aIter, &aResult->mMaskLayerIndex) && + ReadParam(aMsg, aIter, &aResult->mClipRect)); } }; diff --git a/gfx/layers/FrameMetrics.cpp b/gfx/layers/FrameMetrics.cpp index a4cf2b579a..3b55cb1973 100644 --- a/gfx/layers/FrameMetrics.cpp +++ b/gfx/layers/FrameMetrics.cpp @@ -10,7 +10,6 @@ namespace mozilla { namespace layers { const FrameMetrics::ViewID FrameMetrics::NULL_SCROLL_ID = 0; -const FrameMetrics FrameMetrics::sNullMetrics; void FrameMetrics::SetUsesContainerScrolling(bool aValue) { @@ -18,5 +17,7 @@ FrameMetrics::SetUsesContainerScrolling(bool aValue) { mUsesContainerScrolling = aValue; } +StaticAutoPtr ScrollMetadata::sNullMetadata; + +} } -} \ No newline at end of file diff --git a/gfx/layers/FrameMetrics.h b/gfx/layers/FrameMetrics.h index 556d16572e..c712a8ad67 100644 --- a/gfx/layers/FrameMetrics.h +++ b/gfx/layers/FrameMetrics.h @@ -14,8 +14,10 @@ #include "mozilla/gfx/Rect.h" // for RoundedIn #include "mozilla/gfx/ScaleFactor.h" // for ScaleFactor #include "mozilla/gfx/Logging.h" // for Log +#include "mozilla/StaticPtr.h" // for StaticAutoPtr #include "mozilla/TimeStamp.h" // for TimeStamp #include "nsString.h" +#include "nsStyleCoord.h" // for nsStyleCoord namespace IPC { template struct ParamTraits; @@ -24,6 +26,15 @@ template struct ParamTraits; namespace mozilla { namespace layers { +/** + * Helper struct to hold a couple of fields that can be updated as part of + * an empty transaction. + */ +struct ScrollUpdateInfo { + uint32_t mScrollGeneration; + CSSPoint mScrollOffset; +}; + /** * The viewport and displayport metrics for the painted frame at the * time of a layer-tree transaction. These metrics are especially @@ -38,7 +49,16 @@ public: static const ViewID NULL_SCROLL_ID; // This container layer does not scroll. static const ViewID START_SCROLL_ID = 2; // This is the ID that scrolling subframes // will begin at. - static const FrameMetrics sNullMetrics; // We often need an empty metrics + + enum ScrollOffsetUpdateType : uint8_t { + eNone, // The default; the scroll offset was not updated + eMainThread, // The scroll offset was updated by the main thread. + ePending, // The scroll offset was updated on the main thread, but not + // painted, so the layer texture data is still at the old + // offset. + + eSentinel // For IPC use only + }; FrameMetrics() : mScrollId(NULL_SCROLL_ID) @@ -63,18 +83,17 @@ public: , mContentDescription() , mLineScrollAmount(0, 0) , mPageScrollAmount(0, 0) - , mClipRect() - , mMaskLayerIndex() , mPaintRequestTime() + , mScrollUpdateType(eNone) , mIsRootContent(false) , mHasScrollgrab(false) - , mUpdateScrollOffset(false) , mDoSmoothScroll(false) , mUseDisplayPortMargins(false) , mAllowVerticalScrollWithWheel(false) , mIsLayersIdRoot(false) , mUsesContainerScrolling(false) , mIsScrollInfoLayer(false) + , mForceDisableApz(false) { } @@ -105,18 +124,17 @@ public: // don't compare mContentDescription mLineScrollAmount == aOther.mLineScrollAmount && mPageScrollAmount == aOther.mPageScrollAmount && - mClipRect == aOther.mClipRect && - mMaskLayerIndex == aOther.mMaskLayerIndex && mPaintRequestTime == aOther.mPaintRequestTime && + mScrollUpdateType == aOther.mScrollUpdateType && mIsRootContent == aOther.mIsRootContent && mHasScrollgrab == aOther.mHasScrollgrab && - mUpdateScrollOffset == aOther.mUpdateScrollOffset && mDoSmoothScroll == aOther.mDoSmoothScroll && mUseDisplayPortMargins == aOther.mUseDisplayPortMargins && mAllowVerticalScrollWithWheel == aOther.mAllowVerticalScrollWithWheel && mIsLayersIdRoot == aOther.mIsLayersIdRoot && mUsesContainerScrolling == aOther.mUsesContainerScrolling && - mIsScrollInfoLayer == aOther.mIsScrollInfoLayer; + mIsScrollInfoLayer == aOther.mIsScrollInfoLayer && + mForceDisableApz == aOther.mForceDisableApz; } bool operator!=(const FrameMetrics& aOther) const @@ -124,14 +142,6 @@ public: return !operator==(aOther); } - bool IsDefault() const - { - FrameMetrics def; - - def.mPresShellId = mPresShellId; - return (def == *this); - } - bool IsScrollable() const { return mScrollId != NULL_SCROLL_ID; @@ -205,6 +215,15 @@ public: return size; } + CSSRect CalculateScrollRange() const + { + CSSSize scrollPortSize = CalculateCompositedSizeInCssPixels(); + CSSRect scrollRange = mScrollableRect; + scrollRange.width = std::max(scrollRange.width - scrollPortSize.width, 0.0f); + scrollRange.height = std::max(scrollRange.height - scrollPortSize.height, 0.0f); + return scrollRange; + } + void ScrollBy(const CSSPoint& aPoint) { mScrollOffset += aPoint; @@ -234,10 +253,11 @@ public: mDoSmoothScroll = aOther.mDoSmoothScroll; } - void UpdateScrollInfo(uint32_t aScrollGeneration, const CSSPoint& aScrollOffset) + void UpdatePendingScrollInfo(const ScrollUpdateInfo& aInfo) { - mScrollOffset = aScrollOffset; - mScrollGeneration = aScrollGeneration; + mScrollOffset = aInfo.mScrollOffset; + mScrollGeneration = aInfo.mScrollGeneration; + mScrollUpdateType = ePending; } // Make a copy of this FrameMetrics object which does not have any pointers @@ -363,7 +383,7 @@ public: void SetScrollOffsetUpdated(uint32_t aScrollGeneration) { - mUpdateScrollOffset = true; + mScrollUpdateType = eMainThread; mScrollGeneration = aScrollGeneration; } @@ -373,9 +393,14 @@ public: mScrollGeneration = aScrollGeneration; } + ScrollOffsetUpdateType GetScrollUpdateType() const + { + return mScrollUpdateType; + } + bool GetScrollOffsetUpdated() const { - return mUpdateScrollOffset; + return mScrollUpdateType != eNone; } bool GetDoSmoothScroll() const @@ -528,28 +553,6 @@ public: mAllowVerticalScrollWithWheel = aValue; } - void SetClipRect(const Maybe& aClipRect) - { - mClipRect = aClipRect; - } - const Maybe& GetClipRect() const - { - return mClipRect; - } - bool HasClipRect() const { - return mClipRect.isSome(); - } - const ParentLayerIntRect& ClipRect() const { - return mClipRect.ref(); - } - - void SetMaskLayerIndex(const Maybe& aIndex) { - mMaskLayerIndex = aIndex; - } - const Maybe& GetMaskLayerIndex() const { - return mMaskLayerIndex; - } - void SetPaintRequestTime(const TimeStamp& aTime) { mPaintRequestTime = aTime; } @@ -578,6 +581,13 @@ public: return mIsScrollInfoLayer; } + void SetForceDisableApz(bool aForceDisable) { + mForceDisableApz = aForceDisable; + } + bool IsApzForceDisabled() const { + return mForceDisableApz; + } + private: // A unique ID assigned to each scrollable frame. ViewID mScrollId; @@ -731,27 +741,19 @@ private: // The value of GetPageScrollAmount(), for scroll frames. LayoutDeviceIntSize mPageScrollAmount; - // The clip rect to use when compositing a layer with this FrameMetrics. - Maybe mClipRect; - - // An extra clip mask layer to use when compositing a layer with this - // FrameMetrics. This is an index into the MetricsMaskLayers array on - // the Layer. - Maybe mMaskLayerIndex; - // The time at which the APZC last requested a repaint for this scrollframe. TimeStamp mPaintRequestTime; + // Whether mScrollOffset was updated by something other than the APZ code, and + // if the APZC receiving this metrics should update its local copy. + ScrollOffsetUpdateType mScrollUpdateType; + // Whether or not this is the root scroll frame for the root content document. bool mIsRootContent:1; // Whether or not this frame is for an element marked 'scrollgrab'. bool mHasScrollgrab:1; - // Whether mScrollOffset was updated by something other than the APZ code, and - // if the APZC receiving this metrics should update its local copy. - bool mUpdateScrollOffset:1; - // When mDoSmoothScroll, the scroll offset should be animated to // smoothly transition to mScrollOffset rather than be updated instantly. bool mDoSmoothScroll:1; @@ -774,6 +776,10 @@ private: // Whether or not this frame has a "scroll info layer" to capture events. bool mIsScrollInfoLayer:1; + // Whether or not the compositor should actually do APZ-scrolling on this + // scrollframe. + bool mForceDisableApz:1; + // WARNING!!!! // // When adding new fields to FrameMetrics, the following places should be @@ -786,14 +792,119 @@ private: // Private helpers for IPC purposes - void SetUpdateScrollOffset(bool aValue) { - mUpdateScrollOffset = aValue; - } void SetDoSmoothScroll(bool aValue) { mDoSmoothScroll = aValue; } }; +struct ScrollSnapInfo { + ScrollSnapInfo() + : mScrollSnapTypeX(NS_STYLE_SCROLL_SNAP_TYPE_NONE) + , mScrollSnapTypeY(NS_STYLE_SCROLL_SNAP_TYPE_NONE) + {} + + // The scroll frame's scroll-snap-type. + // One of NS_STYLE_SCROLL_SNAP_{NONE, MANDATORY, PROXIMITY}. + uint8_t mScrollSnapTypeX; + uint8_t mScrollSnapTypeY; + + // The intervals derived from the scroll frame's scroll-snap-points. + Maybe mScrollSnapIntervalX; + Maybe mScrollSnapIntervalY; + + // The scroll frame's scroll-snap-destination, in cooked form (to avoid + // shipping the raw nsStyleCoord::CalcValue over IPC). + nsPoint mScrollSnapDestination; + + // The scroll-snap-coordinates of any descendant frames of the scroll frame, + // relative to the origin of the scrolled frame. + nsTArray mScrollSnapCoordinates; +}; + +/** + * Metadata about a scroll frame that's stored in the layer tree for use by + * the compositor (including APZ). This includes the scroll frame's FrameMetrics, + * as well as other metadata. We don't put the other metadata into FrameMetrics + * to avoid FrameMetrics becoming too bloated (as a FrameMetrics is e.g. sent + * over IPC for every repaint request for every active scroll frame). + */ +struct ScrollMetadata { + friend struct IPC::ParamTraits; +public: + static StaticAutoPtr sNullMetadata; // We sometimes need an empty metadata + + ScrollMetadata() + : mMetrics() + , mSnapInfo() + , mMaskLayerIndex() + , mClipRect() + {} + + bool operator==(const ScrollMetadata& aOther) const + { + // TODO(botond): Should we include mSnapInfo in the comparison? + return mMetrics == aOther.mMetrics && + mMaskLayerIndex == aOther.mMaskLayerIndex && + mClipRect == aOther.mClipRect; + } + + bool operator!=(const ScrollMetadata& aOther) const + { + return !operator==(aOther); + } + + bool IsDefault() const + { + ScrollMetadata def; + + def.mMetrics.SetPresShellId(mMetrics.GetPresShellId()); + return (def == *this); + } + + FrameMetrics& GetMetrics() { return mMetrics; } + const FrameMetrics& GetMetrics() const { return mMetrics; } + + void SetSnapInfo(ScrollSnapInfo&& aSnapInfo) { + mSnapInfo = Move(aSnapInfo); + } + const ScrollSnapInfo& GetSnapInfo() const { return mSnapInfo; } + + void SetMaskLayerIndex(const Maybe& aIndex) { + mMaskLayerIndex = aIndex; + } + const Maybe& GetMaskLayerIndex() const { + return mMaskLayerIndex; + } + + void SetClipRect(const Maybe& aClipRect) + { + mClipRect = aClipRect; + } + const Maybe& GetClipRect() const + { + return mClipRect; + } + bool HasClipRect() const { + return mClipRect.isSome(); + } + const ParentLayerIntRect& ClipRect() const { + return mClipRect.ref(); + } +private: + FrameMetrics mMetrics; + + // Information used to determine where to snap to for a given scroll. + ScrollSnapInfo mSnapInfo; + + // An extra clip mask layer to use when compositing a layer with this + // FrameMetrics. This is an index into the MetricsMaskLayers array on + // the Layer. + Maybe mMaskLayerIndex; + + // The clip rect to use when compositing a layer with this FrameMetrics. + Maybe mClipRect; +}; + /** * This class allows us to uniquely identify a scrollable layer. The * mLayersId identifies the layer tree (corresponding to a child process diff --git a/gfx/layers/LayerMetricsWrapper.h b/gfx/layers/LayerMetricsWrapper.h index fbda790f07..cdd565d5da 100644 --- a/gfx/layers/LayerMetricsWrapper.h +++ b/gfx/layers/LayerMetricsWrapper.h @@ -44,7 +44,7 @@ namespace layers { * being leaf nodes. Layer C is in the middle and has n+1 FrameMetrics, labelled * FM0...FMn. FM0 is the FrameMetrics you get by calling c->GetFrameMetrics(0) * and FMn is the FrameMetrics you can obtain by calling - * c->GetFrameMetrics(c->GetFrameMetricsCount() - 1). This layer tree is + * c->GetFrameMetrics(c->GetScrollMetadataCount() - 1). This layer tree is * conceptually equivalent to this one below: * * +---+ @@ -113,8 +113,8 @@ namespace layers { * the wrapped layer as a void* for printf purposes. * * The implementation may look like it special-cases mIndex == 0 and/or - * GetFrameMetricsCount() == 0. This is an artifact of the fact that both - * mIndex and GetFrameMetricsCount() are uint32_t and GetFrameMetricsCount() + * GetScrollMetadataCount() == 0. This is an artifact of the fact that both + * mIndex and GetScrollMetadataCount() are uint32_t and GetScrollMetadataCount() * can return 0 but mIndex cannot store -1. This seems better than the * alternative of making mIndex a int32_t that can store -1, but then having * to cast to uint32_t all over the place. @@ -142,7 +142,7 @@ public: switch (aStart) { case StartAt::TOP: - mIndex = mLayer->GetFrameMetricsCount(); + mIndex = mLayer->GetScrollMetadataCount(); if (mIndex > 0) { mIndex--; } @@ -161,7 +161,7 @@ public: , mIndex(aMetricsIndex) { MOZ_ASSERT(mLayer); - MOZ_ASSERT(mIndex == 0 || mIndex < mLayer->GetFrameMetricsCount()); + MOZ_ASSERT(mIndex == 0 || mIndex < mLayer->GetScrollMetadataCount()); } bool IsValid() const @@ -240,21 +240,26 @@ public: return LayerMetricsWrapper(nullptr); } - const FrameMetrics& Metrics() const + const ScrollMetadata& Metadata() const { MOZ_ASSERT(IsValid()); - if (mIndex >= mLayer->GetFrameMetricsCount()) { - return FrameMetrics::sNullMetrics; + if (mIndex >= mLayer->GetScrollMetadataCount()) { + return *ScrollMetadata::sNullMetadata; } - return mLayer->GetFrameMetrics(mIndex); + return mLayer->GetScrollMetadata(mIndex); + } + + const FrameMetrics& Metrics() const + { + return Metadata().GetMetrics(); } AsyncPanZoomController* GetApzc() const { MOZ_ASSERT(IsValid()); - if (mIndex >= mLayer->GetFrameMetricsCount()) { + if (mIndex >= mLayer->GetScrollMetadataCount()) { return nullptr; } return mLayer->GetAsyncPanZoomController(mIndex); @@ -264,12 +269,12 @@ public: { MOZ_ASSERT(IsValid()); - if (mLayer->GetFrameMetricsCount() == 0) { + if (mLayer->GetScrollMetadataCount() == 0) { MOZ_ASSERT(mIndex == 0); MOZ_ASSERT(aApzc == nullptr); return; } - MOZ_ASSERT(mIndex < mLayer->GetFrameMetricsCount()); + MOZ_ASSERT(mIndex < mLayer->GetScrollMetadataCount()); mLayer->SetAsyncPanZoomController(mIndex, aApzc); } @@ -441,30 +446,30 @@ public: static const FrameMetrics& TopmostScrollableMetrics(Layer* aLayer) { - for (uint32_t i = aLayer->GetFrameMetricsCount(); i > 0; i--) { + for (uint32_t i = aLayer->GetScrollMetadataCount(); i > 0; i--) { if (aLayer->GetFrameMetrics(i - 1).IsScrollable()) { return aLayer->GetFrameMetrics(i - 1); } } - return FrameMetrics::sNullMetrics; + return ScrollMetadata::sNullMetadata->GetMetrics(); } static const FrameMetrics& BottommostScrollableMetrics(Layer* aLayer) { - for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) { + for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) { if (aLayer->GetFrameMetrics(i).IsScrollable()) { return aLayer->GetFrameMetrics(i); } } - return FrameMetrics::sNullMetrics; + return ScrollMetadata::sNullMetadata->GetMetrics(); } static const FrameMetrics& BottommostMetrics(Layer* aLayer) { - if (aLayer->GetFrameMetricsCount() > 0) { + if (aLayer->GetScrollMetadataCount() > 0) { return aLayer->GetFrameMetrics(0); } - return FrameMetrics::sNullMetrics; + return ScrollMetadata::sNullMetadata->GetMetrics(); } private: @@ -475,7 +480,7 @@ private: bool AtTopLayer() const { - return mLayer->GetFrameMetricsCount() == 0 || mIndex == mLayer->GetFrameMetricsCount() - 1; + return mLayer->GetScrollMetadataCount() == 0 || mIndex == mLayer->GetScrollMetadataCount() - 1; } private: diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index ef4a89f8dd..1f7a3d78ac 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -538,14 +538,14 @@ Layer::StartPendingAnimations(const TimeStamp& aReadyTime) void Layer::SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller) { - MOZ_ASSERT(aIndex < GetFrameMetricsCount()); + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); mApzcs[aIndex] = controller; } AsyncPanZoomController* Layer::GetAsyncPanZoomController(uint32_t aIndex) const { - MOZ_ASSERT(aIndex < GetFrameMetricsCount()); + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); #ifdef DEBUG if (mApzcs[aIndex]) { MOZ_ASSERT(GetFrameMetrics(aIndex).IsScrollable()); @@ -555,9 +555,9 @@ Layer::GetAsyncPanZoomController(uint32_t aIndex) const } void -Layer::FrameMetricsChanged() +Layer::ScrollMetadataChanged() { - mApzcs.SetLength(GetFrameMetricsCount()); + mApzcs.SetLength(GetScrollMetadataCount()); } void @@ -567,6 +567,11 @@ Layer::ApplyPendingUpdatesToSubtree() for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) { child->ApplyPendingUpdatesToSubtree(); } + if (!GetParent()) { + // Once we're done recursing through the whole tree, clear the pending + // updates from the manager. + Manager()->ClearPendingScrollInfoUpdate(); + } } bool @@ -846,17 +851,23 @@ Layer::CalculateScissorRect(const RenderTargetIntRect& aCurrentScissorRect) return currentClip.Intersect(scissor); } +const ScrollMetadata& +Layer::GetScrollMetadata(uint32_t aIndex) const +{ + MOZ_ASSERT(aIndex < GetScrollMetadataCount()); + return mScrollMetadata[aIndex]; +} + const FrameMetrics& Layer::GetFrameMetrics(uint32_t aIndex) const { - MOZ_ASSERT(aIndex < GetFrameMetricsCount()); - return mFrameMetrics[aIndex]; + return GetScrollMetadata(aIndex).GetMetrics(); } bool Layer::HasScrollableFrameMetrics() const { - for (uint32_t i = 0; i < GetFrameMetricsCount(); i++) { + for (uint32_t i = 0; i < GetScrollMetadataCount(); i++) { if (GetFrameMetrics(i).IsScrollable()) { return true; } @@ -940,6 +951,15 @@ Layer::ApplyPendingUpdatesForThisTransaction() mPendingAnimations = nullptr; Mutated(); } + + for (size_t i = 0; i < mScrollMetadata.Length(); i++) { + FrameMetrics& fm = mScrollMetadata[i].GetMetrics(); + Maybe update = Manager()->GetPendingScrollInfoUpdate(fm.GetScrollId()); + if (update) { + fm.UpdatePendingScrollInfo(update.value()); + Mutated(); + } + } } float @@ -1088,12 +1108,12 @@ Layer::GetCombinedClipRect() const { Maybe clip = GetClipRect(); - for (size_t i = 0; i < mFrameMetrics.Length(); i++) { - if (!mFrameMetrics[i].HasClipRect()) { + for (size_t i = 0; i < mScrollMetadata.Length(); i++) { + if (!mScrollMetadata[i].HasClipRect()) { continue; } - const ParentLayerIntRect& other = mFrameMetrics[i].ClipRect(); + const ParentLayerIntRect& other = mScrollMetadata[i].ClipRect(); if (clip) { clip = Some(clip.value().Intersect(other)); } else { @@ -1968,10 +1988,10 @@ Layer::PrintInfo(std::stringstream& aStream, const char* aPrefix) if (mMaskLayer) { aStream << nsPrintfCString(" [mMaskLayer=%p]", mMaskLayer.get()).get(); } - for (uint32_t i = 0; i < mFrameMetrics.Length(); i++) { - if (!mFrameMetrics[i].IsDefault()) { + for (uint32_t i = 0; i < mScrollMetadata.Length(); i++) { + if (!mScrollMetadata[i].IsDefault()) { aStream << nsPrintfCString(" [metrics%d=", i).get(); - AppendToString(aStream, mFrameMetrics[i], "", "]"); + AppendToString(aStream, mScrollMetadata[i], "", "]"); } } } @@ -2440,6 +2460,29 @@ LayerManager::IsLogEnabled() return MOZ_LOG_TEST(GetLog(), LogLevel::Debug); } +void +LayerManager::SetPendingScrollUpdateForNextTransaction(FrameMetrics::ViewID aScrollId, + const ScrollUpdateInfo& aUpdateInfo) +{ + mPendingScrollUpdates[aScrollId] = aUpdateInfo; +} + +Maybe +LayerManager::GetPendingScrollInfoUpdate(FrameMetrics::ViewID aScrollId) +{ + auto it = mPendingScrollUpdates.find(aScrollId); + if (it != mPendingScrollUpdates.end()) { + return Some(it->second); + } + return Nothing(); +} + +void +LayerManager::ClearPendingScrollInfoUpdate() +{ + mPendingScrollUpdates.clear(); +} + void PrintInfo(std::stringstream& aStream, LayerComposite* aLayerComposite) { diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index a07fe2e63c..6502738d39 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -6,6 +6,7 @@ #ifndef GFX_LAYERS_H #define GFX_LAYERS_H +#include #include // for uint32_t, uint64_t, uint8_t #include // for FILE #include // for int32_t, int64_t @@ -698,6 +699,19 @@ private: FramesTimingRecording mRecording; TimeStamp mTabSwitchStart; + +public: + /* + * Methods to store/get/clear a "pending scroll info update" object on a + * per-scrollid basis. This is used for empty transactions that push over + * scroll position updates to the APZ code. + */ + void SetPendingScrollUpdateForNextTransaction(FrameMetrics::ViewID aScrollId, + const ScrollUpdateInfo& aUpdateInfo); + Maybe GetPendingScrollInfoUpdate(FrameMetrics::ViewID aScrollId); + void ClearPendingScrollInfoUpdate(); +private: + std::map mPendingScrollUpdates; }; typedef InfallibleTArray AnimationArray; @@ -855,12 +869,13 @@ public: * them with the provided FrameMetrics. See the documentation for * SetFrameMetrics(const nsTArray&) for more details. */ - void SetFrameMetrics(const FrameMetrics& aFrameMetrics) + void SetScrollMetadata(const ScrollMetadata& aScrollMetadata) { - if (mFrameMetrics.Length() != 1 || mFrameMetrics[0] != aFrameMetrics) { + Manager()->ClearPendingScrollInfoUpdate(); + if (mScrollMetadata.Length() != 1 || mScrollMetadata[0] != aScrollMetadata) { MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this)); - mFrameMetrics.ReplaceElementsAt(0, mFrameMetrics.Length(), aFrameMetrics); - FrameMetricsChanged(); + mScrollMetadata.ReplaceElementsAt(0, mScrollMetadata.Length(), aScrollMetadata); + ScrollMetadataChanged(); Mutated(); } } @@ -871,23 +886,24 @@ public: * rooted at this. There might be multiple metrics on this layer * because the layer may, for example, be contained inside multiple * nested scrolling subdocuments. In general a Layer having multiple - * FrameMetrics objects is conceptually equivalent to having a stack + * ScrollMetadata objects is conceptually equivalent to having a stack * of ContainerLayers that have been flattened into this Layer. * See the documentation in LayerMetricsWrapper.h for a more detailed * explanation of this conceptual equivalence. * * Note also that there is actually a many-to-many relationship between - * Layers and FrameMetrics, because multiple Layers may have identical - * FrameMetrics objects. This happens when those layers belong to the + * Layers and ScrollMetadata, because multiple Layers may have identical + * ScrollMetadata objects. This happens when those layers belong to the * same scrolling subdocument and therefore end up with the same async * transform when they are scrolled by the APZ code. */ - void SetFrameMetrics(const nsTArray& aMetricsArray) + void SetScrollMetadata(const nsTArray& aMetadataArray) { - if (mFrameMetrics != aMetricsArray) { + Manager()->ClearPendingScrollInfoUpdate(); + if (mScrollMetadata != aMetadataArray) { MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FrameMetrics", this)); - mFrameMetrics = aMetricsArray; - FrameMetricsChanged(); + mScrollMetadata = aMetadataArray; + ScrollMetadataChanged(); Mutated(); } } @@ -1254,9 +1270,10 @@ public: uint32_t GetContentFlags() { return mContentFlags; } const gfx::IntRect& GetLayerBounds() const { return mLayerBounds; } const LayerIntRegion& GetVisibleRegion() const { return mVisibleRegion; } + const ScrollMetadata& GetScrollMetadata(uint32_t aIndex) const; const FrameMetrics& GetFrameMetrics(uint32_t aIndex) const; - uint32_t GetFrameMetricsCount() const { return mFrameMetrics.Length(); } - const nsTArray& GetAllFrameMetrics() { return mFrameMetrics; } + uint32_t GetScrollMetadataCount() const { return mScrollMetadata.Length(); } + const nsTArray& GetAllScrollMetadata() { return mScrollMetadata; } bool HasScrollableFrameMetrics() const; bool IsScrollInfoLayer() const; const EventRegions& GetEventRegions() const { return mEventRegions; } @@ -1664,13 +1681,13 @@ public: // and can be used anytime. // A layer has an APZC at index aIndex only-if GetFrameMetrics(aIndex).IsScrollable(); // attempting to get an APZC for a non-scrollable metrics will return null. - // The aIndex for these functions must be less than GetFrameMetricsCount(). + // The aIndex for these functions must be less than GetScrollMetadataCount(). void SetAsyncPanZoomController(uint32_t aIndex, AsyncPanZoomController *controller); AsyncPanZoomController* GetAsyncPanZoomController(uint32_t aIndex) const; - // The FrameMetricsChanged function is used internally to ensure the APZC array length + // The ScrollMetadataChanged function is used internally to ensure the APZC array length // matches the frame metrics array length. private: - void FrameMetricsChanged(); + void ScrollMetadataChanged(); public: void ApplyPendingUpdatesForThisTransaction(); @@ -1791,7 +1808,7 @@ protected: gfx::UserData mUserData; gfx::IntRect mLayerBounds; LayerIntRegion mVisibleRegion; - nsTArray mFrameMetrics; + nsTArray mScrollMetadata; EventRegions mEventRegions; gfx::Matrix4x4 mTransform; // A mutation of |mTransform| that we've queued to be applied at the @@ -2498,8 +2515,6 @@ private: virtual bool RepositionChild(Layer* aChild, Layer* aAfter) override { MOZ_CRASH(); return false; } - using Layer::SetFrameMetrics; - public: /** * CONSTRUCTION PHASE ONLY diff --git a/gfx/layers/LayersLogging.cpp b/gfx/layers/LayersLogging.cpp index 22fd69c05f..12d1c5d966 100644 --- a/gfx/layers/LayersLogging.cpp +++ b/gfx/layers/LayersLogging.cpp @@ -144,6 +144,18 @@ AppendToString(std::stringstream& aStream, const EventRegions& e, aStream << "}" << sfx; } +void +AppendToString(std::stringstream& aStream, const ScrollMetadata& m, + const char* pfx, const char* sfx) +{ + aStream << pfx; + AppendToString(aStream, m.GetMetrics(), "{ [metrics=", "]"); + if (m.HasClipRect()) { + AppendToString(aStream, m.ClipRect(), " [clip=", "]"); + } + aStream << "}" << sfx; +} + void AppendToString(std::stringstream& aStream, const FrameMetrics& m, const char* pfx, const char* sfx, bool detailed) @@ -166,9 +178,6 @@ AppendToString(std::stringstream& aStream, const FrameMetrics& m, if (m.IsRootContent()) { aStream << "] [rcd"; } - if (m.HasClipRect()) { - AppendToString(aStream, m.ClipRect(), "] [clip="); - } AppendToString(aStream, m.GetZoom(), "] [z=", "] }"); } else { AppendToString(aStream, m.GetDisplayPortMargins(), " [dpm="); @@ -182,7 +191,7 @@ AppendToString(std::stringstream& aStream, const FrameMetrics& m, AppendToString(aStream, m.GetZoom(), " z="); AppendToString(aStream, m.GetExtraResolution(), " er="); aStream << nsPrintfCString(")] [u=(%d %d %lu)", - m.GetScrollOffsetUpdated(), m.GetDoSmoothScroll(), + m.GetScrollUpdateType(), m.GetDoSmoothScroll(), m.GetScrollGeneration()).get(); AppendToString(aStream, m.GetScrollParentId(), "] [p="); aStream << nsPrintfCString("] [i=(%ld %lld %d)] }", diff --git a/gfx/layers/LayersLogging.h b/gfx/layers/LayersLogging.h index 169acb8f67..96229728a7 100644 --- a/gfx/layers/LayersLogging.h +++ b/gfx/layers/LayersLogging.h @@ -114,6 +114,10 @@ void AppendToString(std::stringstream& aStream, const EventRegions& e, const char* pfx="", const char* sfx=""); +void +AppendToString(std::stringstream& aStream, const ScrollMetadata& m, + const char* pfx="", const char* sfx=""); + void AppendToString(std::stringstream& aStream, const FrameMetrics& m, const char* pfx="", const char* sfx="", bool detailed = false); diff --git a/gfx/layers/apz/public/GeckoContentController.h b/gfx/layers/apz/public/GeckoContentController.h index ecaafe20c5..650b9ce059 100644 --- a/gfx/layers/apz/public/GeckoContentController.h +++ b/gfx/layers/apz/public/GeckoContentController.h @@ -152,7 +152,8 @@ public: virtual void NotifyFlushComplete() = 0; virtual void UpdateOverscrollVelocity(const float aX, const float aY) {} - virtual void UpdateOverscrollOffset(const float aX,const float aY) {} + virtual void UpdateOverscrollOffset(const float aX, const float aY) {} + virtual void SetScrollingRootContent(const bool isRootContent) {} GeckoContentController() {} virtual void ChildAdopted() {} diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index 65b208019b..05319d452a 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -457,7 +457,7 @@ APZCTreeManager::PrepareNodeForLayer(const LayerMetricsWrapper& aLayer, APZCTM_LOG("Using APZC %p for layer %p with identifiers %" PRId64 " %" PRId64 "\n", apzc, aLayer.GetLayer(), aLayersId, aMetrics.GetScrollId()); - apzc->NotifyLayersUpdated(aMetrics, aState.mIsFirstPaint, + apzc->NotifyLayersUpdated(aLayer.Metadata(), aState.mIsFirstPaint, aLayersId == aState.mOriginatingLayersId); // Since this is the first time we are encountering an APZC with this guid, diff --git a/gfx/layers/apz/src/APZUtils.h b/gfx/layers/apz/src/APZUtils.h index 75565ca067..03a3ac2622 100644 --- a/gfx/layers/apz/src/APZUtils.h +++ b/gfx/layers/apz/src/APZUtils.h @@ -25,7 +25,7 @@ enum HitTestResult { enum CancelAnimationFlags : uint32_t { Default = 0x0, /* Cancel all animations */ ExcludeOverscroll = 0x1, /* Don't clear overscroll */ - RequestSnap = 0x2 /* Request snapping to snap points */ + ScrollSnap = 0x2 /* Snap to snap points */ }; inline CancelAnimationFlags diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 0795d0244d..5448aa1033 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -70,6 +70,7 @@ #include "nsThreadUtils.h" // for NS_IsMainThread #include "prsystem.h" // for PR_GetPhysicalMemorySize #include "SharedMemoryBasic.h" // for SharedMemoryBasic +#include "ScrollSnap.h" // for ScrollSnapUtils #include "WheelScrollAnimation.h" #define ENABLE_APZC_LOGGING 0 @@ -689,7 +690,13 @@ public: // snap point, so we request one now. If there are no snap points, this will // do nothing. If there are snap points, we'll get a scrollTo that snaps us // back to the nearest valid snap point. - mApzc.RequestSnap(); + // The scroll snapping is done in a deferred task, otherwise the state + // change to NOTHING caused by the overscroll animation ending would + // clobber a possible state change to SMOOTH_SCROLL in ScrollSnap(). + if (!mDeferredTasks.append(NewRunnableMethod(&mApzc, + &AsyncPanZoomController::ScrollSnap))) { + MOZ_CRASH(); + } return false; } return true; @@ -707,7 +714,6 @@ private: class SmoothScrollAnimation : public AsyncPanZoomAnimation { public: SmoothScrollAnimation(AsyncPanZoomController& aApzc, - ScrollSource aSource, const nsPoint &aInitialPosition, const nsPoint &aInitialVelocity, const nsPoint& aDestination, double aSpringConstant, @@ -858,6 +864,7 @@ AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId, // mTreeManager must be initialized before GetFrameTime() is called mTreeManager(aTreeManager), mSharingFrameMetricsAcrossProcesses(false), + mFrameMetrics(mScrollMetadata.GetMetrics()), mMonitor("AsyncPanZoomController"), mX(this), mY(this), @@ -922,7 +929,7 @@ AsyncPanZoomController::Destroy() { APZThreadUtils::AssertOnCompositorThread(); - CancelAnimation(CancelAnimationFlags::RequestSnap); + CancelAnimation(CancelAnimationFlags::ScrollSnap); { // scope the lock MonitorAutoLock lock(mRefPtrMonitor); @@ -1317,6 +1324,11 @@ nsEventStatus AsyncPanZoomController::OnTouchMove(const MultiTouchInput& aEvent) nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) { APZC_LOG("%p got a touch-end in state %d\n", this, mState); + RefPtr controller = GetGeckoContentController(); + if (controller) { + controller->SetScrollingRootContent(false); + } + OnTouchEndOrCancel(); // In case no touch behavior triggered previously we can avoid sending @@ -1543,6 +1555,20 @@ nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent { ReentrantMonitorAutoEnter lock(mMonitor); + ScheduleComposite(); + RequestContentRepaint(); + UpdateSharedCompositorFrameMetrics(); + } + + // Non-negative focus point would indicate that one finger is still down + if (aEvent.mFocusPoint.x != -1 && aEvent.mFocusPoint.y != -1) { + mPanDirRestricted = false; + mX.StartTouch(aEvent.mFocusPoint.x, aEvent.mTime); + mY.StartTouch(aEvent.mFocusPoint.y, aEvent.mTime); + SetState(TOUCHING); + } else { + // Otherwise, handle the fingers being lifted. + ReentrantMonitorAutoEnter lock(mMonitor); // We can get into a situation where we are overscrolled at the end of a // pinch if we go into overscroll with a two-finger pan, and then turn @@ -1558,21 +1584,8 @@ nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent ClearOverscroll(); } // Along with clearing the overscroll, we also want to snap to the nearest - // snap point as appropriate, so ask the main thread (which knows about such - // things) to handle it. - RequestSnap(); - - ScheduleComposite(); - RequestContentRepaint(); - UpdateSharedCompositorFrameMetrics(); - } - - // Non-negative focus point would indicate that one finger is still down - if (aEvent.mFocusPoint.x != -1 && aEvent.mFocusPoint.y != -1) { - mPanDirRestricted = false; - mX.StartTouch(aEvent.mFocusPoint.x, aEvent.mTime); - mY.StartTouch(aEvent.mFocusPoint.y, aEvent.mTime); - SetState(TOUCHING); + // snap point as appropriate. + ScrollSnap(); } return nsEventStatus_eConsumeNoDefault; @@ -1995,6 +2008,10 @@ nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) { SetState(NOTHING); RequestContentRepaint(); + if (!aEvent.mFollowedByMomentum) { + ScrollSnap(); + } + return nsEventStatus_eConsumeNoDefault; } @@ -2007,7 +2024,7 @@ nsEventStatus AsyncPanZoomController::OnPanMomentumStart(const PanGestureInput& } SetState(PAN_MOMENTUM); - RequestSnapToDestination(); + ScrollSnapToDestination(); // Call into OnPan in order to process any delta included in this event. OnPan(aEvent, false); @@ -2354,11 +2371,11 @@ bool AsyncPanZoomController::AttemptScroll(ParentLayerPoint& aStartPoint, // a later event in the block could potentially scroll an APZC earlier // in the handoff chain, than an earlier event in the block (because // the earlier APZC was scrolled to its extent in the original direction). - // If immediate handoff is disallowed, we want to disallow this (to - // preserve the property that a single input block only scrolls one APZC), - // so we skip the earlier APZC. - bool scrollThisApzc = gfxPrefs::APZAllowImmediateHandoff() || - (CurrentInputBlock() && (!CurrentInputBlock()->GetScrolledApzc() || this == CurrentInputBlock()->GetScrolledApzc())); + // We want to disallow this. + bool scrollThisApzc = false; + if (InputBlockState* block = CurrentInputBlock()) { + scrollThisApzc = !block->GetScrolledApzc() || block->IsDownchainOfScrolledApzc(this); + } if (scrollThisApzc) { ReentrantMonitorAutoEnter lock(mMonitor); @@ -2377,21 +2394,25 @@ bool AsyncPanZoomController::AttemptScroll(ParentLayerPoint& aStartPoint, if (!IsZero(adjustedDisplacement)) { ScrollBy(adjustedDisplacement / mFrameMetrics.GetZoom()); - if (!gfxPrefs::APZAllowImmediateHandoff()) { - if (InputBlockState* block = CurrentInputBlock()) { - block->SetScrolledApzc(this); + if (CancelableBlockState* block = CurrentInputBlock()) { + if (block->AsTouchBlock() && (block->GetScrolledApzc() != this)) { + RefPtr controller = GetGeckoContentController(); + if (controller) { + controller->SetScrollingRootContent(IsRootContent()); + } } + block->SetScrolledApzc(this); } ScheduleCompositeAndMaybeRepaint(); UpdateSharedCompositorFrameMetrics(); } + + // Adjust the start point to reflect the consumed portion of the scroll. + aStartPoint = aEndPoint + overscroll; } else { overscroll = displacement; } - // Adjust the start point to reflect the consumed portion of the scroll. - aStartPoint = aEndPoint + overscroll; - // If we consumed the entire displacement as a normal scroll, great. if (IsZero(overscroll)) { return true; @@ -2504,51 +2525,18 @@ void AsyncPanZoomController::AcceptFling(FlingHandoffState& aHandoffState) { mY.SetVelocity(mY.GetVelocity() + aHandoffState.mVelocity.y); aHandoffState.mVelocity.y = 0; } - SetState(FLING); - FlingAnimation *fling = new FlingAnimation(*this, - aHandoffState.mChain, - !aHandoffState.mIsHandoff, // only apply acceleration if this is an initial fling - aHandoffState.mScrolledApzc); - RequestSnapToDestination(); - StartAnimation(fling); -} -void -AsyncPanZoomController::RequestSnapToDestination() -{ - ReentrantMonitorAutoEnter lock(mMonitor); - - float friction = gfxPrefs::APZFlingFriction(); - ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity()); - ParentLayerPoint predictedDelta; - // "-velocity / log(1.0 - friction)" is the integral of the deceleration - // curve modeled for flings in the "Axis" class. - if (velocity.x != 0.0f) { - predictedDelta.x = -velocity.x / log(1.0 - friction); - } - if (velocity.y != 0.0f) { - predictedDelta.y = -velocity.y / log(1.0 - friction); - } - CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom(); - - // If the fling will overscroll, don't request a fling snap, because the - // resulting content scrollTo() would unnecessarily cancel the overscroll - // animation. - bool flingWillOverscroll = IsOverscrolled() && ((velocity.x * mX.GetOverscroll() >= 0) || - (velocity.y * mY.GetOverscroll() >= 0)); - if (!flingWillOverscroll) { - RefPtr controller = GetGeckoContentController(); - if (controller) { - APZC_LOG("%p fling snapping. friction: %f velocity: %f, %f " - "predictedDelta: %f, %f position: %f, %f " - "predictedDestination: %f, %f\n", - this, friction, velocity.x, velocity.y, (float)predictedDelta.x, - (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x, - (float)mFrameMetrics.GetScrollOffset().y, - (float)predictedDestination.x, (float)predictedDestination.y); - controller->RequestFlingSnap(mFrameMetrics.GetScrollId(), - predictedDestination); - } + // If there's a scroll snap point near the predicted fling destination, + // scroll there using a smooth scroll animation. Otherwise, start a + // fling animation. + ScrollSnapToDestination(); + if (mState != SMOOTH_SCROLL) { + SetState(FLING); + FlingAnimation *fling = new FlingAnimation(*this, + aHandoffState.mChain, + !aHandoffState.mIsHandoff, // only apply acceleration if this is an initial fling + aHandoffState.mScrolledApzc); + StartAnimation(fling); } } @@ -2591,21 +2579,28 @@ void AsyncPanZoomController::HandleSmoothScrollOverscroll(const ParentLayerPoint HandleFlingOverscroll(aVelocity, BuildOverscrollHandoffChain(), nullptr); } -void AsyncPanZoomController::StartSmoothScroll(ScrollSource aSource) { - SetState(SMOOTH_SCROLL); - nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset()); - // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to - // appunits/second - nsPoint initialVelocity = CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), - mY.GetVelocity())) * 1000.0f; - nsPoint destination = CSSPoint::ToAppUnits(mFrameMetrics.GetSmoothScrollOffset()); +void AsyncPanZoomController::SmoothScrollTo(const CSSPoint& aDestination) { + if (mState == SMOOTH_SCROLL && mAnimation) { + APZC_LOG("%p updating destination on existing animation\n", this); + RefPtr animation( + static_cast(mAnimation.get())); + animation->SetDestination(CSSPoint::ToAppUnits(aDestination)); + } else { + CancelAnimation(); + SetState(SMOOTH_SCROLL); + nsPoint initialPosition = CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset()); + // Cast velocity from ParentLayerPoints/ms to CSSPoints/ms then convert to + // appunits/second + nsPoint initialVelocity = CSSPoint::ToAppUnits(CSSPoint(mX.GetVelocity(), + mY.GetVelocity())) * 1000.0f; + nsPoint destination = CSSPoint::ToAppUnits(aDestination); - StartAnimation(new SmoothScrollAnimation(*this, - aSource, - initialPosition, initialVelocity, - destination, - gfxPrefs::ScrollBehaviorSpringConstant(), - gfxPrefs::ScrollBehaviorDampingRatio())); + StartAnimation(new SmoothScrollAnimation(*this, + initialPosition, initialVelocity, + destination, + gfxPrefs::ScrollBehaviorSpringConstant(), + gfxPrefs::ScrollBehaviorDampingRatio())); + } } void AsyncPanZoomController::StartOverscrollAnimation(const ParentLayerPoint& aVelocity) { @@ -2686,9 +2681,9 @@ void AsyncPanZoomController::CancelAnimation(CancelAnimationFlags aFlags) { repaint = true; } // Similar to relieving overscroll, we also need to snap to any snap points - // if appropriate, so ask the main thread to do that. - if (aFlags & CancelAnimationFlags::RequestSnap) { - RequestSnap(); + // if appropriate. + if (aFlags & CancelAnimationFlags::ScrollSnap) { + ScrollSnap(); } if (repaint) { RequestContentRepaint(); @@ -2885,7 +2880,7 @@ bool AsyncPanZoomController::SnapBackIfOverscrolled() { // main thread to snap to any nearby snap points, assuming we haven't already // done so when we started this fling if (mState != FLING) { - RequestSnap(); + ScrollSnap(); } return false; } @@ -3280,25 +3275,25 @@ bool AsyncPanZoomController::IsCurrentlyCheckerboarding() const { return true; } -void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, +void AsyncPanZoomController::NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata, bool aIsFirstPaint, bool aThisLayerTreeUpdated) { APZThreadUtils::AssertOnCompositorThread(); ReentrantMonitorAutoEnter lock(mMonitor); - bool isDefault = mFrameMetrics.IsDefault(); + bool isDefault = mScrollMetadata.IsDefault(); + + const FrameMetrics& aLayerMetrics = aScrollMetadata.GetMetrics(); if ((aLayerMetrics == mLastContentPaintMetrics) && !isDefault) { - // No new information here, skip it. Note that this is not just an - // optimization; it's correctness too. In the case where we get one of these - // stale aLayerMetrics *after* a call to NotifyScrollUpdated, processing the - // stale aLayerMetrics would clobber the more up-to-date information from - // NotifyScrollUpdated. + // No new information here, skip it. APZC_LOG("%p NotifyLayersUpdated short-circuit\n", this); return; } - mLastContentPaintMetrics = aLayerMetrics; + if (aLayerMetrics.GetScrollUpdateType() != FrameMetrics::ScrollOffsetUpdateType::ePending) { + mLastContentPaintMetrics = aLayerMetrics; + } mFrameMetrics.SetScrollParentId(aLayerMetrics.GetScrollParentId()); APZC_LOG_FM(aLayerMetrics, "%p got a NotifyLayersUpdated with aIsFirstPaint=%d, aThisLayerTreeUpdated=%d", @@ -3372,7 +3367,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri // that was just painted is something we knew nothing about previously CancelAnimation(); - mFrameMetrics = aLayerMetrics; + mScrollMetadata = aScrollMetadata; if (scrollOffsetUpdated) { AcknowledgeScrollUpdate(); } @@ -3428,11 +3423,13 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri mFrameMetrics.SetHasScrollgrab(aLayerMetrics.GetHasScrollgrab()); mFrameMetrics.SetLineScrollAmount(aLayerMetrics.GetLineScrollAmount()); mFrameMetrics.SetPageScrollAmount(aLayerMetrics.GetPageScrollAmount()); - mFrameMetrics.SetClipRect(aLayerMetrics.GetClipRect()); - mFrameMetrics.SetMaskLayerIndex(aLayerMetrics.GetMaskLayerIndex()); + mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo())); + mScrollMetadata.SetClipRect(aScrollMetadata.GetClipRect()); + mScrollMetadata.SetMaskLayerIndex(aScrollMetadata.GetMaskLayerIndex()); mFrameMetrics.SetIsLayersIdRoot(aLayerMetrics.IsLayersIdRoot()); mFrameMetrics.SetUsesContainerScrolling(aLayerMetrics.UsesContainerScrolling()); mFrameMetrics.SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer()); + mFrameMetrics.SetForceDisableApz(aLayerMetrics.IsApzForceDisabled()); if (scrollOffsetUpdated) { APZC_LOG("%p updating scroll offset from %s to %s\n", this, @@ -3482,16 +3479,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri AcknowledgeScrollUpdate(); mExpectedGeckoMetrics = aLayerMetrics; - if (mState == SMOOTH_SCROLL && mAnimation) { - APZC_LOG("%p updating destination on existing animation\n", this); - RefPtr animation( - static_cast(mAnimation.get())); - animation->SetDestination( - CSSPoint::ToAppUnits(aLayerMetrics.GetSmoothScrollOffset())); - } else { - CancelAnimation(); - StartSmoothScroll(ScrollSource::DOM); - } + SmoothScrollTo(mFrameMetrics.GetSmoothScrollOffset()); } if (needContentRepaint) { @@ -3500,35 +3488,6 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri UpdateSharedCompositorFrameMetrics(); } -void -AsyncPanZoomController::NotifyScrollUpdated(uint32_t aScrollGeneration, - const CSSPoint& aScrollOffset) -{ - APZThreadUtils::AssertOnCompositorThread(); - ReentrantMonitorAutoEnter lock(mMonitor); - - APZC_LOG("%p NotifyScrollUpdated(%d, %s)\n", this, aScrollGeneration, - Stringify(aScrollOffset).c_str()); - - bool scrollOffsetUpdated = aScrollGeneration != mFrameMetrics.GetScrollGeneration(); - if (!scrollOffsetUpdated) { - return; - } - APZC_LOG("%p updating scroll offset from %s to %s\n", this, - Stringify(mFrameMetrics.GetScrollOffset()).c_str(), - Stringify(aScrollOffset).c_str()); - - mFrameMetrics.UpdateScrollInfo(aScrollGeneration, aScrollOffset); - AcknowledgeScrollUpdate(); - mExpectedGeckoMetrics.UpdateScrollInfo(aScrollGeneration, aScrollOffset); - CancelAnimation(); - RequestContentRepaint(); - UpdateSharedCompositorFrameMetrics(); - // We don't call ScheduleComposite() here because that happens higher up - // in the call stack, when LayerTransactionParent handles this message. - // If we did it here it would incur an extra message posting unnecessarily. -} - void AsyncPanZoomController::AcknowledgeScrollUpdate() const { @@ -3733,7 +3692,7 @@ AsyncPanZoomController::CancelAnimationAndGestureState() { mX.CancelGesture(); mY.CancelGesture(); - CancelAnimation(CancelAnimationFlags::RequestSnap); + CancelAnimation(CancelAnimationFlags::ScrollSnap); } bool @@ -3910,12 +3869,62 @@ void AsyncPanZoomController::ShareCompositorFrameMetrics() { } } -void AsyncPanZoomController::RequestSnap() { - if (RefPtr controller = GetGeckoContentController()) { - APZC_LOG("%p requesting snap near %s\n", this, - Stringify(mFrameMetrics.GetScrollOffset()).c_str()); - controller->RequestFlingSnap(mFrameMetrics.GetScrollId(), - mFrameMetrics.GetScrollOffset()); +void AsyncPanZoomController::ScrollSnapNear(const CSSPoint& aDestination) { + mMonitor.AssertCurrentThreadIn(); + APZC_LOG("%p scroll snapping near %s\n", this, Stringify(aDestination).c_str()); + CSSRect scrollRange = mFrameMetrics.CalculateScrollRange(); + if (Maybe snapPoint = ScrollSnapUtils::GetSnapPointForDestination( + mScrollMetadata.GetSnapInfo(), + nsIScrollableFrame::DEVICE_PIXELS, + CSSSize::ToAppUnits(mFrameMetrics.CalculateCompositedSizeInCssPixels()), + CSSRect::ToAppUnits(scrollRange), + CSSPoint::ToAppUnits(mFrameMetrics.GetScrollOffset()), + CSSPoint::ToAppUnits(aDestination))) { + CSSPoint cssSnapPoint = CSSPoint::FromAppUnits(snapPoint.ref()); + // GetSnapPointForDestination() can produce a destination that's outside + // of the scroll frame's scroll range. Clamp it here (this matches the + // behaviour of the main-thread code path, which clamps it in + // nsGfxScrollFrame::ScrollTo()). + cssSnapPoint = scrollRange.ClampPoint(cssSnapPoint); + SmoothScrollTo(cssSnapPoint); + } +} + +void AsyncPanZoomController::ScrollSnap() { + ReentrantMonitorAutoEnter lock(mMonitor); + ScrollSnapNear(mFrameMetrics.GetScrollOffset()); +} + +void AsyncPanZoomController::ScrollSnapToDestination() { + ReentrantMonitorAutoEnter lock(mMonitor); + + float friction = gfxPrefs::APZFlingFriction(); + ParentLayerPoint velocity(mX.GetVelocity(), mY.GetVelocity()); + ParentLayerPoint predictedDelta; + // "-velocity / log(1.0 - friction)" is the integral of the deceleration + // curve modeled for flings in the "Axis" class. + if (velocity.x != 0.0f) { + predictedDelta.x = -velocity.x / log(1.0 - friction); + } + if (velocity.y != 0.0f) { + predictedDelta.y = -velocity.y / log(1.0 - friction); + } + CSSPoint predictedDestination = mFrameMetrics.GetScrollOffset() + predictedDelta / mFrameMetrics.GetZoom(); + + // If the fling will overscroll, don't scroll snap, because then the user + // user would not see any overscroll animation. + bool flingWillOverscroll = IsOverscrolled() && ((velocity.x * mX.GetOverscroll() >= 0) || + (velocity.y * mY.GetOverscroll() >= 0)); + if (!flingWillOverscroll) { + APZC_LOG("%p fling snapping. friction: %f velocity: %f, %f " + "predictedDelta: %f, %f position: %f, %f " + "predictedDestination: %f, %f\n", + this, friction, velocity.x, velocity.y, (float)predictedDelta.x, + (float)predictedDelta.y, (float)mFrameMetrics.GetScrollOffset().x, + (float)mFrameMetrics.GetScrollOffset().y, + (float)predictedDestination.x, (float)predictedDestination.y); + + ScrollSnapNear(predictedDestination); } } diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index aa86d188f5..eec8e971aa 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -178,22 +178,15 @@ public: AsyncTransformComponentMatrix GetOverscrollTransform() const; /** - * A shadow layer update has arrived. |aLayerMetrics| is the new FrameMetrics + * A shadow layer update has arrived. |aScrollMetdata| is the new ScrollMetadata * for the container layer corresponding to this APZC. * |aIsFirstPaint| is a flag passed from the shadow - * layers code indicating that the frame metrics being sent with this call are - * the initial metrics and the initial paint of the frame has just happened. + * layers code indicating that the scroll metadata being sent with this call are + * the initial metadata and the initial paint of the frame has just happened. */ - void NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint, + void NotifyLayersUpdated(const ScrollMetadata& aScrollMetadata, bool aIsFirstPaint, bool aThisLayerTreeUpdated); - /** - * A lightweight version of NotifyLayersUpdated that allows just the scroll - * offset and scroll generation from the main thread to be propagated to APZ. - */ - void NotifyScrollUpdated(uint32_t aScrollGeneration, - const CSSPoint& aScrollOffset); - /** * The platform implementation must set the compositor parent so that we can * request composites. @@ -643,13 +636,14 @@ protected: // Common processing at the end of a touch block. void OnTouchEndOrCancel(); - // This is called to request that the main thread snap the scroll position - // to a nearby snap position if appropriate. The current scroll position is - // used as the final destination. - void RequestSnap(); - // Same as above, but takes into account the current velocity to find a - // predicted destination. - void RequestSnapToDestination(); + // Snap to a snap position nearby the current scroll position, if appropriate. + void ScrollSnap(); + // Snap to a snap position nearby the destination predicted based on the + // current velocity, if appropriate. + void ScrollSnapToDestination(); + + // Helper function for ScrollSnap() and ScrollSnapToDestination(). + void ScrollSnapNear(const CSSPoint& aDestination); uint64_t mLayersId; RefPtr mCompositorBridgeParent; @@ -681,7 +675,8 @@ protected: protected: // Both |mFrameMetrics| and |mLastContentPaintMetrics| are protected by the // monitor. Do not read from or modify either of them without locking. - FrameMetrics mFrameMetrics; + ScrollMetadata mScrollMetadata; + FrameMetrics& mFrameMetrics; // for convenience, refers to mScrollMetadata.mMetrics // Protects |mFrameMetrics|, |mLastContentPaintMetrics|, and |mState|. // Before manipulating |mFrameMetrics| or |mLastContentPaintMetrics|, the @@ -878,7 +873,7 @@ private: // Start an overscroll animation with the given initial velocity. void StartOverscrollAnimation(const ParentLayerPoint& aVelocity); - void StartSmoothScroll(ScrollSource aSource); + void SmoothScrollTo(const CSSPoint& aDestination); // Returns whether overscroll is allowed during an event. bool AllowScrollHandoffInCurrentBlock() const; diff --git a/gfx/layers/apz/src/InputBlockState.cpp b/gfx/layers/apz/src/InputBlockState.cpp index b1552c7113..0b23ebb337 100644 --- a/gfx/layers/apz/src/InputBlockState.cpp +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -88,7 +88,7 @@ InputBlockState::IsTargetConfirmed() const } bool -InputBlockState::IsAncestorOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB) +InputBlockState::IsDownchainOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB) const { if (aA == aB) { return true; @@ -112,7 +112,7 @@ void InputBlockState::SetScrolledApzc(AsyncPanZoomController* aApzc) { // An input block should only have one scrolled APZC. - MOZ_ASSERT(!mScrolledApzc || mScrolledApzc == aApzc); + MOZ_ASSERT(!mScrolledApzc || (gfxPrefs::APZAllowImmediateHandoff() ? IsDownchainOf(mScrolledApzc, aApzc) : mScrolledApzc == aApzc)); mScrolledApzc = aApzc; } @@ -123,6 +123,14 @@ InputBlockState::GetScrolledApzc() const return mScrolledApzc; } +bool +InputBlockState::IsDownchainOfScrolledApzc(AsyncPanZoomController* aApzc) const +{ + MOZ_ASSERT(aApzc && mScrolledApzc); + + return IsDownchainOf(mScrolledApzc, aApzc); +} + CancelableBlockState::CancelableBlockState(const RefPtr& aTargetApzc, bool aTargetConfirmed) : InputBlockState(aTargetApzc, aTargetConfirmed) diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h index d962c1426f..4648dfecb0 100644 --- a/gfx/layers/apz/src/InputBlockState.h +++ b/gfx/layers/apz/src/InputBlockState.h @@ -52,6 +52,7 @@ public: void SetScrolledApzc(AsyncPanZoomController* aApzc); AsyncPanZoomController* GetScrolledApzc() const; + bool IsDownchainOfScrolledApzc(AsyncPanZoomController* aApzc) const; protected: virtual void UpdateTargetApzc(const RefPtr& aTargetApzc); @@ -59,7 +60,7 @@ protected: private: // Checks whether |aA| is an ancestor of |aB| (or the same as |aB|) in // |mOverscrollHandoffChain|. - bool IsAncestorOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB); + bool IsDownchainOf(AsyncPanZoomController* aA, AsyncPanZoomController* aB) const; private: RefPtr mTargetApzc; diff --git a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h index f21be66bf5..4a51ed4dbd 100644 --- a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h +++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h @@ -13,11 +13,13 @@ */ #include "APZTestCommon.h" +#include "gfxPlatform.h" class APZCTreeManagerTester : public ::testing::Test { protected: virtual void SetUp() { gfxPrefs::GetSingleton(); + gfxPlatform::GetPlatform(); APZThreadUtils::SetThreadAssertionsEnabled(false); APZThreadUtils::SetControllerThread(MessageLoop::current()); @@ -57,7 +59,8 @@ protected: protected: static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) { - FrameMetrics metrics; + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); metrics.SetScrollId(aScrollId); // By convention in this test file, START_SCROLL_ID is the root, so mark it as such. if (aScrollId == FrameMetrics::START_SCROLL_ID) { @@ -70,7 +73,7 @@ protected: metrics.SetScrollOffset(CSSPoint(0, 0)); metrics.SetPageScrollAmount(LayoutDeviceIntSize(50, 100)); metrics.SetAllowVerticalScrollWithWheel(true); - aLayer->SetFrameMetrics(metrics); + aLayer->SetScrollMetadata(metadata); aLayer->SetClipRect(Some(ViewAs(layerBound))); if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) { // The purpose of this is to roughly mimic what layout would do in the @@ -84,13 +87,13 @@ protected: } void SetScrollHandoff(Layer* aChild, Layer* aParent) { - FrameMetrics metrics = aChild->GetFrameMetrics(0); - metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId()); - aChild->SetFrameMetrics(metrics); + ScrollMetadata metadata = aChild->GetScrollMetadata(0); + metadata.GetMetrics().SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId()); + aChild->SetScrollMetadata(metadata); } static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) { - EXPECT_EQ(1u, aLayer->GetFrameMetricsCount()); + EXPECT_EQ(1u, aLayer->GetScrollMetadataCount()); return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0); } @@ -151,9 +154,9 @@ protected: SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1); // Make layers[1] the root content - FrameMetrics childMetrics = layers[1]->GetFrameMetrics(0); - childMetrics.SetIsRootContent(true); - layers[1]->SetFrameMetrics(childMetrics); + ScrollMetadata childMetadata = layers[1]->GetScrollMetadata(0); + childMetadata.GetMetrics().SetIsRootContent(true); + layers[1]->SetScrollMetadata(childMetadata); // Both layers are fully dispatch-to-content EventRegions regions; diff --git a/gfx/layers/apz/test/gtest/TestBasic.cpp b/gfx/layers/apz/test/gtest/TestBasic.cpp index 1bc9fc9fc1..1ee82d0a44 100644 --- a/gfx/layers/apz/test/gtest/TestBasic.cpp +++ b/gfx/layers/apz/test/gtest/TestBasic.cpp @@ -78,7 +78,8 @@ TEST_F(APZCBasicTester, ComplexTransform) { RefPtr lm; RefPtr root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers); - FrameMetrics metrics; + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24)); metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6)); metrics.SetScrollOffset(CSSPoint(10, 10)); @@ -89,11 +90,12 @@ TEST_F(APZCBasicTester, ComplexTransform) { metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3)); metrics.SetScrollId(FrameMetrics::START_SCROLL_ID); - FrameMetrics childMetrics = metrics; + ScrollMetadata childMetadata = metadata; + FrameMetrics& childMetrics = childMetadata.GetMetrics(); childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1); - layers[0]->SetFrameMetrics(metrics); - layers[1]->SetFrameMetrics(childMetrics); + layers[0]->SetScrollMetadata(metadata); + layers[1]->SetScrollMetadata(childMetadata); ParentLayerPoint pointOut; AsyncTransform viewTransformOut; @@ -103,13 +105,13 @@ TEST_F(APZCBasicTester, ComplexTransform) { // initial transform apzc->SetFrameMetrics(metrics); - apzc->NotifyLayersUpdated(metrics, true, true); + apzc->NotifyLayersUpdated(metadata, true, true); apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut); EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); childApzc->SetFrameMetrics(childMetrics); - childApzc->NotifyLayersUpdated(childMetrics, true, true); + childApzc->NotifyLayersUpdated(childMetadata, true, true); childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut); EXPECT_EQ(ParentLayerPoint(60, 60), pointOut); diff --git a/gfx/layers/apz/util/APZEventState.cpp b/gfx/layers/apz/util/APZEventState.cpp index ff02aee487..3b0022ab36 100644 --- a/gfx/layers/apz/util/APZEventState.cpp +++ b/gfx/layers/apz/util/APZEventState.cpp @@ -338,8 +338,7 @@ APZEventState::ProcessWheelEvent(const WidgetWheelEvent& aEvent, { // If this event starts a swipe, indicate that it shouldn't result in a // scroll by setting defaultPrevented to true. - bool defaultPrevented = - aEvent.mFlags.mDefaultPrevented || aEvent.TriggersSwipe(); + bool defaultPrevented = aEvent.DefaultPrevented() || aEvent.TriggersSwipe(); mContentReceivedInputBlockCallback(aGuid, aInputBlockId, defaultPrevented); } diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index e7226efd14..51eed0891e 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -330,7 +330,7 @@ IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, gfxFloat aMax) static LayerMetricsWrapper FindMetricsWithScrollId(Layer* aLayer, FrameMetrics::ViewID aScrollId) { - for (uint64_t i = 0; i < aLayer->GetFrameMetricsCount(); ++i) { + for (uint64_t i = 0; i < aLayer->GetScrollMetadataCount(); ++i) { if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollId) { return LayerMetricsWrapper(aLayer, i); } @@ -686,7 +686,7 @@ AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer) RecordShadowTransforms(child); } - for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) { + for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) { AsyncPanZoomController* apzc = aLayer->GetAsyncPanZoomController(i); if (!apzc) { continue; @@ -836,7 +836,7 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, // of all scroll frames inside the current one. nsTArray ancestorMaskLayers; - for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) { + for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) { AsyncPanZoomController* controller = aLayer->GetAsyncPanZoomController(i); if (!controller) { continue; @@ -857,7 +857,8 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, controller->MarkAsyncTransformAppliedToContent(); } - const FrameMetrics& metrics = aLayer->GetFrameMetrics(i); + const ScrollMetadata& scrollMetadata = aLayer->GetScrollMetadata(i); + const FrameMetrics& metrics = scrollMetadata.GetMetrics(); #if defined(MOZ_ANDROID_APZ) // If we find a metrics which is the root content doc, use that. If not, use @@ -869,7 +870,7 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, if (!(*aOutFoundRoot)) { *aOutFoundRoot = metrics.IsRootContent() || /* RCD */ (aLayer->GetParent() == nullptr && /* rootmost metrics */ - i + 1 >= aLayer->GetFrameMetricsCount()); + i + 1 >= aLayer->GetScrollMetadataCount()); if (*aOutFoundRoot) { mRootScrollableId = metrics.GetScrollId(); CSSToLayerScale geckoZoom = metrics.LayersPixelsPerCSSPixel().ToScaleFactor(); @@ -938,8 +939,8 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, // Combine the local clip with the ancestor scrollframe clip. This is not // included in the async transform above, since the ancestor clip should not // move with this APZC. - if (metrics.HasClipRect()) { - ParentLayerIntRect clip = metrics.ClipRect(); + if (scrollMetadata.HasClipRect()) { + ParentLayerIntRect clip = scrollMetadata.ClipRect(); if (aLayer->GetParent() && aLayer->GetParent()->GetTransformIsPerspective()) { // If our parent layer has a perspective transform, we want to apply // our scroll clip to it instead of to this layer (see bug 1168263). @@ -961,8 +962,8 @@ AsyncCompositionManager::ApplyAsyncContentTransformToTree(Layer *aLayer, } // Append the ancestor mask layer for this scroll frame to ancestorMaskLayers. - if (metrics.GetMaskLayerIndex()) { - size_t maskLayerIndex = metrics.GetMaskLayerIndex().value(); + if (scrollMetadata.GetMaskLayerIndex()) { + size_t maskLayerIndex = scrollMetadata.GetMaskLayerIndex().value(); Layer* ancestorMaskLayer = aLayer->GetAncestorMaskLayerAt(maskLayerIndex); ancestorMaskLayers.AppendElement(ancestorMaskLayer); } diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp index 8924a347c2..c7d0362272 100755 --- a/gfx/layers/composite/ContainerLayerComposite.cpp +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -343,7 +343,8 @@ ContainerRenderVR(ContainerT* aContainer, static bool NeedToDrawCheckerboardingForLayer(Layer* aLayer, Color* aOutCheckerboardingColor) { - return (aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) && + return (aLayer->Manager()->AsyncPanZoomEnabled() && + aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) && aLayer->IsOpaqueForVisibility() && LayerHasCheckerboardingAPZC(aLayer, aOutCheckerboardingColor); } @@ -462,7 +463,7 @@ RenderMinimap(ContainerT* aContainer, LayerManagerComposite* aManager, { Compositor* compositor = aManager->GetCompositor(); - if (aLayer->GetFrameMetricsCount() < 1) { + if (aLayer->GetScrollMetadataCount() < 1) { return; } @@ -648,7 +649,7 @@ RenderLayers(ContainerT* aContainer, // frames higher up, so loop from the top down, and accumulate an async // transform as we go along. Matrix4x4 asyncTransform; - for (uint32_t i = layer->GetFrameMetricsCount(); i > 0; --i) { + for (uint32_t i = layer->GetScrollMetadataCount(); i > 0; --i) { if (layer->GetFrameMetrics(i - 1).IsScrollable()) { // Since the composition bounds are in the parent layer's coordinates, // use the parent's effective transform rather than the layer's own. diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp index e79a8b06a9..9ba742dcc0 100644 --- a/gfx/layers/ipc/LayerTransactionParent.cpp +++ b/gfx/layers/ipc/LayerTransactionParent.cpp @@ -366,7 +366,7 @@ LayerTransactionParent::RecvUpdate(InfallibleTArray&& cset, layer->SetMaskLayer(nullptr); } layer->SetAnimations(common.animations()); - layer->SetFrameMetrics(common.metrics()); + layer->SetScrollMetadata(common.scrollMetadata()); layer->SetDisplayListLog(common.displayListLog().get()); // The updated invalid region is added to the existing one, since we can @@ -781,7 +781,7 @@ LayerTransactionParent::RecvGetAnimationTransform(PLayerParent* aParent, static AsyncPanZoomController* GetAPZCForViewID(Layer* aLayer, FrameMetrics::ViewID aScrollID) { - for (uint32_t i = 0; i < aLayer->GetFrameMetricsCount(); i++) { + for (uint32_t i = 0; i < aLayer->GetScrollMetadataCount(); i++) { if (aLayer->GetFrameMetrics(i).GetScrollId() == aScrollID) { return aLayer->GetAsyncPanZoomController(i); } @@ -798,25 +798,6 @@ GetAPZCForViewID(Layer* aLayer, FrameMetrics::ViewID aScrollID) return nullptr; } -bool -LayerTransactionParent::RecvUpdateScrollOffset( - const FrameMetrics::ViewID& aScrollID, - const uint32_t& aScrollGeneration, - const CSSPoint& aScrollOffset) -{ - if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) { - return false; - } - - AsyncPanZoomController* controller = GetAPZCForViewID(mRoot, aScrollID); - if (!controller) { - return false; - } - controller->NotifyScrollUpdated(aScrollGeneration, aScrollOffset); - mShadowLayersManager->ForceComposite(this); - return true; -} - bool LayerTransactionParent::RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aScrollID, const float& aX, const float& aY) diff --git a/gfx/layers/ipc/LayerTransactionParent.h b/gfx/layers/ipc/LayerTransactionParent.h index 215a7967f3..24b504980b 100644 --- a/gfx/layers/ipc/LayerTransactionParent.h +++ b/gfx/layers/ipc/LayerTransactionParent.h @@ -130,9 +130,6 @@ protected: virtual bool RecvGetAnimationTransform(PLayerParent* aParent, MaybeTransform* aTransform) override; - virtual bool RecvUpdateScrollOffset(const FrameMetrics::ViewID& aScrollId, - const uint32_t& aScrollGeneration, - const CSSPoint& aScrollOffset) override; virtual bool RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aId, const float& aX, const float& aY) override; virtual bool RecvSetAsyncZoom(const FrameMetrics::ViewID& aId, diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh index 040858a596..424562af91 100644 --- a/gfx/layers/ipc/LayersMessages.ipdlh +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -41,7 +41,7 @@ using mozilla::layers::ScaleMode from "mozilla/layers/LayersTypes.h"; using mozilla::layers::EventRegions from "mozilla/layers/LayersTypes.h"; using mozilla::layers::EventRegionsOverride from "mozilla/layers/LayersTypes.h"; using mozilla::layers::DiagnosticTypes from "mozilla/layers/CompositorTypes.h"; -using struct mozilla::layers::FrameMetrics from "FrameMetrics.h"; +using struct mozilla::layers::ScrollMetadata from "FrameMetrics.h"; using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h"; using struct mozilla::layers::FenceHandle from "mozilla/layers/FenceUtils.h"; using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; @@ -237,7 +237,7 @@ struct CommonLayerAttributes { // Animated colors will only honored for ColorLayers. Animation[] animations; nsIntRegion invalidRegion; - FrameMetrics[] metrics; + ScrollMetadata[] scrollMetadata; nsCString displayListLog; }; diff --git a/gfx/layers/ipc/PLayerTransaction.ipdl b/gfx/layers/ipc/PLayerTransaction.ipdl index a0d51f73fa..d99febd3a1 100644 --- a/gfx/layers/ipc/PLayerTransaction.ipdl +++ b/gfx/layers/ipc/PLayerTransaction.ipdl @@ -22,7 +22,6 @@ using class mozilla::layers::APZTestData from "mozilla/layers/APZTestData.h"; using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h"; using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h"; using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; -using mozilla::CSSPoint from "Units.h"; /** * The layers protocol is spoken between thread contexts that manage @@ -90,10 +89,6 @@ parent: // be void_t. sync GetAnimationTransform(PLayer layer) returns (MaybeTransform transform); - // Updates the scroll offset and generation counter on the APZC for the - // given scroll id. - sync UpdateScrollOffset(ViewID id, uint32_t generation, CSSPoint offset); - // The next time the layer tree is composited, add this async scroll offset in // CSS pixels for the given ViewID. // Useful for testing rendering of async scrolling. diff --git a/gfx/layers/ipc/ShadowLayers.cpp b/gfx/layers/ipc/ShadowLayers.cpp index 6487ca2224..b2d4546071 100644 --- a/gfx/layers/ipc/ShadowLayers.cpp +++ b/gfx/layers/ipc/ShadowLayers.cpp @@ -836,7 +836,7 @@ ShadowLayerForwarder::EndTransaction(InfallibleTArray* aReplies, common.maskLayerParent() = nullptr; common.animations() = mutant->GetAnimations(); common.invalidRegion() = mutant->GetInvalidRegion(); - common.metrics() = mutant->GetAllFrameMetrics(); + common.scrollMetadata() = mutant->GetAllScrollMetadata(); for (size_t i = 0; i < mutant->GetAncestorMaskLayerCount(); i++) { auto layer = Shadow(mutant->GetAncestorMaskLayerAt(i)->AsShadowableLayer()); common.ancestorMaskLayersChild().AppendElement(layer); diff --git a/gfx/src/DriverCrashGuard.cpp b/gfx/src/DriverCrashGuard.cpp index 6609d59301..e5a9eb3cc8 100644 --- a/gfx/src/DriverCrashGuard.cpp +++ b/gfx/src/DriverCrashGuard.cpp @@ -298,7 +298,8 @@ DriverCrashGuard::FeatureEnabled(int aFeature, bool aDefault) return aDefault; } int32_t status; - if (!NS_SUCCEEDED(mGfxInfo->GetFeatureStatus(aFeature, &status))) { + nsCString discardFailureId; + if (!NS_SUCCEEDED(mGfxInfo->GetFeatureStatus(aFeature, discardFailureId, &status))) { return false; } return status == nsIGfxInfo::FEATURE_STATUS_OK; diff --git a/gfx/src/gfxCrashReporterUtils.cpp b/gfx/src/gfxCrashReporterUtils.cpp index da002c35bb..650488f82f 100644 --- a/gfx/src/gfxCrashReporterUtils.cpp +++ b/gfx/src/gfxCrashReporterUtils.cpp @@ -83,7 +83,7 @@ public: } }; -class AppendAppNotesRunnable : public nsCancelableRunnable { +class AppendAppNotesRunnable : public CancelableRunnable { public: explicit AppendAppNotesRunnable(const nsACString& aFeatureStr) : mFeatureString(aFeatureStr) diff --git a/gfx/tests/gtest/TestLayers.cpp b/gfx/tests/gtest/TestLayers.cpp index 1dddcf3b2c..c14bc0bfb1 100644 --- a/gfx/tests/gtest/TestLayers.cpp +++ b/gfx/tests/gtest/TestLayers.cpp @@ -351,7 +351,15 @@ TEST(Layers, RepositionChild) { ASSERT_EQ(nullptr, layers[1]->GetNextSibling()); } -TEST(LayerMetricsWrapper, SimpleTree) { +class LayerMetricsWrapperTester : public ::testing::Test { +protected: + virtual void SetUp() { + // This ensures ScrollMetadata::sNullMetadata is initialized. + gfxPlatform::GetPlatform(); + } +}; + +TEST_F(LayerMetricsWrapperTester, SimpleTree) { nsTArray > layers; RefPtr lm; RefPtr root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers); @@ -388,43 +396,43 @@ TEST(LayerMetricsWrapper, SimpleTree) { ASSERT_TRUE(rootWrapper == wrapper.GetParent()); } -static FrameMetrics -MakeMetrics(FrameMetrics::ViewID aId) { - FrameMetrics metrics; - metrics.SetScrollId(aId); - return metrics; +static ScrollMetadata +MakeMetadata(FrameMetrics::ViewID aId) { + ScrollMetadata metadata; + metadata.GetMetrics().SetScrollId(aId); + return metadata; } -TEST(LayerMetricsWrapper, MultiFramemetricsTree) { +TEST_F(LayerMetricsWrapperTester, MultiFramemetricsTree) { nsTArray > layers; RefPtr lm; RefPtr root = CreateLayerTree("c(c(c(tt)c(t)))", nullptr, nullptr, lm, layers); - nsTArray metrics; - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 0)); // topmost of root layer - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID)); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 1)); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 2)); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID)); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID)); // bottom of root layer - root->SetFrameMetrics(metrics); + nsTArray metadata; + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 0)); // topmost of root layer + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID)); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 1)); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 2)); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID)); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID)); // bottom of root layer + root->SetScrollMetadata(metadata); - metrics.Clear(); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 3)); - layers[1]->SetFrameMetrics(metrics); + metadata.Clear(); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 3)); + layers[1]->SetScrollMetadata(metadata); - metrics.Clear(); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::NULL_SCROLL_ID)); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 4)); - layers[2]->SetFrameMetrics(metrics); + metadata.Clear(); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::NULL_SCROLL_ID)); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 4)); + layers[2]->SetScrollMetadata(metadata); - metrics.Clear(); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 5)); - layers[4]->SetFrameMetrics(metrics); + metadata.Clear(); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 5)); + layers[4]->SetScrollMetadata(metadata); - metrics.Clear(); - metrics.InsertElementAt(0, MakeMetrics(FrameMetrics::START_SCROLL_ID + 6)); - layers[5]->SetFrameMetrics(metrics); + metadata.Clear(); + metadata.InsertElementAt(0, MakeMetadata(FrameMetrics::START_SCROLL_ID + 6)); + layers[5]->SetScrollMetadata(metadata); LayerMetricsWrapper wrapper(root, LayerMetricsWrapper::StartAt::TOP); nsTArray expectedLayers; diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 1b6536526d..7ec1bab6e8 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -9,6 +9,7 @@ #include "mozilla/layers/ImageBridgeChild.h" #include "mozilla/layers/SharedBufferManagerChild.h" #include "mozilla/layers/ISurfaceAllocator.h" // for GfxMemoryImageReporter +#include "mozilla/ClearOnShutdown.h" #include "mozilla/Telemetry.h" #include "mozilla/TimeStamp.h" @@ -67,6 +68,7 @@ #include "nsILocaleService.h" #include "nsIObserverService.h" #include "nsIScreenManager.h" +#include "FrameMetrics.h" #include "MainThreadUtils.h" #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" @@ -757,6 +759,9 @@ gfxPlatform::Init() SkGraphics::SetFontCacheLimit(skiaCacheSize); } #endif + + ScrollMetadata::sNullMetadata = new ScrollMetadata(); + ClearOnShutdown(&ScrollMetadata::sNullMetadata); } static bool sLayersIPCIsUp = false; @@ -1254,8 +1259,10 @@ bool gfxPlatform::UseAcceleratedCanvas() if (mPreferredCanvasBackend == BackendType::SKIA && gfxPrefs::CanvasAzureAccelerated()) { nsCOMPtr gfxInfo = do_GetService("@mozilla.org/gfx/info;1"); int32_t status; + nsCString discardFailureId; return !gfxInfo || (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION, + discardFailureId, &status)) && status == nsIGfxInfo::FEATURE_STATUS_OK); } @@ -2054,22 +2061,20 @@ InitLayersAccelerationPrefs() sPrefBrowserTabsRemoteAutostart = BrowserTabsRemoteAutostart(); nsCOMPtr gfxInfo = services::GetGfxInfo(); + nsCString discardFailureId; int32_t status; #ifdef XP_WIN if (gfxPrefs::LayersAccelerationForceEnabled()) { sLayersSupportsD3D9 = true; sLayersSupportsD3D11 = true; - } else if (gfxInfo) { - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, &status))) { + } else if (!gfxPrefs::LayersAccelerationDisabled() && gfxInfo) { + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, discardFailureId, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK) { - if (sPrefBrowserTabsRemoteAutostart && !IsVistaOrLater()) { - gfxWarning() << "Disallowing D3D9 on Windows XP with E10S - see bug 1237770"; - } else { - sLayersSupportsD3D9 = true; - } + MOZ_ASSERT(!sPrefBrowserTabsRemoteAutostart || IsVistaOrLater()); + sLayersSupportsD3D9 = true; } } - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) { + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, discardFailureId, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK) { sLayersSupportsD3D11 = true; } @@ -2078,7 +2083,7 @@ InitLayersAccelerationPrefs() // Always support D3D11 when WARP is allowed. sLayersSupportsD3D11 = true; } - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, &status))) { + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_ANGLE, discardFailureId, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK) { gANGLESupportsD3D11 = true; } @@ -2091,7 +2096,7 @@ InitLayersAccelerationPrefs() Preferences::GetBool("media.windows-media-foundation.use-dxva", true) && #endif NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING, - &status))) { + discardFailureId, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK || gfxPrefs::HardwareVideoDecodingForceEnabled()) { sLayersSupportsHardwareVideoDecoding = true; } @@ -2368,7 +2373,8 @@ AllowOpenGL(bool* aWhitelisted) gfxInfo->GetData(); int32_t status; - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_OPENGL_LAYERS, &status))) { + nsCString discardFailureId; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_OPENGL_LAYERS, discardFailureId, &status))) { if (status == nsIGfxInfo::FEATURE_STATUS_OK) { *aWhitelisted = true; return true; diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 277837f774..8203d627b9 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -393,6 +393,7 @@ private: DECL_GFX_PREF(Live, "layout.css.scroll-behavior.spring-constant", ScrollBehaviorSpringConstant, float, 250.0f); DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-max-velocity", ScrollSnapPredictionMaxVelocity, int32_t, 2000); DECL_GFX_PREF(Live, "layout.css.scroll-snap.prediction-sensitivity", ScrollSnapPredictionSensitivity, float, 0.750f); + DECL_GFX_PREF(Live, "layout.css.scroll-snap.proximity-threshold", ScrollSnapProximityThreshold, int32_t, 200); DECL_GFX_PREF(Once, "layout.css.touch_action.enabled", TouchActionEnabled, bool, false); DECL_GFX_PREF(Live, "layout.display-list.dump", LayoutDumpDisplayList, bool, false); DECL_GFX_PREF(Live, "layout.event-regions.enabled", LayoutEventRegionsEnabledDoNotUseDirectly, bool, false); diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index 602a6838bd..34a738d67e 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -1495,11 +1495,13 @@ public: GetFeatureStatusRunnable(dom::workers::WorkerPrivate* workerPrivate, const nsCOMPtr& gfxInfo, int32_t feature, + nsACString& failureId, int32_t* status) : WorkerMainThreadRunnable(workerPrivate) , mGfxInfo(gfxInfo) , mFeature(feature) , mStatus(status) + , mFailureId(failureId) , mNSResult(NS_OK) { } @@ -1507,7 +1509,7 @@ public: bool MainThreadRun() override { if (mGfxInfo) { - mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mStatus); + mNSResult = mGfxInfo->GetFeatureStatus(mFeature, mFailureId, mStatus); } return true; } @@ -1524,18 +1526,22 @@ private: nsCOMPtr mGfxInfo; int32_t mFeature; int32_t* mStatus; + nsACString& mFailureId; nsresult mNSResult; }; /* static */ nsresult gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr& gfxInfo, - int32_t feature, int32_t* status) + int32_t feature, nsACString& failureId, + int32_t* status) { if (!NS_IsMainThread()) { dom::workers::WorkerPrivate* workerPrivate = dom::workers::GetCurrentThreadWorkerPrivate(); + RefPtr runnable = - new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, status); + new GetFeatureStatusRunnable(workerPrivate, gfxInfo, feature, failureId, + status); ErrorResult rv; runnable->Dispatch(rv); @@ -1549,7 +1555,7 @@ gfxUtils::ThreadSafeGetFeatureStatus(const nsCOMPtr& gfxInfo, return runnable->GetNSResult(); } - return gfxInfo->GetFeatureStatus(feature, status); + return gfxInfo->GetFeatureStatus(feature, failureId, status); } /* static */ bool diff --git a/gfx/thebes/gfxUtils.h b/gfx/thebes/gfxUtils.h index 3665e41f39..2beb946f2a 100644 --- a/gfx/thebes/gfxUtils.h +++ b/gfx/thebes/gfxUtils.h @@ -291,6 +291,7 @@ public: static nsresult ThreadSafeGetFeatureStatus(const nsCOMPtr& gfxInfo, int32_t feature, + nsACString& failureId, int32_t* status); /** diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index eb8893c652..031a2f3e72 100644 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -1985,7 +1985,8 @@ gfxWindowsPlatform::CheckD3D11Support(bool* aCanUseHardware) if (nsCOMPtr gfxInfo = services::GetGfxInfo()) { int32_t status; - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, &status))) { + nsCString discardFailureId; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, discardFailureId, &status))) { if (status != nsIGfxInfo::FEATURE_STATUS_OK) { if (CanUseWARP()) { *aCanUseHardware = false; @@ -2420,7 +2421,8 @@ IsD2DBlacklisted() nsCOMPtr gfxInfo = services::GetGfxInfo(); if (gfxInfo) { int32_t status; - if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT2D, &status))) { + nsCString discardFailureId; + if (NS_SUCCEEDED(gfxInfo->GetFeatureStatus(nsIGfxInfo::FEATURE_DIRECT2D, discardFailureId, &status))) { if (status != nsIGfxInfo::FEATURE_STATUS_OK) { return true; } diff --git a/ipc/glue/BackgroundChildImpl.cpp b/ipc/glue/BackgroundChildImpl.cpp index 81f96c3844..b02e1b711d 100644 --- a/ipc/glue/BackgroundChildImpl.cpp +++ b/ipc/glue/BackgroundChildImpl.cpp @@ -14,6 +14,8 @@ #include "mozilla/media/MediaChild.h" #include "mozilla/Assertions.h" #include "mozilla/dom/PBlobChild.h" +#include "mozilla/dom/PFileSystemRequestChild.h" +#include "mozilla/dom/FileSystemTaskBase.h" #include "mozilla/dom/asmjscache/AsmJSCache.h" #include "mozilla/dom/cache/ActorUtils.h" #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryChild.h" @@ -415,6 +417,23 @@ BackgroundChildImpl::DeallocPAsmJSCacheEntryChild(PAsmJSCacheEntryChild* aActor) return true; } +dom::PFileSystemRequestChild* +BackgroundChildImpl::AllocPFileSystemRequestChild(const FileSystemParams& aParams) +{ + MOZ_CRASH("Should never get here!"); + return nullptr; +} + +bool +BackgroundChildImpl::DeallocPFileSystemRequestChild(PFileSystemRequestChild* aActor) +{ + // The reference is increased in FileSystemTaskBase::Start of + // FileSystemTaskBase.cpp. We should decrease it after IPC. + RefPtr child = + dont_AddRef(static_cast(aActor)); + return true; +} + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/BackgroundChildImpl.h b/ipc/glue/BackgroundChildImpl.h index 2f4fe4fb98..491a25e5f9 100644 --- a/ipc/glue/BackgroundChildImpl.h +++ b/ipc/glue/BackgroundChildImpl.h @@ -145,6 +145,13 @@ protected: virtual bool DeallocPAsmJSCacheEntryChild(PAsmJSCacheEntryChild* aActor) override; + + virtual PFileSystemRequestChild* + AllocPFileSystemRequestChild(const FileSystemParams&) override; + + virtual bool + DeallocPFileSystemRequestChild(PFileSystemRequestChild*) override; + }; class BackgroundChildImpl::ThreadLocal final diff --git a/ipc/glue/BackgroundImpl.cpp b/ipc/glue/BackgroundImpl.cpp index 6529f5383d..9fd213e65e 100644 --- a/ipc/glue/BackgroundImpl.cpp +++ b/ipc/glue/BackgroundImpl.cpp @@ -700,7 +700,7 @@ private: // Must be cancelable in order to dispatch on active worker threads class ChildImpl::AlreadyCreatedCallbackRunnable final : - public nsCancelableRunnable + public CancelableRunnable { public: AlreadyCreatedCallbackRunnable() diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp index e517cd4f74..5eb697214c 100644 --- a/ipc/glue/BackgroundParentImpl.cpp +++ b/ipc/glue/BackgroundParentImpl.cpp @@ -14,6 +14,8 @@ #include "mozilla/Assertions.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/FileSystemBase.h" +#include "mozilla/dom/FileSystemRequestParent.h" #include "mozilla/dom/NuwaParent.h" #include "mozilla/dom/PBlobParent.h" #include "mozilla/dom/MessagePortParent.h" @@ -28,9 +30,11 @@ #include "mozilla/ipc/PBackgroundTestParent.h" #include "mozilla/layout/VsyncParent.h" #include "mozilla/dom/network/UDPSocketParent.h" +#include "mozilla/Preferences.h" #include "nsIAppsService.h" #include "nsNetUtil.h" #include "nsIScriptSecurityManager.h" +#include "nsProxyRelease.h" #include "mozilla/RefPtr.h" #include "nsThreadUtils.h" #include "nsTraceRefcnt.h" @@ -48,10 +52,12 @@ using mozilla::dom::asmjscache::PAsmJSCacheEntryParent; using mozilla::dom::cache::PCacheParent; using mozilla::dom::cache::PCacheStorageParent; using mozilla::dom::cache::PCacheStreamControlParent; +using mozilla::dom::FileSystemBase; +using mozilla::dom::FileSystemRequestParent; using mozilla::dom::MessagePortParent; +using mozilla::dom::NuwaParent; using mozilla::dom::PMessagePortParent; using mozilla::dom::PNuwaParent; -using mozilla::dom::NuwaParent; using mozilla::dom::UDPSocketParent; namespace { @@ -386,9 +392,9 @@ BackgroundParentImpl::RecvPUDPSocketConstructor(PUDPSocketParent* aActor, // we have a filter, we can safely skip the Dispatch and just invoke Init() // to install the filter. - // For mtransport, this will always be "stun", which doesn't allow outbound packets if - // they aren't STUN packets until a STUN response is seen. - if (!aFilter.EqualsASCII("stun")) { + // For mtransport, this will always be "stun", which doesn't allow outbound + // packets if they aren't STUN packets until a STUN response is seen. + if (!aFilter.EqualsASCII(NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX)) { return false; } @@ -423,6 +429,20 @@ BackgroundParentImpl::AllocPBroadcastChannelParent( namespace { +struct MOZ_STACK_CLASS NullifyContentParentRAII +{ + explicit NullifyContentParentRAII(RefPtr& aContentParent) + : mContentParent(aContentParent) + {} + + ~NullifyContentParentRAII() + { + mContentParent = nullptr; + } + + RefPtr& mContentParent; +}; + class CheckPrincipalRunnable final : public nsRunnable { public: @@ -432,34 +452,18 @@ public: : mContentParent(aParent) , mPrincipalInfo(aPrincipalInfo) , mOrigin(aOrigin) - , mBackgroundThread(NS_GetCurrentThread()) { AssertIsInMainProcess(); AssertIsOnBackgroundThread(); MOZ_ASSERT(mContentParent); - MOZ_ASSERT(mBackgroundThread); } NS_IMETHODIMP Run() { MOZ_ASSERT(NS_IsMainThread()); - struct MOZ_STACK_CLASS RunRAII - { - explicit RunRAII(RefPtr& aContentParent) - : mContentParent(aContentParent) - {} - - ~RunRAII() - { - mContentParent = nullptr; - } - - RefPtr& mContentParent; - }; - - RunRAII raii(mContentParent); + NullifyContentParentRAII raii(mContentParent); nsCOMPtr principal = PrincipalInfoToPrincipal(mPrincipalInfo); AssertAppPrincipal(mContentParent, principal); @@ -490,7 +494,87 @@ private: RefPtr mContentParent; PrincipalInfo mPrincipalInfo; nsCString mOrigin; - nsCOMPtr mBackgroundThread; +}; + +class CheckPermissionRunnable final : public nsRunnable +{ +public: + CheckPermissionRunnable(already_AddRefed aParent, + FileSystemRequestParent* aActor, + FileSystemBase::ePermissionCheckType aPermissionCheckType, + const nsCString& aPermissionName) + : mContentParent(aParent) + , mActor(aActor) + , mPermissionCheckType(aPermissionCheckType) + , mPermissionName(aPermissionName) + , mBackgroundEventTarget(NS_GetCurrentThread()) + { + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + MOZ_ASSERT(mContentParent); + MOZ_ASSERT(mBackgroundEventTarget); + MOZ_ASSERT(mPermissionCheckType == FileSystemBase::ePermissionCheckRequired || + mPermissionCheckType == FileSystemBase::ePermissionCheckByTestingPref); + } + + NS_IMETHOD + Run() override + { + if (NS_IsMainThread()) { + NullifyContentParentRAII raii(mContentParent); + + // If the permission is granted, we go back to the background thread to + // dispatch this task. + if (CheckPermission()) { + return mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); + } + + return NS_OK; + } + + AssertIsOnBackgroundThread(); + + // It can happen that this actor has been destroyed in the meantime we were + // on the main-thread. + if (!mActor->Destroyed()) { + mActor->Start(); + } + + return NS_OK; + } + +private: + ~CheckPermissionRunnable() + { + NS_ProxyRelease(mBackgroundEventTarget, mActor.forget()); + } + + bool + CheckPermission() + { + if (mPermissionCheckType == FileSystemBase::ePermissionCheckByTestingPref && + mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) { + return true; + } + + if (!AssertAppProcessPermission(mContentParent.get(), + mPermissionName.get())) { + mContentParent->KillHard("PBackground actor killed: permission denied."); + return false; + } + + return true; + } + + RefPtr mContentParent; + + RefPtr mActor; + + FileSystemBase::ePermissionCheckType mPermissionCheckType; + nsCString mPermissionName; + + nsCOMPtr mBackgroundEventTarget; }; } // namespace @@ -684,6 +768,73 @@ BackgroundParentImpl::DeallocPAsmJSCacheEntryParent( return true; } +dom::PFileSystemRequestParent* +BackgroundParentImpl::AllocPFileSystemRequestParent( + const FileSystemParams& aParams) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr result = new FileSystemRequestParent(); + + if (NS_WARN_IF(!result->Initialize(aParams))) { + return nullptr; + } + + return result.forget().take(); +} + +bool +BackgroundParentImpl::RecvPFileSystemRequestConstructor( + PFileSystemRequestParent* aActor, + const FileSystemParams& aParams) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr actor = static_cast(aActor); + + if (actor->PermissionCheckType() == FileSystemBase::ePermissionCheckNotRequired) { + actor->Start(); + return true; + } + + RefPtr parent = BackgroundParent::GetContentParent(this); + + // If the ContentParent is null we are dealing with a same-process actor. + if (!parent) { + actor->Start(); + return true; + } + + const nsCString& permissionName = actor->PermissionName(); + MOZ_ASSERT(!permissionName.IsEmpty()); + + // At this point we should have the right permission but we do the last check + // with this runnable. If the app doesn't have the permission, we kill the + // child process. + RefPtr runnable = + new CheckPermissionRunnable(parent.forget(), actor, + actor->PermissionCheckType(), permissionName); + + nsresult rv = NS_DispatchToMainThread(runnable); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); + + return true; +} + +bool +BackgroundParentImpl::DeallocPFileSystemRequestParent( + PFileSystemRequestParent* aDoomed) +{ + AssertIsInMainProcess(); + AssertIsOnBackgroundThread(); + + RefPtr parent = + dont_AddRef(static_cast(aDoomed)); + return true; +} + } // namespace ipc } // namespace mozilla diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h index a002a19fb9..adec127519 100644 --- a/ipc/glue/BackgroundParentImpl.h +++ b/ipc/glue/BackgroundParentImpl.h @@ -169,6 +169,17 @@ protected: virtual bool DeallocPAsmJSCacheEntryParent(PAsmJSCacheEntryParent* aActor) override; + + virtual PFileSystemRequestParent* + AllocPFileSystemRequestParent(const FileSystemParams&) override; + + virtual bool + RecvPFileSystemRequestConstructor(PFileSystemRequestParent* aActor, + const FileSystemParams& aParams) override; + + virtual bool + DeallocPFileSystemRequestParent(PFileSystemRequestParent*) override; + }; } // namespace ipc diff --git a/ipc/glue/MessagePump.cpp b/ipc/glue/MessagePump.cpp index c44ab2d7ab..6ab511368b 100644 --- a/ipc/glue/MessagePump.cpp +++ b/ipc/glue/MessagePump.cpp @@ -39,7 +39,7 @@ static MessagePump::Delegate* gFirstDelegate; namespace mozilla { namespace ipc { -class DoWorkRunnable final : public nsCancelableRunnable, +class DoWorkRunnable final : public CancelableRunnable, public nsITimerCallback { public: @@ -210,7 +210,7 @@ MessagePump::DoDelayedWork(base::MessagePump::Delegate* aDelegate) } } -NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, nsCancelableRunnable, +NS_IMPL_ISUPPORTS_INHERITED(DoWorkRunnable, CancelableRunnable, nsITimerCallback) NS_IMETHODIMP diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl index 6c59ba3d7a..a084ae0411 100644 --- a/ipc/glue/PBackground.ipdl +++ b/ipc/glue/PBackground.ipdl @@ -11,6 +11,7 @@ include protocol PCache; include protocol PCacheStorage; include protocol PCacheStreamControl; include protocol PFileDescriptorSet; +include protocol PFileSystemRequest; include protocol PMessagePort; include protocol PCameras; include protocol PNuwa; @@ -21,6 +22,7 @@ include protocol PVsync; include DOMTypes; include PBackgroundSharedTypes; include PBackgroundIDBSharedTypes; +include PFileSystemParams; include "mozilla/dom/cache/IPCUtils.h"; @@ -47,6 +49,7 @@ sync protocol PBackground manages PCacheStorage; manages PCacheStreamControl; manages PFileDescriptorSet; + manages PFileSystemRequest; manages PMessagePort; manages PCameras; manages PNuwa; @@ -84,6 +87,8 @@ parent: WriteParams write, PrincipalInfo principalInfo); + async PFileSystemRequest(FileSystemParams params); + child: async PCache(); async PCacheStreamControl(); diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 23cd01f717..de23e404c7 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -686,9 +686,9 @@ struct NewLayerEntry { RefPtr mLayer; AnimatedGeometryRoot* mAnimatedGeometryRoot; const DisplayItemScrollClip* mScrollClip; - // If non-null, this FrameMetrics is set to the be the first FrameMetrics + // If non-null, this ScrollMetadata is set to the be the first ScrollMetadata // on the layer. - UniquePtr mBaseFrameMetrics; + UniquePtr mBaseScrollMetadata; // The following are only used for retained layers (for occlusion // culling of those layers). These regions are all relative to the // container reference frame. @@ -4107,15 +4107,15 @@ ContainerState::ProcessDisplayItems(nsDisplayList* aList) if (itemType == nsDisplayItem::TYPE_SCROLL_INFO_LAYER) { nsDisplayScrollInfoLayer* scrollItem = static_cast(item); newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false; - newLayerEntry->mBaseFrameMetrics = - scrollItem->ComputeFrameMetrics(ownLayer, mParameters); + newLayerEntry->mBaseScrollMetadata = + scrollItem->ComputeScrollMetadata(ownLayer, mParameters); } else if ((itemType == nsDisplayItem::TYPE_SUBDOCUMENT || itemType == nsDisplayItem::TYPE_ZOOM || itemType == nsDisplayItem::TYPE_RESOLUTION) && gfxPrefs::LayoutUseContainersForRootFrames()) { - newLayerEntry->mBaseFrameMetrics = - static_cast(item)->ComputeFrameMetrics(ownLayer, mParameters); + newLayerEntry->mBaseScrollMetadata = + static_cast(item)->ComputeScrollMetadata(ownLayer, mParameters); } /** @@ -4657,13 +4657,13 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) return; } - AutoTArray metricsArray; - if (aEntry->mBaseFrameMetrics) { - metricsArray.AppendElement(*aEntry->mBaseFrameMetrics); + AutoTArray metricsArray; + if (aEntry->mBaseScrollMetadata) { + metricsArray.AppendElement(*aEntry->mBaseScrollMetadata); // The base FrameMetrics was not computed by the nsIScrollableframe, so it // should not have a mask layer. - MOZ_ASSERT(!aEntry->mBaseFrameMetrics->GetMaskLayerIndex()); + MOZ_ASSERT(!aEntry->mBaseScrollMetadata->GetMaskLayerIndex()); } // Any extra mask layers we need to attach to FrameMetrics. @@ -4682,9 +4682,9 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) nsIScrollableFrame* scrollFrame = scrollClip->mScrollableFrame; const DisplayItemClip* clip = scrollClip->mClip; - Maybe metrics = - scrollFrame->ComputeFrameMetrics(aEntry->mLayer, mContainerReferenceFrame, mParameters, clip); - if (!metrics) { + Maybe metadata = + scrollFrame->ComputeScrollMetadata(aEntry->mLayer, mContainerReferenceFrame, mParameters, clip); + if (!metadata) { continue; } @@ -4700,16 +4700,16 @@ ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) RefPtr maskLayer = CreateMaskLayer(aEntry->mLayer, *clip, nextIndex, clip->GetRoundedRectCount()); if (maskLayer) { - metrics->SetMaskLayerIndex(nextIndex); + metadata->SetMaskLayerIndex(nextIndex); maskLayers.AppendElement(maskLayer); } } - metricsArray.AppendElement(*metrics); + metricsArray.AppendElement(*metadata); } // Watch out for FrameMetrics copies in profiles - aEntry->mLayer->SetFrameMetrics(metricsArray); + aEntry->mLayer->SetScrollMetadata(metricsArray); aEntry->mLayer->SetAncestorMaskLayers(maskLayers); } @@ -4749,8 +4749,8 @@ InvalidateVisibleBoundsChangesForScrolledLayer(PaintedLayer* aLayer) static inline const Maybe& GetStationaryClipInContainer(Layer* aLayer) { - if (size_t metricsCount = aLayer->GetFrameMetricsCount()) { - return aLayer->GetFrameMetrics(metricsCount - 1).GetClipRect(); + if (size_t metricsCount = aLayer->GetScrollMetadataCount()) { + return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect(); } return aLayer->GetClipRect(); } diff --git a/layout/base/Units.h b/layout/base/Units.h index 90cc41d9d8..52c25f3452 100644 --- a/layout/base/Units.h +++ b/layout/base/Units.h @@ -248,6 +248,11 @@ struct CSSPixel { NSToCoordRoundWithClamp(float(aPoint.y) * float(AppUnitsPerCSSPixel()))); } + static nsSize ToAppUnits(const CSSSize& aSize) { + return nsSize(NSToCoordRoundWithClamp(aSize.width * float(AppUnitsPerCSSPixel())), + NSToCoordRoundWithClamp(aSize.height * float(AppUnitsPerCSSPixel()))); + } + static nsSize ToAppUnits(const CSSIntSize& aSize) { return nsSize(NSToCoordRoundWithClamp(float(aSize.width) * float(AppUnitsPerCSSPixel())), NSToCoordRoundWithClamp(float(aSize.height) * float(AppUnitsPerCSSPixel()))); diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 91972e7329..f4bf61339a 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1690,11 +1690,11 @@ already_AddRefed nsDisplayList::PaintRoot(nsDisplayListBuilder* aB props = Move(LayerProperties::CloneFrom(layerManager->GetRoot())); } - // Clear any FrameMetrics that may have been set on the root layer on a + // Clear any ScrollMetadata that may have been set on the root layer on a // previous paint. This paint will set new metrics if necessary, and if we // don't clear the old one here, we may be left with extra metrics. if (Layer* root = layerManager->GetRoot()) { - root->SetFrameMetrics(nsTArray()); + root->SetScrollMetadata(nsTArray()); } ContainerLayerParameters containerParameters @@ -1766,8 +1766,8 @@ already_AddRefed nsDisplayList::PaintRoot(nsDisplayListBuilder* aB nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize()); - root->SetFrameMetrics( - nsLayoutUtils::ComputeFrameMetrics(frame, + root->SetScrollMetadata( + nsLayoutUtils::ComputeScrollMetadata(frame, rootScrollFrame, content, aBuilder->FindReferenceFrameFor(frame), root, FrameMetrics::NULL_SCROLL_ID, viewport, Nothing(), @@ -4585,12 +4585,12 @@ nsDisplaySubDocument::BuildLayer(nsDisplayListBuilder* aBuilder, return layer.forget(); } -UniquePtr -nsDisplaySubDocument::ComputeFrameMetrics(Layer* aLayer, - const ContainerLayerParameters& aContainerParameters) +UniquePtr +nsDisplaySubDocument::ComputeScrollMetadata(Layer* aLayer, + const ContainerLayerParameters& aContainerParameters) { if (!(mFlags & GENERATE_SCROLLABLE_LAYER)) { - return UniquePtr(nullptr); + return UniquePtr(nullptr); } nsPresContext* presContext = mFrame->PresContext(); @@ -4611,8 +4611,8 @@ nsDisplaySubDocument::ComputeFrameMetrics(Layer* aLayer, mFrame->GetPosition() + mFrame->GetOffsetToCrossDoc(ReferenceFrame()); - return MakeUnique( - nsLayoutUtils::ComputeFrameMetrics( + return MakeUnique( + nsLayoutUtils::ComputeScrollMetadata( mFrame, rootScrollFrame, rootScrollFrame->GetContent(), ReferenceFrame(), aLayer, mScrollParentId, viewport, Nothing(), isRootContentDocument, params)); @@ -4994,9 +4994,9 @@ nsDisplayScrollInfoLayer::GetLayerState(nsDisplayListBuilder* aBuilder, return LAYER_ACTIVE_EMPTY; } -UniquePtr -nsDisplayScrollInfoLayer::ComputeFrameMetrics(Layer* aLayer, - const ContainerLayerParameters& aContainerParameters) +UniquePtr +nsDisplayScrollInfoLayer::ComputeScrollMetadata(Layer* aLayer, + const ContainerLayerParameters& aContainerParameters) { ContainerLayerParameters params = aContainerParameters; if (mScrolledFrame->GetContent() && @@ -5008,13 +5008,13 @@ nsDisplayScrollInfoLayer::ComputeFrameMetrics(Layer* aLayer, mScrollFrame->GetPosition() + mScrollFrame->GetOffsetToCrossDoc(ReferenceFrame()); - FrameMetrics metrics = nsLayoutUtils::ComputeFrameMetrics( + ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata( mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(), ReferenceFrame(), aLayer, mScrollParentId, viewport, Nothing(), false, params); - metrics.SetIsScrollInfoLayer(true); + metadata.GetMetrics().SetIsScrollInfoLayer(true); - return UniquePtr(new FrameMetrics(metrics)); + return UniquePtr(new ScrollMetadata(metadata)); } diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index c17b52c760..8c1b0db87e 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -225,7 +225,7 @@ public: PAINTING, EVENT_DELIVERY, PLUGIN_GEOMETRY, - IMAGE_VISIBILITY, + FRAME_VISIBILITY, TRANSFORM_COMPUTATION }; nsDisplayListBuilder(nsIFrame* aReferenceFrame, Mode aMode, bool aBuildCaret); @@ -261,11 +261,13 @@ public: * @return true if the display list is being built for painting. */ bool IsForPainting() { return mMode == PAINTING; } + /** - * @return true if the display list is being built for determining image + * @return true if the display list is being built for determining frame * visibility. */ - bool IsForImageVisibility() { return mMode == IMAGE_VISIBILITY; } + bool IsForFrameVisibility() { return mMode == FRAME_VISIBILITY; } + bool WillComputePluginGeometry() { return mWillComputePluginGeometry; } /** * @return true if "painting is suppressed" during page load and we @@ -1306,6 +1308,7 @@ public: typedef mozilla::DisplayItemClip DisplayItemClip; typedef mozilla::DisplayItemScrollClip DisplayItemScrollClip; typedef mozilla::layers::FrameMetrics FrameMetrics; + typedef mozilla::layers::ScrollMetadata ScrollMetadata; typedef mozilla::layers::FrameMetrics::ViewID ViewID; typedef mozilla::layers::Layer Layer; typedef mozilla::layers::LayerManager LayerManager; @@ -3548,8 +3551,8 @@ public: NS_DISPLAY_DECL_NAME("SubDocument", TYPE_SUBDOCUMENT) - mozilla::UniquePtr ComputeFrameMetrics(Layer* aLayer, - const ContainerLayerParameters& aContainerParameters); + mozilla::UniquePtr ComputeScrollMetadata(Layer* aLayer, + const ContainerLayerParameters& aContainerParameters); protected: ViewID mScrollParentId; @@ -3689,8 +3692,8 @@ public: virtual void WriteDebugInfo(std::stringstream& aStream) override; - mozilla::UniquePtr ComputeFrameMetrics(Layer* aLayer, - const ContainerLayerParameters& aContainerParameters); + mozilla::UniquePtr ComputeScrollMetadata(Layer* aLayer, + const ContainerLayerParameters& aContainerParameters); protected: nsIFrame* mScrollFrame; diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index 3dd0eac4cf..a89a69d044 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -1180,8 +1180,7 @@ nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow, // the event being dispatched. if (!sIsBeforeUnloadDisabled && *aShouldPrompt && dialogsAreEnabled && mDocument && (!sBeforeUnloadRequiresInteraction || mDocument->UserHasInteracted()) && - (event->WidgetEventPtr()->mFlags.mDefaultPrevented || - !text.IsEmpty())) { + (event->WidgetEventPtr()->DefaultPrevented() || !text.IsEmpty())) { // Ask the user if it's ok to unload the current page nsCOMPtr prompt = do_GetInterface(docShell); diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index 3799ce3ac7..ff6ecef141 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -138,10 +138,10 @@ typedef struct CapturingContentInfo { mozilla::StaticRefPtr mContent; } CapturingContentInfo; -// f17842ee-f1f0-4193-814f-70d706b67060 +// a75573d6-34c8-4485-8fb7-edcb6fc70e12 #define NS_IPRESSHELL_IID \ -{ 0xf17842ee, 0xf1f0, 0x4193, \ - { 0x81, 0x4f, 0x70, 0xd7, 0x06, 0xb6, 0x70, 0x60 } } +{ 0xa75573d6, 0x34c8, 0x4485, \ + { 0x8f, 0xb7, 0xed, 0xcb, 0x6f, 0xc7, 0x0e, 0x12 } } // debug VerifyReflow flags #define VERIFY_REFLOW_ON 0x01 @@ -1564,23 +1564,38 @@ public: void InvalidatePresShellIfHidden(); void CancelInvalidatePresShellIfHidden(); - // Schedule an update of the list of visible images. - virtual void ScheduleImageVisibilityUpdate() = 0; - // Clears the current list of visible images on this presshell and replaces it - // with images that are in the display list aList. - virtual void RebuildImageVisibilityDisplayList(const nsDisplayList& aList) = 0; - virtual void RebuildImageVisibility(nsRect* aRect = nullptr, - bool aRemoveOnly = false) = 0; + ////////////////////////////////////////////////////////////////////////////// + // Approximate frame visibility tracking public API. + ////////////////////////////////////////////////////////////////////////////// - // Ensures the image is in the list of visible images. - virtual void EnsureImageInVisibleList(nsIImageLoadingContent* aImage) = 0; + /// Schedule an update of the list of approximately visible frames "soon". + /// This lets the refresh driver know that we want a visibility update in the + /// near future. The refresh driver applies its own heuristics and throttling + /// to decide when to actually perform the visibility update. + virtual void ScheduleApproximateFrameVisibilityUpdateSoon() = 0; - // Removes the image from the list of visible images if it is present there. - virtual void RemoveImageFromVisibleList(nsIImageLoadingContent* aImage) = 0; + /// Schedule an update of the list of approximately visible frames "now". The + /// update runs asynchronously, but it will be posted to the event loop + /// immediately. Prefer the "soon" variation of this method when possible, as + /// this variation ignores the refresh driver's heuristics. + virtual void ScheduleApproximateFrameVisibilityUpdateNow() = 0; + + /// Clears the current list of approximately visible frames on this pres shell + /// and replaces it with frames that are in the display list @aList. + virtual void RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList) = 0; + virtual void RebuildApproximateFrameVisibility(nsRect* aRect = nullptr, + bool aRemoveOnly = false) = 0; + + /// Ensures @aFrame is in the list of approximately visible frames. + virtual void EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) = 0; + + /// Removes @aFrame from the list of approximately visible frames if present. + virtual void RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) = 0; + + /// Whether we should assume all frames are visible. + virtual bool AssumeAllFramesVisible() = 0; - // Whether we should assume all images are visible. - virtual bool AssumeAllImagesVisible() = 0; /** * Returns whether the document's style set's rule processor for the diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index f14a95d9f4..f6f52c88a2 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1159,35 +1159,35 @@ nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent, // have drastically changed. Check if we should schedule an update. nsRect oldDisplayPort; bool hadDisplayPort = - scrollableFrame->GetDisplayPortAtLastImageVisibilityUpdate(&oldDisplayPort); + scrollableFrame->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(&oldDisplayPort); nsRect newDisplayPort; Unused << GetDisplayPort(aContent, &newDisplayPort); - bool needImageVisibilityUpdate = !hadDisplayPort; + bool needVisibilityUpdate = !hadDisplayPort; // Check if the total size has changed by a large factor. - if (!needImageVisibilityUpdate) { + if (!needVisibilityUpdate) { if ((newDisplayPort.width > 2 * oldDisplayPort.width) || (oldDisplayPort.width > 2 * newDisplayPort.width) || (newDisplayPort.height > 2 * oldDisplayPort.height) || (oldDisplayPort.height > 2 * newDisplayPort.height)) { - needImageVisibilityUpdate = true; + needVisibilityUpdate = true; } } // Check if it's moved by a significant amount. - if (!needImageVisibilityUpdate) { + if (!needVisibilityUpdate) { if (nsRect* baseData = static_cast(aContent->GetProperty(nsGkAtoms::DisplayPortBase))) { nsRect base = *baseData; if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) || (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) > base.width) || (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) || (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) > base.height)) { - needImageVisibilityUpdate = true; + needVisibilityUpdate = true; } } } - if (needImageVisibilityUpdate) { - aPresShell->ScheduleImageVisibilityUpdate(); + if (needVisibilityUpdate) { + aPresShell->ScheduleApproximateFrameVisibilityUpdateNow(); } return true; @@ -8051,77 +8051,6 @@ nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame, return shadows; } -/* static */ void -nsLayoutUtils::UpdateImageVisibilityForFrame(nsIFrame* aImageFrame) -{ -#ifdef DEBUG - nsIAtom* type = aImageFrame->GetType(); - MOZ_ASSERT(type == nsGkAtoms::imageFrame || - type == nsGkAtoms::imageControlFrame || - type == nsGkAtoms::svgImageFrame, "wrong type of frame"); -#endif - - nsCOMPtr content = do_QueryInterface(aImageFrame->GetContent()); - if (!content) { - return; - } - - nsIPresShell* presShell = aImageFrame->PresContext()->PresShell(); - if (presShell->AssumeAllImagesVisible()) { - presShell->EnsureImageInVisibleList(content); - return; - } - - bool visible = true; - nsIFrame* f = aImageFrame->GetParent(); - nsRect rect = aImageFrame->GetContentRectRelativeToSelf(); - nsIFrame* rectFrame = aImageFrame; - while (f) { - nsIScrollableFrame* sf = do_QueryFrame(f); - if (sf) { - nsRect transformedRect = - nsLayoutUtils::TransformFrameRectToAncestor(rectFrame, rect, f); - if (!sf->IsRectNearlyVisible(transformedRect)) { - visible = false; - break; - } - // Move transformedRect to be contained in the scrollport as best we can - // (it might not fit) to pretend that it was scrolled into view. - nsRect scrollPort = sf->GetScrollPortRect(); - if (transformedRect.XMost() > scrollPort.XMost()) { - transformedRect.x -= transformedRect.XMost() - scrollPort.XMost(); - } - if (transformedRect.x < scrollPort.x) { - transformedRect.x = scrollPort.x; - } - if (transformedRect.YMost() > scrollPort.YMost()) { - transformedRect.y -= transformedRect.YMost() - scrollPort.YMost(); - } - if (transformedRect.y < scrollPort.y) { - transformedRect.y = scrollPort.y; - } - transformedRect.width = std::min(transformedRect.width, scrollPort.width); - transformedRect.height = std::min(transformedRect.height, scrollPort.height); - rect = transformedRect; - rectFrame = f; - } - nsIFrame* parent = f->GetParent(); - if (!parent) { - parent = nsLayoutUtils::GetCrossDocParentFrame(f); - if (parent && parent->PresContext()->IsChrome()) { - break; - } - } - f = parent; - } - - if (visible) { - presShell->EnsureImageInVisibleList(content); - } else { - presShell->RemoveImageFromVisibleList(content); - } -} - /* static */ bool nsLayoutUtils::GetContentViewerSize(nsPresContext* aPresContext, LayoutDeviceIntSize& aOutSize) @@ -8649,23 +8578,24 @@ nsLayoutUtils::CanScrollOriginClobberApz(nsIAtom* aScrollOrigin) && aScrollOrigin != nsGkAtoms::restore; } -/* static */ FrameMetrics -nsLayoutUtils::ComputeFrameMetrics(nsIFrame* aForFrame, - nsIFrame* aScrollFrame, - nsIContent* aContent, - const nsIFrame* aReferenceFrame, - Layer* aLayer, - ViewID aScrollParentId, - const nsRect& aViewport, - const Maybe& aClipRect, - bool aIsRootContent, - const ContainerLayerParameters& aContainerParameters) +/* static */ ScrollMetadata +nsLayoutUtils::ComputeScrollMetadata(nsIFrame* aForFrame, + nsIFrame* aScrollFrame, + nsIContent* aContent, + const nsIFrame* aReferenceFrame, + Layer* aLayer, + ViewID aScrollParentId, + const nsRect& aViewport, + const Maybe& aClipRect, + bool aIsRootContent, + const ContainerLayerParameters& aContainerParameters) { nsPresContext* presContext = aForFrame->PresContext(); int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); nsIPresShell* presShell = presContext->GetPresShell(); - FrameMetrics metrics; + ScrollMetadata metadata; + FrameMetrics& metrics = metadata.GetMetrics(); metrics.SetViewport(CSSRect::FromAppUnits(aViewport)); ViewID scrollId = FrameMetrics::NULL_SCROLL_ID; @@ -8733,6 +8663,8 @@ nsLayoutUtils::ComputeFrameMetrics(nsIFrame* aForFrame, } metrics.SetUsesContainerScrolling(scrollableFrame->UsesContainerScrolling()); + + metadata.SetSnapInfo(scrollableFrame->GetScrollSnapInfo()); } // If we have the scrollparent being the same as the scroll id, the @@ -8800,7 +8732,7 @@ nsLayoutUtils::ComputeFrameMetrics(nsIFrame* aForFrame, ParentLayerRect rect = LayoutDeviceRect::FromAppUnits(*aClipRect, auPerDevPixel) * metrics.GetCumulativeResolution() * layerToParentLayerScale; - metrics.SetClipRect(Some(RoundedToInt(rect))); + metadata.SetClipRect(Some(RoundedToInt(rect))); } // For the root scroll frame of the root content document (RCD-RSF), the above calculation @@ -8864,13 +8796,13 @@ nsLayoutUtils::ComputeFrameMetrics(nsIFrame* aForFrame, } } - return metrics; + return metadata; } /* static */ bool nsLayoutUtils::ContainsMetricsWithId(const Layer* aLayer, const ViewID& aScrollId) { - for (uint32_t i = aLayer->GetFrameMetricsCount(); i > 0; i--) { + for (uint32_t i = aLayer->GetScrollMetadataCount(); i > 0; i--) { if (aLayer->GetFrameMetrics(i-1).GetScrollId() == aScrollId) { return true; } diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 19cb567a07..b550127928 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -143,6 +143,7 @@ class nsLayoutUtils public: typedef mozilla::layers::FrameMetrics FrameMetrics; + typedef mozilla::layers::ScrollMetadata ScrollMetadata; typedef FrameMetrics::ViewID ViewID; typedef mozilla::CSSPoint CSSPoint; typedef mozilla::CSSSize CSSSize; @@ -2549,14 +2550,6 @@ public: nsRegion* aPreciseTargetDest, nsRegion* aImpreciseTargetDest); - /** - * Determine if aImageFrame (which is an nsImageFrame, nsImageControlFrame, or - * nsSVGImageFrame) is visible or close to being visible via scrolling and - * update the presshell with this knowledge. - */ - static void - UpdateImageVisibilityForFrame(nsIFrame* aImageFrame); - /** * Populate aOutSize with the size of the content viewer corresponding * to the given prescontext. Return true if the size was set, false @@ -2735,16 +2728,16 @@ public: */ static bool CanScrollOriginClobberApz(nsIAtom* aScrollOrigin); - static FrameMetrics ComputeFrameMetrics(nsIFrame* aForFrame, - nsIFrame* aScrollFrame, - nsIContent* aContent, - const nsIFrame* aReferenceFrame, - Layer* aLayer, - ViewID aScrollParentId, - const nsRect& aViewport, - const mozilla::Maybe& aClipRect, - bool aIsRoot, - const ContainerLayerParameters& aContainerParameters); + static ScrollMetadata ComputeScrollMetadata(nsIFrame* aForFrame, + nsIFrame* aScrollFrame, + nsIContent* aContent, + const nsIFrame* aReferenceFrame, + Layer* aLayer, + ViewID aScrollParentId, + const nsRect& aViewport, + const mozilla::Maybe& aClipRect, + bool aIsRoot, + const ContainerLayerParameters& aContainerParameters); /** * If the given scroll frame needs an area excluded from its composition diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 083eea2738..2b5d0a7ca8 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -1174,9 +1174,9 @@ PresShell::Destroy() mSynthMouseMoveEvent.Revoke(); - mUpdateImageVisibilityEvent.Revoke(); + mUpdateApproximateFrameVisibilityEvent.Revoke(); - ClearVisibleImagesList(nsIImageLoadingContent::ON_NONVISIBLE_REQUEST_DISCARD); + ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DISCARD_IMAGES)); if (mCaret) { mCaret->Terminate(); @@ -3791,7 +3791,7 @@ PresShell::UnsuppressAndInvalidate() if (!mHaveShutDown) { SynthesizeMouseMove(false); - ScheduleImageVisibilityUpdate(); + ScheduleApproximateFrameVisibilityUpdateNow(); } } @@ -5610,87 +5610,73 @@ AddFrameToVisibleRegions(nsIFrame* aFrame, } /* static */ void -PresShell::MarkImagesInListVisible(const nsDisplayList& aList, - Maybe& aVisibleRegions) +PresShell::MarkFramesInListApproximatelyVisible(const nsDisplayList& aList, + Maybe& aVisibleRegions) { for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) { nsDisplayList* sublist = item->GetChildren(); if (sublist) { - MarkImagesInListVisible(*sublist, aVisibleRegions); + MarkFramesInListApproximatelyVisible(*sublist, aVisibleRegions); continue; } - nsIFrame* f = item->Frame(); - // We could check the type of the display item, only a handful can hold an - // image loading content. - // dont bother nscomptr here, it is wasteful - nsCOMPtr content(do_QueryInterface(f->GetContent())); - if (content) { - // use the presshell containing the image - PresShell* presShell = static_cast(f->PresContext()->PresShell()); - uint32_t count = presShell->mVisibleImages.Count(); - presShell->mVisibleImages.PutEntry(content); - if (presShell->mVisibleImages.Count() > count) { - // content was added to mVisibleImages, so we need to increment its visible count - content->IncrementVisibleCount(); - } - AddFrameToVisibleRegions(f, presShell->mViewManager, aVisibleRegions); + nsIFrame* frame = item->Frame(); + MOZ_ASSERT(frame); + + if (!frame->TrackingVisibility()) { + continue; } + + // Use the presshell containing the frame. + auto* presShell = static_cast(frame->PresContext()->PresShell()); + uint32_t count = presShell->mApproximatelyVisibleFrames.Count(); + MOZ_ASSERT(!presShell->AssumeAllFramesVisible()); + presShell->mApproximatelyVisibleFrames.PutEntry(frame); + if (presShell->mApproximatelyVisibleFrames.Count() > count) { + // The frame was added to mApproximatelyVisibleFrames, so increment its visible count. + frame->IncApproximateVisibleCount(); + } + + AddFrameToVisibleRegions(frame, presShell->mViewManager, aVisibleRegions); } } void -PresShell::ReportAnyBadState() +PresShell::ReportBadStateDuringVisibilityUpdate() { if (!NS_IsMainThread()) { - gfxCriticalNote << "Got null image in image visibility: off-main-thread"; + gfxCriticalNote << "Got null frame in frame visibility: off-main-thread"; } if (mIsZombie) { - gfxCriticalNote << "Got null image in image visibility: mIsZombie"; + gfxCriticalNote << "Got null frame in frame visibility: mIsZombie"; } if (mIsDestroying) { - gfxCriticalNote << "Got null image in image visibility: mIsDestroying"; + gfxCriticalNote << "Got null frame in frame visibility: mIsDestroying"; } if (mIsReflowing) { - gfxCriticalNote << "Got null image in image visibility: mIsReflowing"; + gfxCriticalNote << "Got null frame in frame visibility: mIsReflowing"; } if (mPaintingIsFrozen) { - gfxCriticalNote << "Got null image in image visibility: mPaintingIsFrozen"; + gfxCriticalNote << "Got null frame in frame visibility: mPaintingIsFrozen"; } if (mForwardingContainer) { - gfxCriticalNote << "Got null image in image visibility: mForwardingContainer"; + gfxCriticalNote << "Got null frame in frame visibility: mForwardingContainer"; } if (mIsNeverPainting) { - gfxCriticalNote << "Got null image in image visibility: mIsNeverPainting"; + gfxCriticalNote << "Got null frame in frame visibility: mIsNeverPainting"; } if (mIsDocumentGone) { - gfxCriticalNote << "Got null image in image visibility: mIsDocumentGone"; + gfxCriticalNote << "Got null frame in frame visibility: mIsDocumentGone"; } if (!nsContentUtils::IsSafeToRunScript()) { - gfxCriticalNote << "Got null image in image visibility: not safe to run script"; + gfxCriticalNote << "Got null frame in frame visibility: not safe to run script"; } } void -PresShell::SetInImageVisibility(bool aState) +PresShell::SetInFrameVisibilityUpdate(bool aState) { - mInImageVisibility = aState; -} - -static void -DecrementVisibleCount(nsTHashtable>& aImages, - uint32_t aNonvisibleAction, PresShell* aPresShell) -{ - for (auto iter = aImages.Iter(); !iter.Done(); iter.Next()) { - if (MOZ_UNLIKELY(!iter.Get()->GetKey())) { - // We are about to crash, annotate crash report with some info that might - // help debug the crash (bug 1251150) - aPresShell->ReportAnyBadState(); - } - aPresShell->SetInImageVisibility(true); - iter.Get()->GetKey()->DecrementVisibleCount(aNonvisibleAction); - aPresShell->SetInImageVisibility(false); - } + mInFrameVisibilityUpdate = aState; } static void @@ -5743,15 +5729,43 @@ NotifyCompositorOfVisibleRegionsChange(PresShell* aPresShell, } } -void -PresShell::RebuildImageVisibilityDisplayList(const nsDisplayList& aList) +/* static */ void +PresShell::DecApproximateVisibleCount(VisibleFrames& aFrames, + Maybe aNonvisibleAction + /* = Nothing() */) { - MOZ_ASSERT(!mImageVisibilityVisited, "already visited?"); - mImageVisibilityVisited = true; - // Remove the entries of the mVisibleImages hashtable and put them in - // oldVisibleImages. - nsTHashtable< nsRefPtrHashKey > oldVisibleImages; - mVisibleImages.SwapElements(oldVisibleImages); + for (auto iter = aFrames.Iter(); !iter.Done(); iter.Next()) { + nsIFrame* frame = iter.Get()->GetKey(); + + if (MOZ_UNLIKELY(!frame)) { + // We are about to crash, annotate crash report with some info that might + // help debug the crash (bug 1251150) + ReportBadStateDuringVisibilityUpdate(); + } + + SetInFrameVisibilityUpdate(true); + + // Decrement the frame's visible count if we're still tracking its + // visibility. (We may not be, if the frame disabled visibility tracking + // after we added it to the visible frames list.) + if (frame->TrackingVisibility()) { + frame->DecApproximateVisibleCount(aNonvisibleAction); + } + + SetInFrameVisibilityUpdate(false); + } +} + +void +PresShell::RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList) +{ + MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?"); + mApproximateFrameVisibilityVisited = true; + + // Remove the entries of the mApproximatelyVisibleFrames hashtable and put + // them in oldApproxVisibleFrames. + VisibleFrames oldApproximatelyVisibleFrames; + mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames); // If we're visualizing visible regions, create a VisibleRegions object to // store information about them. The functions we call will populate this @@ -5762,58 +5776,58 @@ PresShell::RebuildImageVisibilityDisplayList(const nsDisplayList& aList) visibleRegions.emplace(); } - MarkImagesInListVisible(aList, visibleRegions); + MarkFramesInListApproximatelyVisible(aList, visibleRegions); - DecrementVisibleCount(oldVisibleImages, - nsIImageLoadingContent::ON_NONVISIBLE_NO_ACTION, this); + DecApproximateVisibleCount(oldApproximatelyVisibleFrames); NotifyCompositorOfVisibleRegionsChange(this, visibleRegions); } /* static */ void -PresShell::ClearImageVisibilityVisited(nsView* aView, bool aClear) +PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView, bool aClear) { nsViewManager* vm = aView->GetViewManager(); if (aClear) { PresShell* presShell = static_cast(vm->GetPresShell()); - if (!presShell->mImageVisibilityVisited) { - presShell->ClearVisibleImagesList( - nsIImageLoadingContent::ON_NONVISIBLE_NO_ACTION); + if (!presShell->mApproximateFrameVisibilityVisited) { + presShell->ClearApproximatelyVisibleFramesList(); } - presShell->mImageVisibilityVisited = false; + presShell->mApproximateFrameVisibilityVisited = false; } for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) { - ClearImageVisibilityVisited(v, v->GetViewManager() != vm); + ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm); } } void -PresShell::ClearVisibleImagesList(uint32_t aNonvisibleAction) +PresShell::ClearApproximatelyVisibleFramesList(Maybe aNonvisibleAction + /* = Nothing() */) { - if (mInImageVisibility) { - gfxCriticalNoteOnce << "ClearVisibleImagesList is re-entering on " + if (mInFrameVisibilityUpdate) { + gfxCriticalNoteOnce << "ClearApproximatelyVisibleFramesList is re-entering on " << (NS_IsMainThread() ? "" : "non-") << "main thread"; } - DecrementVisibleCount(mVisibleImages, aNonvisibleAction, this); - mVisibleImages.Clear(); + DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction); + mApproximatelyVisibleFrames.Clear(); } void -PresShell::MarkImagesInSubtreeVisible(nsIFrame* aFrame, - const nsRect& aRect, - Maybe& aVisibleRegions, - bool aRemoveOnly /* = false */) +PresShell::MarkFramesInSubtreeApproximatelyVisible(nsIFrame* aFrame, + const nsRect& aRect, + Maybe& aVisibleRegions, + bool aRemoveOnly /* = false */) { MOZ_ASSERT(aFrame->PresContext()->PresShell() == this, "wrong presshell"); - nsCOMPtr content(do_QueryInterface(aFrame->GetContent())); - if (content && aFrame->StyleVisibility()->IsVisible() && - (!aRemoveOnly || content->GetVisibleCount() > 0)) { - uint32_t count = mVisibleImages.Count(); - mVisibleImages.PutEntry(content); - if (mVisibleImages.Count() > count) { - // content was added to mVisibleImages, so we need to increment its visible count - content->IncrementVisibleCount(); + if (aFrame->TrackingVisibility() && + aFrame->StyleVisibility()->IsVisible() && + (!aRemoveOnly || aFrame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE)) { + MOZ_ASSERT(!AssumeAllFramesVisible()); + uint32_t count = mApproximatelyVisibleFrames.Count(); + mApproximatelyVisibleFrames.PutEntry(aFrame); + if (mApproximatelyVisibleFrames.Count() > count) { + // The frame was added to mApproximatelyVisibleFrames, so increment its visible count. + aFrame->IncApproximateVisibleCount(); } AddFrameToVisibleRegions(aFrame, mViewManager, aVisibleRegions); @@ -5823,7 +5837,7 @@ PresShell::MarkImagesInSubtreeVisible(nsIFrame* aFrame, if (subdocFrame) { nsIPresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting( nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION); - if (presShell) { + if (presShell && !presShell->AssumeAllFramesVisible()) { nsRect rect = aRect; nsIFrame* root = presShell->GetRootFrame(); if (root) { @@ -5835,7 +5849,7 @@ PresShell::MarkImagesInSubtreeVisible(nsIFrame* aFrame, aFrame->PresContext()->AppUnitsPerDevPixel(), presShell->GetPresContext()->AppUnitsPerDevPixel()); - presShell->RebuildImageVisibility(&rect); + presShell->RebuildApproximateFrameVisibility(&rect); } return; } @@ -5844,7 +5858,7 @@ PresShell::MarkImagesInSubtreeVisible(nsIFrame* aFrame, nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame); if (scrollFrame) { - scrollFrame->NotifyImageVisibilityUpdate(); + scrollFrame->NotifyApproximateFrameVisibilityUpdate(); nsRect displayPort; bool usingDisplayport = nsLayoutUtils::GetDisplayPortForVisibilityTesting( @@ -5859,7 +5873,7 @@ PresShell::MarkImagesInSubtreeVisible(nsIFrame* aFrame, bool preserves3DChildren = aFrame->Extend3DContext(); - // we assume all images in popups are visible elsewhere, so we skip them here + // We assume all frames in popups are visible, so we skip them here. const nsIFrame::ChildListIDs skip(nsIFrame::kPopupList | nsIFrame::kSelectPopupList); for (nsIFrame::ChildListIterator childLists(aFrame); @@ -5885,32 +5899,32 @@ PresShell::MarkImagesInSubtreeVisible(nsIFrame* aFrame, } } } - MarkImagesInSubtreeVisible(child, r, aVisibleRegions); + MarkFramesInSubtreeApproximatelyVisible(child, r, aVisibleRegions); } } } void -PresShell::RebuildImageVisibility(nsRect* aRect, - bool aRemoveOnly /* = false */) +PresShell::RebuildApproximateFrameVisibility(nsRect* aRect, + bool aRemoveOnly /* = false */) { - MOZ_ASSERT(!mImageVisibilityVisited, "already visited?"); - mImageVisibilityVisited = true; + MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?"); + mApproximateFrameVisibilityVisited = true; nsIFrame* rootFrame = GetRootFrame(); if (!rootFrame) { return; } - if (mInImageVisibility) { - gfxCriticalNoteOnce << "RebuildImageVisibility is re-entering on " + if (mInFrameVisibilityUpdate) { + gfxCriticalNoteOnce << "RebuildApproximateFrameVisibility is re-entering on " << (NS_IsMainThread() ? "" : "non-") << "main thread"; } - // Remove the entries of the mVisibleImages hashtable and put them in - // oldVisibleImages. - nsTHashtable< nsRefPtrHashKey > oldVisibleImages; - mVisibleImages.SwapElements(oldVisibleImages); + // Remove the entries of the mApproximatelyVisibleFrames hashtable and put + // them in oldApproximatelyVisibleFrames. + VisibleFrames oldApproximatelyVisibleFrames; + mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames); // If we're visualizing visible regions, create a VisibleRegions object to // store information about them. The functions we call will populate this @@ -5926,27 +5940,26 @@ PresShell::RebuildImageVisibility(nsRect* aRect, vis = *aRect; } - MarkImagesInSubtreeVisible(rootFrame, vis, visibleRegions, aRemoveOnly); + MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, visibleRegions, aRemoveOnly); - DecrementVisibleCount(oldVisibleImages, - nsIImageLoadingContent::ON_NONVISIBLE_NO_ACTION, this); + DecApproximateVisibleCount(oldApproximatelyVisibleFrames); NotifyCompositorOfVisibleRegionsChange(this, visibleRegions); } void -PresShell::UpdateImageVisibility() +PresShell::UpdateApproximateFrameVisibility() { - DoUpdateImageVisibility(/* aRemoveOnly = */ false); + DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false); } void -PresShell::DoUpdateImageVisibility(bool aRemoveOnly) +PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) { MOZ_ASSERT(!mPresContext || mPresContext->IsRootContentDocument(), - "updating image visibility on a non-root content document?"); + "Updating approximate frame visibility on a non-root content document?"); - mUpdateImageVisibilityEvent.Revoke(); + mUpdateApproximateFrameVisibilityEvent.Revoke(); if (mHaveShutDown || mIsDestroying) { return; @@ -5955,21 +5968,20 @@ PresShell::DoUpdateImageVisibility(bool aRemoveOnly) // call update on that frame nsIFrame* rootFrame = GetRootFrame(); if (!rootFrame) { - ClearVisibleImagesList( - nsIImageLoadingContent::ON_NONVISIBLE_REQUEST_DISCARD); + ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DISCARD_IMAGES)); return; } - RebuildImageVisibility(/* aRect = */ nullptr, aRemoveOnly); - ClearImageVisibilityVisited(rootFrame->GetView(), true); + RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly); + ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true); -#ifdef DEBUG_IMAGE_VISIBILITY_DISPLAY_LIST - // This can be used to debug the frame walker by comparing beforeImageList and - // mVisibleImages in RebuildImageVisibilityDisplayList to see if they produce - // the same results (mVisibleImages holds the images the display list thinks - // are visible, beforeImageList holds the images the frame walker thinks are - // visible). - nsDisplayListBuilder builder(rootFrame, nsDisplayListBuilder::IMAGE_VISIBILITY, false); +#ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST + // This can be used to debug the frame walker by comparing beforeFrameList + // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see if + // they produce the same results (mApproximatelyVisibleFrames holds the frames the + // display list thinks are visible, beforeFrameList holds the frames the + // frame walker thinks are visible). + nsDisplayListBuilder builder(rootFrame, nsDisplayListBuilder::FRAME_VISIBILITY, false); nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize()); nsIFrame* rootScroll = GetRootScrollFrame(); if (rootScroll) { @@ -5988,31 +6000,31 @@ PresShell::DoUpdateImageVisibility(bool aRemoveOnly) rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list); builder.LeavePresShell(rootFrame, updateRect); - RebuildImageVisibilityDisplayList(list); + RebuildApproximateFrameVisibilityDisplayList(list); - ClearImageVisibilityVisited(rootFrame->GetView(), true); + ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true); list.DeleteAll(); #endif } bool -PresShell::AssumeAllImagesVisible() +PresShell::AssumeAllFramesVisible() { - static bool sImageVisibilityEnabled = true; - static bool sImageVisibilityPrefCached = false; + static bool sFrameVisibilityEnabled = true; + static bool sFrameVisibilityPrefCached = false; - if (!sImageVisibilityPrefCached) { - Preferences::AddBoolVarCache(&sImageVisibilityEnabled, - "layout.imagevisibility.enabled", true); - sImageVisibilityPrefCached = true; + if (!sFrameVisibilityPrefCached) { + Preferences::AddBoolVarCache(&sFrameVisibilityEnabled, + "layout.framevisibility.enabled", true); + sFrameVisibilityPrefCached = true; } - if (!sImageVisibilityEnabled || !mPresContext || !mDocument) { + if (!sFrameVisibilityEnabled || !mPresContext || !mDocument) { return true; } - // We assume all images are visible in print, print preview, chrome, xul, and + // We assume all frames are visible in print, print preview, chrome, xul, and // resource docs and don't keep track of them. if (mPresContext->Type() == nsPresContext::eContext_PrintPreview || mPresContext->Type() == nsPresContext::eContext_Print || @@ -6026,10 +6038,31 @@ PresShell::AssumeAllImagesVisible() } void -PresShell::ScheduleImageVisibilityUpdate() +PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() { - if (AssumeAllImagesVisible()) + if (AssumeAllFramesVisible()) { return; + } + + if (!mPresContext) { + return; + } + + nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver(); + if (!refreshDriver) { + return; + } + + // Ask the refresh driver to update frame visibility soon. + refreshDriver->ScheduleFrameVisibilityUpdate(); +} + +void +PresShell::ScheduleApproximateFrameVisibilityUpdateNow() +{ + if (AssumeAllFramesVisible()) { + return; + } if (!mPresContext->IsRootContentDocument()) { nsPresContext* presContext = mPresContext->GetToplevelContentDocumentPresContext(); @@ -6037,79 +6070,89 @@ PresShell::ScheduleImageVisibilityUpdate() return; MOZ_ASSERT(presContext->IsRootContentDocument(), "Didn't get a root prescontext from GetToplevelContentDocumentPresContext?"); - presContext->PresShell()->ScheduleImageVisibilityUpdate(); + presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow(); return; } - if (mHaveShutDown || mIsDestroying) + if (mHaveShutDown || mIsDestroying) { return; + } - if (mUpdateImageVisibilityEvent.IsPending()) + if (mUpdateApproximateFrameVisibilityEvent.IsPending()) { return; + } RefPtr > ev = - NS_NewRunnableMethod(this, &PresShell::UpdateImageVisibility); + NS_NewRunnableMethod(this, &PresShell::UpdateApproximateFrameVisibility); if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) { - mUpdateImageVisibilityEvent = ev; + mUpdateApproximateFrameVisibilityEvent = ev; } } void -PresShell::EnsureImageInVisibleList(nsIImageLoadingContent* aImage) +PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) { - if (AssumeAllImagesVisible()) { - aImage->IncrementVisibleCount(); + if (!aFrame->TrackingVisibility()) { + return; + } + + if (AssumeAllFramesVisible()) { + aFrame->IncApproximateVisibleCount(); return; } #ifdef DEBUG - // if it has a frame make sure its in this presshell - nsCOMPtr content = do_QueryInterface(aImage); + // Make sure it's in this pres shell. + nsCOMPtr content = aFrame->GetContent(); if (content) { PresShell* shell = static_cast(content->OwnerDoc()->GetShell()); MOZ_ASSERT(!shell || shell == this, "wrong shell"); } #endif - if (mInImageVisibility) { - gfxCriticalNoteOnce << "EnsureImageInVisibleList is re-entering on " + if (mInFrameVisibilityUpdate) { + gfxCriticalNoteOnce << "EnsureFrameInApproximatelyVisibleList is re-entering on " << (NS_IsMainThread() ? "" : "non-") << "main thread"; } - if (!mVisibleImages.Contains(aImage)) { - mVisibleImages.PutEntry(aImage); - aImage->IncrementVisibleCount(); + if (!mApproximatelyVisibleFrames.Contains(aFrame)) { + MOZ_ASSERT(!AssumeAllFramesVisible()); + mApproximatelyVisibleFrames.PutEntry(aFrame); + aFrame->IncApproximateVisibleCount(); } } void -PresShell::RemoveImageFromVisibleList(nsIImageLoadingContent* aImage) +PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) { #ifdef DEBUG - // if it has a frame make sure its in this presshell - nsCOMPtr content = do_QueryInterface(aImage); + // Make sure it's in this pres shell. + nsCOMPtr content = aFrame->GetContent(); if (content) { PresShell* shell = static_cast(content->OwnerDoc()->GetShell()); MOZ_ASSERT(!shell || shell == this, "wrong shell"); } #endif - if (AssumeAllImagesVisible()) { - MOZ_ASSERT(mVisibleImages.Count() == 0, "shouldn't have any images in the table"); + if (AssumeAllFramesVisible()) { + MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0, + "Shouldn't have any frames in the table"); return; } - if (mInImageVisibility) { - gfxCriticalNoteOnce << "RemoveImageFromVisibleList is re-entering on " + if (mInFrameVisibilityUpdate) { + gfxCriticalNoteOnce << "RemoveFrameFromApproximatelyVisibleList is re-entering on " << (NS_IsMainThread() ? "" : "non-") << "main thread"; } - uint32_t count = mVisibleImages.Count(); - mVisibleImages.RemoveEntry(aImage); - if (mVisibleImages.Count() < count) { - // aImage was in the hashtable, so we need to decrement its visible count - aImage->DecrementVisibleCount( - nsIImageLoadingContent::ON_NONVISIBLE_NO_ACTION); + uint32_t count = mApproximatelyVisibleFrames.Count(); + mApproximatelyVisibleFrames.RemoveEntry(aFrame); + + if (aFrame->TrackingVisibility() && + mApproximatelyVisibleFrames.Count() < count) { + // aFrame was in the hashtable, and we're still tracking its visibility, + // so we need to decrement its visible count. + aFrame->DecApproximateVisibleCount(); } } @@ -6188,7 +6231,7 @@ PresShell::Paint(nsView* aViewToPaint, NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell"); NS_ASSERTION(aViewToPaint, "null view"); - MOZ_ASSERT(!mImageVisibilityVisited, "should have been cleared"); + MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared"); if (!mIsActive || mIsZombie) { return; @@ -6859,7 +6902,8 @@ DispatchPointerFromMouseOrTouch(PresShell* aShell, continue; } - WidgetPointerEvent event(touchEvent->mFlags.mIsTrusted, pointerMessage, touchEvent->widget); + WidgetPointerEvent event(touchEvent->IsTrusted(), pointerMessage, + touchEvent->widget); event.isPrimary = i == 0; event.pointerId = touch->Identifier(); event.refPoint = touch->mRefPoint; @@ -6994,12 +7038,12 @@ PresShell::DispatchBeforeKeyboardEventInternal(const nsTArray } aChainIndex = i; - InternalBeforeAfterKeyboardEvent beforeEvent(aEvent.mFlags.mIsTrusted, + InternalBeforeAfterKeyboardEvent beforeEvent(aEvent.IsTrusted(), message, aEvent.widget); beforeEvent.AssignBeforeAfterKeyEventData(aEvent, false); EventDispatcher::Dispatch(eventTarget, mPresContext, &beforeEvent); - if (beforeEvent.mFlags.mDefaultPrevented) { + if (beforeEvent.DefaultPrevented()) { aDefaultPrevented = true; return; } @@ -7028,12 +7072,12 @@ PresShell::DispatchAfterKeyboardEventInternal(const nsTArray > return; } - InternalBeforeAfterKeyboardEvent afterEvent(aEvent.mFlags.mIsTrusted, + InternalBeforeAfterKeyboardEvent afterEvent(aEvent.IsTrusted(), message, aEvent.widget); afterEvent.AssignBeforeAfterKeyEventData(aEvent, false); afterEvent.mEmbeddedCancelled.SetValue(embeddedCancelled); EventDispatcher::Dispatch(eventTarget, mPresContext, &afterEvent); - embeddedCancelled = afterEvent.mFlags.mDefaultPrevented; + embeddedCancelled = afterEvent.DefaultPrevented(); } } @@ -7105,7 +7149,7 @@ PresShell::HandleKeyboardEvent(nsINode* aTarget, *aStatus = nsEventStatus_eConsumeNoDefault; DispatchAfterKeyboardEventInternal(chain, aEvent, false, chainIndex); // No need to forward the event to child process. - aEvent.mFlags.mNoCrossProcessBoundaryForwarding = true; + aEvent.StopCrossProcessForwarding(); return; } @@ -7118,7 +7162,7 @@ PresShell::HandleKeyboardEvent(nsINode* aTarget, EventDispatcher::Dispatch(aTarget, mPresContext, &aEvent, nullptr, aStatus, aEventCB); - if (aEvent.mFlags.mDefaultPrevented) { + if (aEvent.DefaultPrevented()) { // When embedder prevents the default action of actual key event, attribute // 'embeddedCancelled' of after event is false, i.e. |!targetIsIframe|. // On the contrary, if the defult action is prevented by embedded iframe, @@ -7133,8 +7177,7 @@ PresShell::HandleKeyboardEvent(nsINode* aTarget, } // Dispatch after events to all items in the chain. - DispatchAfterKeyboardEventInternal(chain, aEvent, - aEvent.mFlags.mDefaultPrevented); + DispatchAfterKeyboardEventInternal(chain, aEvent, aEvent.DefaultPrevented()); } nsresult @@ -7945,7 +7988,7 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent, bool isHandlingUserInput = false; // XXX How about IME events and input events for plugins? - if (aEvent->mFlags.mIsTrusted) { + if (aEvent->IsTrusted()) { switch (aEvent->mMessage) { case eKeyPress: case eKeyDown: @@ -7960,7 +8003,13 @@ PresShell::HandleEventInternal(WidgetEvent* aEvent, // DOM fullscreen mode. This prevents the browser ESC key // handler from stopping all loads in the document, which // would cause