From f579c98b654dd2909f653ca92c53ee573dd4a9b3 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Sat, 21 Jan 2023 00:07:37 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 1195755: Don't assert recursion depth sanity on Mac, because there is none. r=me (e25096acc1) - Bug 1217940 - remove BindingUtils.h from CycleCollectedJSRuntime.cpp; r=mccr8 (012fad0b80) - Bug 1118285 - The browser.newtab.url preference is abused and should be removed. (ca573649c6) - Bug 1209591 - allow loadURI consumers to expose whether an error page was immediately loaded as result of an error, r=smaug,mak (c033d86f07) - Bug 1167132 - Part 14: [NetworkManager] Move network information into a separate interface (NetStats). r=ethan (87acc048cc) - Bug 1167132 - Part 15: [NetworkManager] Move network information into a separate interface (NetworkInterfaceList). r=echen (a2a96e481e) - Bug 1205240 - Add JSON Validation code in order to prevent invalid file. r=seanlin (8c7261ba8c) - Bug 1215429 - Add import statement in order to access file object in chrome code of TVSimulatorService. r=seanlin (5ba9e78581) - Bug 1217093 - Remove for-each from dom/. r=smaug (5af3efbd62) - var-let (576b2489ec) - Bug 1183440 - Replaces Promise.defer() with the Promise constructor in push tests. r=kitcambridge (16dfaa59b3) - Bug 1191453 - Drop subscriptions for a site when the user revokes push permissions. r=mt,MattN (5edd10e5ad) - Bug 1159641, Part 1 - Skip the permission check in `pushManager.getSubscription()`. r=mt (d399c496d7) - Bug 1159641, Part 2 - Use tasks in the Push permissions test. r=mt (132484c355) - Bug 1206302 - Use DOMException for Push errors. r=mt (5a675714fa) - Bug 1193365 - Disable push debug. r=kitcambridge (1dc20e69b0) - Bug 1219063, Part 1 - Use transactions for updating Push subscription permissions. r=mt (8c28453942) - Bug 1219063, Part 2 - Remove obsolete "push" permission. r=mt (84a36931cd) - Bug 1217065 - Unconditionally ack incoming updates. r=dragana,benbangert (e0bfa4454f) - Bug 1212593 - Fix PushService behavior when we are switching between push servers. r=kcambridge (0afa39e743) - Bug 1206163 - Retry failed register requests on reconnect. r=dragana (6ed1258b15) - Bug 1218591 - Reset the WebSocket retry counter when the server replies. r=dragana (64e800db60) - Bug 1210943 - Drop subscriptions unconditionally if the UAID changes. r=benbangert (52f538a7de) - Bug 1214366 - Part 1: Don't preprocess PushServiceWebSocket.jsm. r=kitcambridge (a78b9fc838) - Bug 1214366 - Part 3: Use getLastVisited equivalent in PushService.jsm. r=kitcambridge,rnewman (bc7004ad32) - Bug 1210896, Part 1 - Use Console.jsm to log Push errors. r=mt (04335cc37f) - Bug 1216683 - For the WebSocket version unregister should return true even if we are offline. r=kitcambridge (0f6e397a03) - Bug 1210896, Part 2 - Use JS errors to reject internal Push promises. r=mt (3546b2f7c8) - Bug 1223481 - Use the "potentially trustworthy origin" helper to validate Push server URLs. r=dragana (0c21f551f3) - Bug 1223202 - Only send subscription change events if the Push permission is granted. r=mt (afeaf0dceb) - Bug 1201128 - Don't send channel IDs in the Push handshake. r=nsm (dbbadb5c16) - var-let (a35cb6aeca) - Bug 1210211 - Part 1: Delay updating push quota. r=kitcambridge (53f5735ff0) - Bug 1210211 - Part 2: Notify Push service of visible notifications. r=baku (9182bcb7d1) - Bug 1170115 - Use clear-origin-data to remove Push records. r=allstars.chh (47f1070bab) - Bug 1211418 - Part 1: Ensure Data Consistency after Collision of SMS Segment. r=echen. (f2d5221984) - Bug 1211418 - Part 2: Add Test Coverage for the Collision of SMS Segment. r=echen. (06f7ba7308) - Bug 1159132 - Part 1: Use dun apn only when config ro.tethering.dun_required is set. r=echen (bbb4fd2798) - Bug 1159132 - Part 2: Set ro.tethering.dun_required when running dun test case. r=echen (11fe9344be) - Bug 1187262 - Let the flag 'Services.io.offline' reference the state of tethering. r=jjong (ee22fd9358) - Bug 1148671 - ipv6 and dual stack support on Lollipop. r=hchang (a9f7dc570e) - Bug 1173671 - just warn if we fail to remove old default routes. r=echen (b4ab24da9f) - Bug 1175817 - [NetworkManager] remove old default routes explicitly. r=echen,smaug (3f9a0b98ab) - Bug 1174998 - Part 1: add setMtu() support in NetworkService. r=echen,smaug (9621036470) - Bug 1174998 - Part 2: Set MTU for connected network interfaces. r=echen (397c898942) - Bug 1197667 - [NetworkManager] Part 1: add missing implementation for 'allNetworkInfo'. r=echen (a49fd3498b) - Bug 1197667 - [NetworkManager] Part 2: add test case for 'allNetworkInfo'. r=echen (942a52b0d4) - Bug 1057091 - Add USB tethring command supporting IPv6 outgoing interface. r=hchang (9210eb5a1d) - Bug 1177236 - Usage alert doesn't work when tethering is enabled. r=ethan (4bdd8ae226) - Bug 1168938 - Memory safety bug in NetworkUtils::postTetherInterfaceList. r=fabrice (97485ac95c) - Bug 1138757 - Part 1: Fix the logic of checking invalid port in CDMA WAP Push. r=echen (68dac00e52) - Bug 1138757 - Part 2: Add Test Coverage for CDMA Wap Push. r=echen (9d54278aa9) - Bug 1209891 - Do Not Reply Read-Report if a MMS Message Was Marked from Unread to Read Multiple Times. r=echen (421550db06) - var-let (2ed380bb64) - bug 1175005: performance regression. backout_f081c464c1e2 (28e1ee74b9) - Bug 1207665 - Block Intel GMA 3150 for d3d11/d2d on all drivers. (bug 1207665 part 1, r=jrmuizel). r=jrmuizel (bb8eac6fa8) - Bug 1188105: Parse bad driver versions. r=botond (8c856cac36) - Bug 1075089 - Move popup menu frame offset to LookAndFeel and fix default offset for OS X. r=Enn (e1f7d0c418) - Bug 1134385. Delete main thread assertion in CompositorVsyncDispatcher. r=kats (0945e91185) - some profiler stuff (d3d68abdad) - Bug 1156283 - Avoid shutdown observer race when shutting down gfx on Mac. r=roc (f66195546b) --- docshell/base/nsDocShell.cpp | 39 +- docshell/base/nsDocShell.h | 8 + docshell/base/nsIDocShell.idl | 13 +- docshell/base/nsIWebNavigation.idl | 10 +- dom/apps/PermissionsTable.jsm | 2 +- dom/base/DOMException.cpp | 3 + dom/base/domerr.msg | 6 + dom/base/test/chrome/title_window.xul | 2 +- dom/base/test/file_XHR_anon.sjs | 2 +- .../test/test_ipc_messagemanager_blob.html | 2 +- .../BrowserElementPromptService.jsm | 2 +- dom/events/test/test_bug336682_2.xul | 2 +- dom/events/test/test_bug822898.html | 4 +- dom/html/test/test_checked.html | 24 +- .../push/nsIPushNotificationService.idl | 18 + dom/media/tests/mochitest/network.js | 2 +- dom/mobilemessage/gonk/MmsService.js | 2 +- dom/mobilemessage/gonk/MobileMessageDB.jsm | 61 +- dom/mobilemessage/gonk/SmsService.js | 33 +- .../tests/marionette/manifest.ini | 1 + .../test_mmdb_ports_in_cdma_wappush.js | 74 ++ .../marionette/test_mmdb_upgradeSchema_22.js | 2 +- .../marionette/test_mt_sms_concatenation.js | 34 +- dom/network/NetworkStatsService.jsm | 34 +- dom/network/NetworkStatsServiceProxy.js | 20 +- .../nsINetworkStatsServiceProxy.idl | 8 +- .../tests/unit_stats/test_networkstats_db.js | 2 +- .../unit_stats/test_networkstats_service.js | 4 +- .../test_networkstats_service_proxy.js | 50 +- dom/notification/Notification.cpp | 34 + dom/payment/Payment.jsm | 4 +- dom/push/Push.js | 242 ++--- dom/push/PushClient.js | 56 +- dom/push/PushDB.jsm | 150 ++-- dom/push/PushManager.cpp | 98 ++- dom/push/PushNotificationService.js | 29 +- dom/push/PushRecord.jsm | 99 ++- dom/push/PushService.jsm | 828 +++++++++++------- dom/push/PushServiceHttp2.jsm | 220 ++--- dom/push/PushServiceWebSocket.jsm | 483 +++++----- dom/push/moz.build | 5 +- dom/push/test/test_multiple_register.html | 1 - .../test/test_serviceworker_lifetime.html | 2 +- dom/push/test/xpcshell/head.js | 13 +- .../test/xpcshell/test_clearAll_successful.js | 1 - .../test/xpcshell/test_clear_origin_data.js | 144 +++ dom/push/test/xpcshell/test_drop_expired.js | 153 ++++ .../test/xpcshell/test_notification_ack.js | 12 +- .../test/xpcshell/test_notification_data.js | 193 ++++ .../xpcshell/test_notification_duplicate.js | 14 +- .../test/xpcshell/test_notification_error.js | 19 +- .../xpcshell/test_notification_incomplete.js | 14 +- .../test_notification_version_string.js | 15 +- dom/push/test/xpcshell/test_permissions.js | 268 ++++++ dom/push/test/xpcshell/test_quota_exceeded.js | 12 +- dom/push/test/xpcshell/test_quota_observer.js | 56 +- .../test/xpcshell/test_reconnect_retry.js | 72 ++ .../xpcshell/test_register_5xxCode_http2.js | 5 - dom/push/test/xpcshell/test_register_case.js | 6 +- .../xpcshell/test_register_error_http2.js | 30 +- dom/push/test/xpcshell/test_register_flush.js | 12 +- .../xpcshell/test_register_invalid_channel.js | 5 +- .../test_register_invalid_endpoint.js | 5 +- .../xpcshell/test_register_invalid_json.js | 11 +- dom/push/test/xpcshell/test_register_no_id.js | 11 +- .../xpcshell/test_register_request_queue.js | 19 +- .../test/xpcshell/test_register_rollback.js | 12 +- .../test/xpcshell/test_register_success.js | 8 - .../xpcshell/test_register_success_http2.js | 8 - .../test/xpcshell/test_register_timeout.js | 27 +- .../test/xpcshell/test_register_wrong_id.js | 11 +- .../test/xpcshell/test_register_wrong_type.js | 13 +- .../test_registration_missing_scope.js | 3 - .../xpcshell/test_registration_success.js | 12 +- .../test_resubscribe_4xxCode_http2.js | 1 - .../test_resubscribe_5xxCode_http2.js | 1 - ...subscribe_listening_for_msg_error_http2.js | 1 - dom/push/test/xpcshell/test_retry_ws.js | 71 ++ .../xpcshell/test_unregister_empty_scope.js | 5 +- .../test/xpcshell/test_unregister_error.js | 7 +- .../xpcshell/test_unregister_invalid_json.js | 6 +- .../test/xpcshell/test_unregister_success.js | 7 +- ...test_updateRecordNoEncryptionKeys_http2.js | 1 - .../test_updateRecordNoEncryptionKeys_ws.js | 3 +- .../test/xpcshell/test_webapps_cleardata.js | 88 -- dom/push/test/xpcshell/xpcshell.ini | 12 +- dom/security/nsContentSecurityManager.cpp | 3 +- dom/simplepush/PushService.jsm | 12 +- dom/svg/test/test_SVGxxxListIndexing.xhtml | 6 +- dom/svg/test/test_pathAnimInterpolation.xhtml | 10 +- .../gonk/NetworkInterfaceListService.js | 12 +- dom/system/gonk/NetworkManager.js | 62 +- dom/system/gonk/NetworkService.js | 100 ++- dom/system/gonk/NetworkUtils.cpp | 267 +++++- dom/system/gonk/NetworkUtils.h | 23 +- dom/system/gonk/TetheringService.js | 110 ++- dom/system/gonk/nsINetworkInterface.idl | 7 +- .../gonk/nsINetworkInterfaceListService.idl | 9 +- dom/system/gonk/nsINetworkService.idl | 39 +- dom/system/gonk/nsITetheringService.idl | 13 +- dom/system/gonk/ril_worker.js | 3 +- dom/system/gonk/tests/marionette/head.js | 29 + dom/system/gonk/tests/marionette/manifest.ini | 1 + .../tests/marionette/test_all_network_info.js | 106 +++ .../test_network_interface_list_service.js | 30 +- dom/tethering/tests/marionette/head.js | 9 + dom/tethering/tests/marionette/manifest.ini | 2 + .../marionette/test_wifi_tethering_dun.js | 1 + dom/tv/TVSimulatorService.js | 117 ++- dom/tv/TVTuner.cpp | 2 +- dom/voicemail/test/marionette/head.js | 6 +- dom/webidl/NetworkOptions.webidl | 3 +- dom/wifi/WifiWorker.js | 3 +- .../test/extensions/bootstrap/bootstrap.js | 2 +- dom/workers/test/test_extensionBootstrap.xul | 4 +- embedding/browser/nsIWebBrowserChrome3.idl | 5 +- gfx/tests/gtest/TestGfxWidgets.cpp | 29 + js/xpconnect/src/xpc.msg | 1 + layout/xul/nsMenuPopupFrame.cpp | 41 +- layout/xul/nsMenuPopupFrame.h | 7 - media/mtransport/gonk_addrs.cpp | 16 +- modules/libpref/init/all.js | 11 +- .../tests/unit/test_single_finger_desktop.py | 5 +- toolkit/content/tests/chrome/popup_trigger.js | 23 +- .../content/tests/chrome/test_bug624329.xul | 19 +- .../tests/chrome/test_contextmenu_list.xul | 36 +- .../content/tests/chrome/window_largemenu.xul | 18 +- widget/GfxDriverInfo.cpp | 6 + widget/GfxDriverInfo.h | 16 +- widget/GfxInfoX11.cpp | 29 - widget/GfxInfoX11.h | 2 - widget/LookAndFeel.h | 9 +- widget/VsyncDispatcher.cpp | 11 +- widget/cocoa/nsLookAndFeel.mm | 6 + widget/gonk/nsLookAndFeel.cpp | 5 + widget/gtk/nsLookAndFeel.cpp | 4 + widget/nsBaseWidget.cpp | 1 + widget/nsXPLookAndFeel.cpp | 6 + widget/qt/nsLookAndFeel.cpp | 5 + widget/uikit/nsLookAndFeel.mm | 4 + widget/windows/GfxInfo.cpp | 12 + widget/windows/nsLookAndFeel.cpp | 4 + xpcom/base/CycleCollectedJSRuntime.cpp | 3 +- xpcom/base/ErrorList.h | 12 + xpcom/base/nsError.h | 1 + xpfe/appshell/nsContentTreeOwner.cpp | 18 + xpfe/appshell/nsIXULBrowserWindow.idl | 5 +- 147 files changed, 3784 insertions(+), 1679 deletions(-) create mode 100644 dom/mobilemessage/tests/marionette/test_mmdb_ports_in_cdma_wappush.js create mode 100644 dom/push/test/xpcshell/test_clear_origin_data.js create mode 100644 dom/push/test/xpcshell/test_drop_expired.js create mode 100644 dom/push/test/xpcshell/test_notification_data.js create mode 100644 dom/push/test/xpcshell/test_permissions.js create mode 100644 dom/push/test/xpcshell/test_reconnect_retry.js create mode 100644 dom/push/test/xpcshell/test_retry_ws.js delete mode 100644 dom/push/test/xpcshell/test_webapps_cleardata.js create mode 100644 dom/system/gonk/tests/marionette/test_all_network_info.js diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 6a62edb57f..0150474de1 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4630,7 +4630,10 @@ nsDocShell::LoadURIWithOptions(const char16_t* aURI, // what happens if (NS_ERROR_MALFORMED_URI == rv) { - DisplayLoadError(rv, uri, aURI, nullptr); + if (DisplayLoadError(rv, uri, aURI, nullptr) && + (aLoadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } } if (NS_FAILED(rv) || !uri) { @@ -4695,8 +4698,10 @@ nsDocShell::LoadURIWithOptions(const char16_t* aURI, NS_IMETHODIMP nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL, - nsIChannel* aFailedChannel) + nsIChannel* aFailedChannel, + bool* aDisplayedErrorPage) { + *aDisplayedErrorPage = false; // Get prompt and string bundle servcies nsCOMPtr prompter; nsCOMPtr stringBundle; @@ -5068,8 +5073,10 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, if (UseErrorPages()) { // Display an error page - LoadErrorPage(aURI, aURL, errorPage.get(), error.get(), - messageStr.get(), cssClass.get(), aFailedChannel); + nsresult loadedPage = LoadErrorPage(aURI, aURL, errorPage.get(), + error.get(), messageStr.get(), + cssClass.get(), aFailedChannel); + *aDisplayedErrorPage = NS_SUCCEEDED(loadedPage); } else { // The prompter reqires that our private window has a document (or it // asserts). Satisfy that assertion now since GetDoc will force @@ -10325,7 +10332,10 @@ nsDocShell::InternalLoad2(nsIURI* aURI, if (NS_FAILED(rv)) { nsCOMPtr chan(do_QueryInterface(req)); - DisplayLoadError(rv, aURI, nullptr, chan); + if (DisplayLoadError(rv, aURI, nullptr, chan) && + (aFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } } return rv; @@ -11713,7 +11723,7 @@ nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI) // should just do a spec compare, rather than two gets of the scheme and // then the path. -Gagan nsresult rv; - nsAutoCString buf, pref; + nsAutoCString buf; rv = aURI->GetScheme(buf); if (NS_FAILED(rv)) { @@ -11731,16 +11741,17 @@ nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI) } } - rv = Preferences::GetDefaultCString("browser.newtab.url", &pref); - - if (NS_FAILED(rv)) { - return true; + // Check if the webbrowser chrome wants us to proceed - by default it ensures + // aURI is not the newtab URI. + nsCOMPtr browserChrome3 = do_GetInterface(mTreeOwner); + if (browserChrome3) { + bool shouldAdd; + rv = browserChrome3->ShouldAddToSessionHistory(this, aURI, &shouldAdd); + NS_ENSURE_SUCCESS(rv, true); + return shouldAdd; } - rv = aURI->GetSpec(buf); - NS_ENSURE_SUCCESS(rv, true); - - return !buf.Equals(pref); + return true; } nsresult diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h index 20b378945f..6ef1b9f706 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -733,6 +733,14 @@ protected: */ void MaybeInitTiming(); + bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL, + nsIChannel* aFailedChannel) + { + bool didDisplayLoadError = false; + DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError); + return didDisplayLoadError; + } + public: // Event type dispatched by RestorePresentation class RestorePresentationEvent : public nsRunnable diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl index ed24b62afb..16e80f0bf9 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -46,7 +46,7 @@ interface nsITabParent; typedef unsigned long nsLoadFlags; -[scriptable, builtinclass, uuid(9f2babc4-4c2a-4cf7-929f-a1efc325b0df)] +[scriptable, builtinclass, uuid(b1df6e41-c8dd-45c2-bc18-dd330d986214)] interface nsIDocShell : nsIDocShellTreeItem { /** @@ -449,11 +449,14 @@ interface nsIDocShell : nsIDocShellTreeItem * @param aURI nsIURI of the page where the error happened * @param aURL wstring of the page where the error happened * @param aFailedChannel The channel related to this error + * + * Returns whether or not we displayed an error page (note: will always + * return false if in-content error pages are disabled!) */ - void displayLoadError(in nsresult aError, - in nsIURI aURI, - in wstring aURL, - [optional] in nsIChannel aFailedChannel); + boolean displayLoadError(in nsresult aError, + in nsIURI aURI, + in wstring aURL, + [optional] in nsIChannel aFailedChannel); /** * The channel that failed to load and resulted in an error page. diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl index d826289958..d12e669b2f 100644 --- a/docshell/base/nsIWebNavigation.idl +++ b/docshell/base/nsIWebNavigation.idl @@ -16,7 +16,7 @@ interface nsIURI; * location, stop or restart an in process load, or determine where the object * has previously gone. */ -[scriptable, uuid(e186891c-b053-4fe7-a268-a1c80234b8a2)] +[scriptable, uuid(3ade79d4-8cb9-4952-b18d-4f9b63ca0d31)] interface nsIWebNavigation : nsISupports { /** @@ -184,6 +184,12 @@ interface nsIWebNavigation : nsISupports */ const unsigned long LOAD_FLAGS_DISALLOW_INHERIT_OWNER = 0x40000; + /** + * Overwrite the returned error code with a specific result code + * when an error page is displayed. + */ + const unsigned long LOAD_FLAGS_ERROR_LOAD_CHANGES_RV = 0x80000; + /** * This flag specifies that the URI may be submitted to a third-party * server for correction. This should only be applied to non-sensitive @@ -196,8 +202,6 @@ interface nsIWebNavigation : nsISupports */ const unsigned long LOAD_FLAGS_FIXUP_SCHEME_TYPOS = 0x200000; - /* Note that flag 0x80000 is available. */ - /** * Loads a given URI. This will give priority to loading the requested URI * in the object implementing this interface. If it can't be loaded here diff --git a/dom/apps/PermissionsTable.jsm b/dom/apps/PermissionsTable.jsm index 60a4791b74..d7c9047bef 100644 --- a/dom/apps/PermissionsTable.jsm +++ b/dom/apps/PermissionsTable.jsm @@ -596,7 +596,7 @@ this.expandPermissions = function expandPermissions(aPermName, aAccess) { // Add the same suffix to each of the additions. if (tableEntry.additional) { - for each (let additional in tableEntry.additional) { + for (let additional of tableEntry.additional) { permArr = permArr.concat(appendAccessToPermName(additional, requestedSuffixes)); } } diff --git a/dom/base/DOMException.cpp b/dom/base/DOMException.cpp index 8e2b2ffc0e..c5ea640b09 100644 --- a/dom/base/DOMException.cpp +++ b/dom/base/DOMException.cpp @@ -87,6 +87,9 @@ enum DOM4ErrorTypeCodeMap { BtAuthFailureError = 0, BtRmtDevDownError = 0, BtAuthRejectedError = 0, + + /* Push API errors */ + PermissionDeniedError = 0, }; #define DOM4_MSG_DEF(name, message, nsresult) {(nsresult), name, #name, message}, diff --git a/dom/base/domerr.msg b/dom/base/domerr.msg index bf6e5b0379..5c8a63d87a 100644 --- a/dom/base/domerr.msg +++ b/dom/base/domerr.msg @@ -151,5 +151,11 @@ DOM4_MSG_DEF(InvalidStateError, "A mutation operation was attempted on a file st DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to FileHandle.abort.", NS_ERROR_DOM_FILEHANDLE_ABORT_ERR) DOM4_MSG_DEF(QuotaExceededError, "The current file handle exceeded its quota limitations.", NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR) +/* Push API errors. */ +DOM4_MSG_DEF(InvalidStateError, "Invalid service worker registration.", NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR) +DOM4_MSG_DEF(PermissionDeniedError, "User denied permission to use the Push API.", NS_ERROR_DOM_PUSH_DENIED_ERR) +DOM4_MSG_DEF(AbortError, "Error retrieving push subscription.", NS_ERROR_DOM_PUSH_ABORT_ERR) +DOM4_MSG_DEF(NetworkError, "Push service unreachable.", NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE) + DOM_MSG_DEF(NS_ERROR_DOM_JS_EXCEPTION, "A callback threw an exception") DOM_MSG_DEF(NS_ERROR_DOM_DOMEXCEPTION, "A DOMException was thrown") diff --git a/dom/base/test/chrome/title_window.xul b/dom/base/test/chrome/title_window.xul index da2ee24b67..5db180f8bf 100644 --- a/dom/base/test/chrome/title_window.xul +++ b/dom/base/test/chrome/title_window.xul @@ -21,7 +21,7 @@ diff --git a/dom/push/test/xpcshell/head.js b/dom/push/test/xpcshell/head.js index b6b9af91ad..6e5b9b6b63 100644 --- a/dom/push/test/xpcshell/head.js +++ b/dom/push/test/xpcshell/head.js @@ -7,10 +7,12 @@ let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; Cu.import('resource://gre/modules/XPCOMUtils.jsm'); Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/Task.jsm'); Cu.import('resource://gre/modules/Timer.jsm'); Cu.import('resource://gre/modules/Promise.jsm'); Cu.import('resource://gre/modules/Preferences.jsm'); Cu.import('resource://gre/modules/PlacesUtils.jsm'); +Cu.import('resource://gre/modules/ObjectUtils.jsm'); const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {}); const servicePrefs = new Preferences('dom.push.'); @@ -180,7 +182,7 @@ function disableServiceWorkerEvents(...scopes) { for (let scope of scopes) { Services.perms.add( Services.io.newURI(scope, null, null), - 'push', + 'desktop-notification', Ci.nsIPermissionManager.DENY_ACTION ); } @@ -194,7 +196,7 @@ function disableServiceWorkerEvents(...scopes) { */ function setPrefs(prefs = {}) { let defaultPrefs = Object.assign({ - debug: true, + loglevel: 'all', serverURL: 'wss://push.example.org', 'connection.enabled': true, userAgentID: '', @@ -221,6 +223,7 @@ function setPrefs(prefs = {}) { 'http2.retryInterval': 500, 'http2.reset_retry_count_after_ms': 60000, maxQuotaPerSubscription: 16, + quotaUpdateDelay: 3000, }, prefs); for (let pref in defaultPrefs) { servicePrefs.set(pref, defaultPrefs[pref]); @@ -378,7 +381,11 @@ MockWebSocket.prototype = { () => this._listener.onServerClose(this._context, statusCode, reason), () => this._listener.onStop(this._context, Cr.NS_BASE_STREAM_CLOSED) ); - } + }, + + serverInterrupt(result = Cr.NS_ERROR_NET_RESET) { + waterfall(() => this._listener.onStop(this._context, result)); + }, }; /** diff --git a/dom/push/test/xpcshell/test_clearAll_successful.js b/dom/push/test/xpcshell/test_clearAll_successful.js index 199b861f0b..1e759019f1 100644 --- a/dom/push/test/xpcshell/test_clearAll_successful.js +++ b/dom/push/test/xpcshell/test_clearAll_successful.js @@ -25,7 +25,6 @@ add_task(function* test_unregister_success() { quota: Infinity, }); - let unregisterDefer = Promise.defer(); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), diff --git a/dom/push/test/xpcshell/test_clear_origin_data.js b/dom/push/test/xpcshell/test_clear_origin_data.js new file mode 100644 index 0000000000..a7f448a6db --- /dev/null +++ b/dom/push/test/xpcshell/test_clear_origin_data.js @@ -0,0 +1,144 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const {PushDB, PushService, PushServiceWebSocket} = serviceExports; + +const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f'; + +let clearForPattern = Task.async(function* (testRecords, pattern) { + let patternString = JSON.stringify(pattern); + yield PushService._clearOriginData(patternString); + + for (let length = testRecords.length; length--;) { + let test = testRecords[length]; + let originSuffix = ChromeUtils.originAttributesToSuffix( + test.originAttributes); + + let registration = yield PushNotificationService.registration( + test.scope, + originSuffix + ); + + let url = test.scope + originSuffix; + + if (ObjectUtils.deepEqual(test.clearIf, pattern)) { + ok(!registration, 'Should clear registration ' + url + + ' for pattern ' + patternString); + testRecords.splice(length, 1); + } else { + ok(registration, 'Should not clear registration ' + url + + ' for pattern ' + patternString); + } + } +}); + +function run_test() { + do_get_profile(); + setPrefs({ + userAgentID, + requestTimeout: 1000, + retryBaseInterval: 150 + }); + disableServiceWorkerEvents( + 'https://example.org/1' + ); + run_next_test(); +} + +add_task(function* test_webapps_cleardata() { + let db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + let testRecords = [{ + scope: 'https://example.org/1', + originAttributes: { appId: 1 }, + clearIf: { appId: 1, inBrowser: false }, + }, { + scope: 'https://example.org/1', + originAttributes: { appId: 1, inBrowser: true }, + clearIf: { appId: 1 }, + }, { + scope: 'https://example.org/1', + originAttributes: { appId: 2, inBrowser: true }, + clearIf: { appId: 2, inBrowser: true }, + }, { + scope: 'https://example.org/2', + originAttributes: { appId: 1 }, + clearIf: { appId: 1, inBrowser: false }, + }, { + scope: 'https://example.org/2', + originAttributes: { appId: 2, inBrowser: true }, + clearIf: { appId: 2, inBrowser: true }, + }, { + scope: 'https://example.org/3', + originAttributes: { appId: 3, inBrowser: true }, + clearIf: { inBrowser: true }, + }, { + scope: 'https://example.org/3', + originAttributes: { appId: 4, inBrowser: true }, + clearIf: { inBrowser: true }, + }]; + + let unregisterDone; + let unregisterPromise = new Promise(resolve => + unregisterDone = after(testRecords.length, resolve)); + + PushService.init({ + serverURI: "wss://push.example.org", + networkInfo: new MockDesktopNetworkInfo(), + db, + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(data) { + equal(data.messageType, 'hello', 'Handshake: wrong message type'); + equal(data.uaid, userAgentID, 'Handshake: wrong device ID'); + this.serverSendMsg(JSON.stringify({ + messageType: 'hello', + status: 200, + uaid: userAgentID + })); + }, + onRegister(data) { + equal(data.messageType, 'register', 'Register: wrong message type'); + this.serverSendMsg(JSON.stringify({ + messageType: 'register', + status: 200, + channelID: data.channelID, + uaid: userAgentID, + pushEndpoint: 'https://example.com/update/' + Math.random(), + })); + }, + onUnregister(data) { + unregisterDone(); + }, + }); + } + }); + + yield Promise.all(testRecords.map(test => + PushNotificationService.register( + test.scope, + ChromeUtils.originAttributesToSuffix(test.originAttributes) + ) + )); + + // Removes records for all scopes with the same app ID. Excludes records + // where `inBrowser` is true. + yield clearForPattern(testRecords, { appId: 1, inBrowser: false }); + + // Removes the remaining record for app ID 1, where `inBrowser` is true. + yield clearForPattern(testRecords, { appId: 1 }); + + // Removes all records for all scopes with the same app ID, where + // `inBrowser` is true. + yield clearForPattern(testRecords, { appId: 2, inBrowser: true }); + + // Removes all records where `inBrowser` is true. + yield clearForPattern(testRecords, { inBrowser: true }); + + equal(testRecords.length, 0, 'Should remove all test records'); + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, + 'Timed out waiting for unregister'); +}); diff --git a/dom/push/test/xpcshell/test_drop_expired.js b/dom/push/test/xpcshell/test_drop_expired.js new file mode 100644 index 0000000000..3db8441d8c --- /dev/null +++ b/dom/push/test/xpcshell/test_drop_expired.js @@ -0,0 +1,153 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const {PushDB, PushService, PushServiceWebSocket} = serviceExports; + +const userAgentID = '2c43af06-ab6e-476a-adc4-16cbda54fb89'; + +var db; +var quotaURI; +var permURI; + +function visitURI(uri, timestamp) { + return addVisit({ + uri: uri, + title: uri.spec, + visits: [{ + visitDate: timestamp * 1000, + transitionType: Ci.nsINavHistoryService.TRANSITION_LINK, + }], + }); +} + +var putRecord = Task.async(function* ({scope, perm, quota, lastPush, lastVisit}) { + let uri = Services.io.newURI(scope, null, null); + + Services.perms.add(uri, 'desktop-notification', + Ci.nsIPermissionManager[perm]); + do_register_cleanup(() => { + Services.perms.remove(uri, 'desktop-notification'); + }); + + yield visitURI(uri, lastVisit); + + yield db.put({ + channelID: uri.path, + pushEndpoint: 'https://example.org/push' + uri.path, + scope: uri.spec, + pushCount: 0, + lastPush: lastPush, + version: null, + originAttributes: '', + quota: quota, + }); + + return uri; +}); + +function run_test() { + do_get_profile(); + setPrefs({ + userAgentID: userAgentID, + }); + + db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + run_next_test(); +} + +add_task(function* setUp() { + // An expired registration that should be evicted on startup. Permission is + // granted for this origin, and the last visit is more recent than the last + // push message. + yield putRecord({ + scope: 'https://example.com/expired-quota-restored', + perm: 'ALLOW_ACTION', + quota: 0, + lastPush: Date.now() - 10, + lastVisit: Date.now(), + }); + + // An expired registration that we should evict when the origin is visited + // again. + quotaURI = yield putRecord({ + scope: 'https://example.xyz/expired-quota-exceeded', + perm: 'ALLOW_ACTION', + quota: 0, + lastPush: Date.now() - 10, + lastVisit: Date.now() - 20, + }); + + // An expired registration that we should evict when permission is granted + // again. + permURI = yield putRecord({ + scope: 'https://example.info/expired-perm-revoked', + perm: 'DENY_ACTION', + quota: 0, + lastPush: Date.now() - 10, + lastVisit: Date.now(), + }); + + // An active registration that we should leave alone. + yield putRecord({ + scope: 'https://example.ninja/active', + perm: 'ALLOW_ACTION', + quota: 16, + lastPush: Date.now() - 10, + lastVisit: Date.now() - 20, + }); + + let subChangePromise = promiseObserverNotification( + 'push-subscription-change', + (subject, data) => data == 'https://example.com/expired-quota-restored' + ); + + PushService.init({ + serverURI: 'wss://push.example.org/', + networkInfo: new MockDesktopNetworkInfo(), + db, + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(request) { + this.serverSendMsg(JSON.stringify({ + messageType: 'hello', + status: 200, + uaid: userAgentID, + })); + }, + }); + }, + }); + + yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, + 'Timed out waiting for subscription change event on startup'); +}); + +add_task(function* test_site_visited() { + let subChangePromise = promiseObserverNotification( + 'push-subscription-change', + (subject, data) => data == 'https://example.xyz/expired-quota-exceeded' + ); + + yield visitURI(quotaURI, Date.now()); + PushService.observe(null, 'idle-daily', ''); + + yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, + 'Timed out waiting for subscription change event after visit'); +}); + +add_task(function* test_perm_restored() { + let subChangePromise = promiseObserverNotification( + 'push-subscription-change', + (subject, data) => data == 'https://example.info/expired-perm-revoked' + ); + + Services.perms.add(permURI, 'desktop-notification', + Ci.nsIPermissionManager.ALLOW_ACTION); + + yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, + 'Timed out waiting for subscription change event after permission'); +}); diff --git a/dom/push/test/xpcshell/test_notification_ack.js b/dom/push/test/xpcshell/test_notification_ack.js index c2b2692f6d..90ab91c26f 100644 --- a/dom/push/test/xpcshell/test_notification_ack.js +++ b/dom/push/test/xpcshell/test_notification_ack.js @@ -54,7 +54,8 @@ add_task(function* test_notification_ack() { ]); let acks = 0; - let ackDefer = Promise.defer(); + let ackDone; + let ackPromise = new Promise(resolve => ackDone = resolve); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -64,11 +65,6 @@ add_task(function* test_notification_ack() { onHello(request) { equal(request.uaid, userAgentID, 'Should send matching device IDs in handshake'); - deepEqual(request.channelIDs.sort(), [ - '21668e05-6da8-42c9-b8ab-9cc3f4d5630c', - '5477bfda-22db-45d4-9614-fee369630260', - '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305' - ], 'Should send matching channel IDs in handshake'); this.serverSendMsg(JSON.stringify({ messageType: 'hello', uaid: userAgentID, @@ -115,7 +111,7 @@ add_task(function* test_notification_ack() { channelID: '5477bfda-22db-45d4-9614-fee369630260', version: 6 }], updates, 'Wrong updates for acknowledgement 3'); - ackDefer.resolve(); + ackDone(); break; default: @@ -128,6 +124,6 @@ add_task(function* test_notification_ack() { yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, 'Timed out waiting for notifications'); - yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, 'Timed out waiting for multiple acknowledgements'); }); diff --git a/dom/push/test/xpcshell/test_notification_data.js b/dom/push/test/xpcshell/test_notification_data.js new file mode 100644 index 0000000000..e5d0b15ece --- /dev/null +++ b/dom/push/test/xpcshell/test_notification_data.js @@ -0,0 +1,193 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const {PushDB, PushService, PushServiceWebSocket} = serviceExports; + +let db; +let userAgentID = 'f5b47f8d-771f-4ea3-b999-91c135f8766d'; + +function run_test() { + do_get_profile(); + setPrefs({ + userAgentID: userAgentID, + }); + run_next_test(); +} + +function putRecord(channelID, scope, publicKey, privateKey) { + return db.put({ + channelID: channelID, + pushEndpoint: 'https://example.org/push/' + channelID, + scope: scope, + pushCount: 0, + lastPush: 0, + originAttributes: '', + quota: Infinity, + p256dhPublicKey: publicKey, + p256dhPrivateKey: privateKey, + }); +} + +add_task(function* test_notification_ack_data() { + db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + yield putRecord( + 'subscription1', + 'https://example.com/page/1', + 'BPCd4gNQkjwRah61LpdALdzZKLLnU5UAwDztQ5_h0QsT26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA', + { + crv: 'P-256', + d: '1jUPhzVsRkzV0vIzwL4ZEsOlKdNOWm7TmaTfzitJkgM', + ext: true, + key_ops: ["deriveBits"], + kty: "EC", + x: '8J3iA1CSPBFqHrUul0At3NkosudTlQDAPO1Dn-HRCxM', + y: '26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA' + } + ); + yield putRecord( + 'subscription2', + 'https://example.com/page/2', + 'BPnWyUo7yMnuMlyKtERuLfWE8a09dtdjHSW2lpC9_BqR5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E', + { + crv: 'P-256', + d: 'lFm4nPsUKYgNGBJb5nXXKxl8bspCSp0bAhCYxbveqT4', + ext: true, + key_ops: ["deriveBits"], + kty: 'EC', + x: '-dbJSjvIye4yXIq0RG4t9YTxrT1212MdJbaWkL38GpE', + y: '5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E' + } + ); + yield putRecord( + 'subscription3', + 'https://example.com/page/3', + 'BDhUHITSeVrWYybFnb7ylVTCDDLPdQWMpf8gXhcWwvaaJa6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI', + { + crv: 'P-256', + d: 'Q1_SE1NySTYzjbqgWwPgrYh7XRg3adqZLkQPsy319G8', + ext: true, + key_ops: ["deriveBits"], + kty: 'EC', + x: 'OFQchNJ5WtZjJsWdvvKVVMIMMs91BYyl_yBeFxbC9po', + y: 'Ja6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI' + } + ); + + let updates = []; + let notifyPromise = promiseObserverNotification('push-notification', function(subject, data) { + let notification = subject.QueryInterface(Ci.nsIPushObserverNotification); + updates.push({ + scope: data, + data: notification.data, + }); + return updates.length == 3; + }); + + let acks = 0; + let ackDone; + let ackPromise = new Promise(resolve => ackDone = resolve); + PushService.init({ + serverURI: "wss://push.example.org/", + networkInfo: new MockDesktopNetworkInfo(), + db, + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(request) { + equal(request.uaid, userAgentID, + 'Should send matching device IDs in handshake'); + this.serverSendMsg(JSON.stringify({ + messageType: 'hello', + uaid: userAgentID, + status: 200, + use_webpush: true, + })); + // subscription1 will send a message with no rs and padding + // length 1. + this.serverSendMsg(JSON.stringify({ + messageType: 'notification', + channelID: 'subscription1', + headers: { + encryption_key: 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"', + encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"', + }, + data: 'NwrrOWPxLE8Sv5Rr0Kep7n0-r_j3rsYrUw_CXPo', + version: 'v1', + })); + }, + onACK(request) { + switch (++acks) { + case 1: + deepEqual([{ + channelID: 'subscription1', + version: 'v1', + }], request.updates, 'Wrong updates for acknowledgement 1'); + // subscription2 will send a message with no rs and padding + // length 16. + this.serverSendMsg(JSON.stringify({ + messageType: 'notification', + channelID: 'subscription2', + headers: { + encryption_key: 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"', + encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"', + }, + data: 'Zt9dEdqgHlyAL_l83385aEtb98ZBilz5tgnGgmwEsl5AOCNgesUUJ4p9qUU', + version: 'v2', + })); + break; + + case 2: + deepEqual([{ + channelID: 'subscription2', + version: 'v2', + }], request.updates, 'Wrong updates for acknowledgement 2'); + // subscription3 will send a message with rs equal 24 and + // padding length 16. + this.serverSendMsg(JSON.stringify({ + messageType: 'notification', + channelID: 'subscription3', + headers: { + encryption_key: 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"', + encryption: 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24', + }, + data: 'LKru3ZzxBZuAxYtsaCfaj_fehkrIvqbVd1iSwnwAUgnL-cTeDD-83blxHXTq7r0z9ydTdMtC3UjAcWi8LMnfY-BFzi0qJAjGYIikDA', + version: 'v3', + })); + break; + + case 3: + deepEqual([{ + channelID: 'subscription3', + version: 'v3', + }], request.updates, 'Wrong updates for acknowledgement 3'); + ackDone(); + break; + + default: + ok(false, 'Unexpected acknowledgement ' + acks); + } + } + }); + } + }); + + yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, + 'Timed out waiting for notifications'); + yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, + 'Timed out waiting for multiple acknowledgements'); + + updates.sort((a, b) => a.scope < b.scope ? -1 : a.scope > b.scope ? 1 : 0); + deepEqual([{ + scope: 'https://example.com/page/1', + data: 'Some message', + }, { + scope: 'https://example.com/page/2', + data: 'Some message', + }, { + scope: 'https://example.com/page/3', + data: 'Some message', + }], updates, 'Wrong data for notifications'); +}); diff --git a/dom/push/test/xpcshell/test_notification_duplicate.js b/dom/push/test/xpcshell/test_notification_duplicate.js index f13695789c..d25fffec7e 100644 --- a/dom/push/test/xpcshell/test_notification_duplicate.js +++ b/dom/push/test/xpcshell/test_notification_duplicate.js @@ -5,9 +5,13 @@ const {PushDB, PushService, PushServiceWebSocket} = serviceExports; +const userAgentID = '1500e7d9-8cbe-4ee6-98da-7fa5d6a39852'; + function run_test() { do_get_profile(); - setPrefs(); + setPrefs({ + userAgentID: userAgentID, + }); disableServiceWorkerEvents( 'https://example.com/1', 'https://example.com/2' @@ -41,8 +45,8 @@ add_task(function* test_notification_duplicate() { let notifyPromise = promiseObserverNotification('push-notification'); let acks = 0; - let ackDefer = Promise.defer(); - let ackDone = after(2, ackDefer.resolve); + let ackDone; + let ackPromise = new Promise(resolve => ackDone = after(2, resolve)); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -53,7 +57,7 @@ add_task(function* test_notification_duplicate() { this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, - uaid: '1500e7d9-8cbe-4ee6-98da-7fa5d6a39852' + uaid: userAgentID, })); this.serverSendMsg(JSON.stringify({ messageType: 'notification', @@ -73,7 +77,7 @@ add_task(function* test_notification_duplicate() { yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, 'Timed out waiting for notifications'); - yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, 'Timed out waiting for stale acknowledgement'); let staleRecord = yield db.getByKeyID( diff --git a/dom/push/test/xpcshell/test_notification_error.js b/dom/push/test/xpcshell/test_notification_error.js index 0871f92982..400422a83b 100644 --- a/dom/push/test/xpcshell/test_notification_error.js +++ b/dom/push/test/xpcshell/test_notification_error.js @@ -5,9 +5,13 @@ const {PushDB, PushService, PushServiceWebSocket} = serviceExports; +const userAgentID = '3c7462fc-270f-45be-a459-b9d631b0d093'; + function run_test() { do_get_profile(); - setPrefs(); + setPrefs({ + userAgentID: userAgentID, + }); disableServiceWorkerEvents( 'https://example.com/a', 'https://example.com/b', @@ -58,8 +62,8 @@ add_task(function* test_notification_error() { ) ]); - let ackDefer = Promise.defer(); - let ackDone = after(records.length, ackDefer.resolve); + let ackDone; + let ackPromise = new Promise(resolve => ackDone = after(records.length, resolve)); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -74,15 +78,10 @@ add_task(function* test_notification_error() { makeWebSocket(uri) { return new MockWebSocket(uri, { onHello(request) { - deepEqual(request.channelIDs.sort(), [ - '3c3930ba-44de-40dc-a7ca-8a133ec1a866', - 'b63f7bef-0a0d-4236-b41e-086a69dfd316', - 'f04f1e46-9139-4826-b2d1-9411b0821283' - ], 'Wrong channel list'); this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, - uaid: '3c7462fc-270f-45be-a459-b9d631b0d093' + uaid: userAgentID, })); this.serverSendMsg(JSON.stringify({ messageType: 'notification', @@ -112,7 +111,7 @@ add_task(function* test_notification_error() { 'Wrong endpoint for notification C'); equal(cPush.version, 4, 'Wrong version for notification C'); - yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, 'Timed out waiting for acknowledgements'); let aRecord = yield db.getByIdentifiers({scope: 'https://example.com/a', diff --git a/dom/push/test/xpcshell/test_notification_incomplete.js b/dom/push/test/xpcshell/test_notification_incomplete.js index ec9606c178..ca41ad9e30 100644 --- a/dom/push/test/xpcshell/test_notification_incomplete.js +++ b/dom/push/test/xpcshell/test_notification_incomplete.js @@ -5,9 +5,13 @@ const {PushDB, PushService, PushServiceWebSocket} = serviceExports; +const userAgentID = '1ca1cf66-eeb4-4df7-87c1-d5c92906ab90'; + function run_test() { do_get_profile(); - setPrefs(); + setPrefs({ + userAgentID: userAgentID, + }); disableServiceWorkerEvents( 'https://example.com/page/1', 'https://example.com/page/2', @@ -57,8 +61,8 @@ add_task(function* test_notification_incomplete() { ok(false, 'Should not deliver malformed updates'); }, 'push-notification', false); - let notificationDefer = Promise.defer(); - let notificationDone = after(2, notificationDefer.resolve); + let notificationDone; + let notificationPromise = new Promise(resolve => notificationDone = after(2, resolve)); let prevHandler = PushServiceWebSocket._handleNotificationReply; PushServiceWebSocket._handleNotificationReply = function _handleNotificationReply() { notificationDone(); @@ -74,7 +78,7 @@ add_task(function* test_notification_incomplete() { this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, - uaid: '1ca1cf66-eeb4-4df7-87c1-d5c92906ab90' + uaid: userAgentID, })); this.serverSendMsg(JSON.stringify({ // Missing "updates" field; should ignore message. @@ -107,7 +111,7 @@ add_task(function* test_notification_incomplete() { } }); - yield waitForPromise(notificationDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(notificationPromise, DEFAULT_TIMEOUT, 'Timed out waiting for incomplete notifications'); let storeRecords = yield db.getAllKeyIDs(); diff --git a/dom/push/test/xpcshell/test_notification_version_string.js b/dom/push/test/xpcshell/test_notification_version_string.js index 4abde87861..c91db8f55a 100644 --- a/dom/push/test/xpcshell/test_notification_version_string.js +++ b/dom/push/test/xpcshell/test_notification_version_string.js @@ -5,9 +5,13 @@ const {PushDB, PushService, PushServiceWebSocket} = serviceExports; +const userAgentID = 'ba31ac13-88d4-4984-8e6b-8731315a7cf8'; + function run_test() { do_get_profile(); - setPrefs(); + setPrefs({ + userAgentID: userAgentID, + }); disableServiceWorkerEvents( 'https://example.net/case' ); @@ -28,7 +32,8 @@ add_task(function* test_notification_version_string() { let notifyPromise = promiseObserverNotification('push-notification'); - let ackDefer = Promise.defer(); + let ackDone; + let ackPromise = new Promise(resolve => ackDone = resolve); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -39,7 +44,7 @@ add_task(function* test_notification_version_string() { this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, - uaid: 'ba31ac13-88d4-4984-8e6b-8731315a7cf8' + uaid: userAgentID, })); this.serverSendMsg(JSON.stringify({ messageType: 'notification', @@ -49,7 +54,7 @@ add_task(function* test_notification_version_string() { }] })); }, - onACK: ackDefer.resolve + onACK: ackDone }); } }); @@ -65,7 +70,7 @@ add_task(function* test_notification_version_string() { 'Wrong push endpoint'); strictEqual(message.version, 4, 'Wrong version'); - yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, 'Timed out waiting for string acknowledgement'); let storeRecord = yield db.getByKeyID( diff --git a/dom/push/test/xpcshell/test_permissions.js b/dom/push/test/xpcshell/test_permissions.js new file mode 100644 index 0000000000..6414b86e3f --- /dev/null +++ b/dom/push/test/xpcshell/test_permissions.js @@ -0,0 +1,268 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const {PushDB, PushService, PushServiceWebSocket} = serviceExports; + +const userAgentID = '2c43af06-ab6e-476a-adc4-16cbda54fb89'; + +let db; + +function run_test() { + do_get_profile(); + setPrefs({ + userAgentID, + }); + + db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + run_next_test(); +} + +let unregisterDefers = {}; + +function putRecord(channelID, scope, quota) { + return db.put({ + channelID: channelID, + pushEndpoint: 'https://example.org/push/' + channelID, + scope: scope, + pushCount: 0, + lastPush: 0, + version: null, + originAttributes: '', + quota: quota, + }); +} + +function makePushPermission(url, capability) { + return { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPermission]), + capability: Ci.nsIPermissionManager[capability], + expireTime: 0, + expireType: Ci.nsIPermissionManager.EXPIRE_NEVER, + principal: Services.scriptSecurityManager.getCodebasePrincipal( + Services.io.newURI(url, null, null) + ), + type: 'desktop-notification', + }; +} + +function promiseSubscriptionChanges(count) { + let notifiedScopes = []; + let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => { + notifiedScopes.push(data); + return notifiedScopes.length == count; + }); + return subChangePromise.then(_ => notifiedScopes.sort()); +} + +function allExpired(...keyIDs) { + return Promise.all(keyIDs.map( + keyID => db.getByKeyID(keyID) + )).then(records => + records.every(record => record.isExpired()) + ); +} + +add_task(function* setUp() { + // Active registration; quota should be reset to 16. Since the quota isn't + // exposed to content, we shouldn't receive a subscription change event. + yield putRecord('active-allow', 'https://example.info/page/1', 8); + + // Expired registration; should be dropped. + yield putRecord('expired-allow', 'https://example.info/page/2', 0); + + // Active registration; should be expired when we change the permission + // to "deny". + yield putRecord('active-deny-changed', 'https://example.xyz/page/1', 16); + + // Two active registrations for a visited site. These will expire when we + // add a "deny" permission. + yield putRecord('active-deny-added-1', 'https://example.net/ham', 16); + yield putRecord('active-deny-added-2', 'https://example.net/green', 8); + + // An already-expired registration for a visited site. We shouldn't send an + // `unregister` request for this one, but still receive an observer + // notification when we restore permissions. + yield putRecord('expired-deny-added', 'https://example.net/eggs', 0); + + // A registration that should not be affected by permission list changes + // because its quota is set to `Infinity`. + yield putRecord('never-expires', 'app://chrome/only', Infinity); + + // A registration that should be dropped when we clear the permission + // list. + yield putRecord('drop-on-clear', 'https://example.edu/lonely', 16); + + let handshakeDone; + let handshakePromise = new Promise(resolve => handshakeDone = resolve); + PushService.init({ + serverURI: 'wss://push.example.org/', + networkInfo: new MockDesktopNetworkInfo(), + db, + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(request) { + this.serverSendMsg(JSON.stringify({ + messageType: 'hello', + status: 200, + uaid: userAgentID, + })); + handshakeDone(); + }, + onUnregister(request) { + let resolve = unregisterDefers[request.channelID]; + equal(typeof resolve, 'function', + 'Dropped unexpected channel ID ' + request.channelID); + delete unregisterDefers[request.channelID]; + resolve(); + }, + onACK(request) {}, + }); + } + }); + yield waitForPromise(handshakePromise, DEFAULT_TIMEOUT, + 'Timed out waiting for handshake'); +}); + +add_task(function* test_permissions_allow_added() { + let subChangePromise = promiseSubscriptionChanges(1); + + yield PushService._onPermissionChange( + makePushPermission('https://example.info', 'ALLOW_ACTION'), + 'added' + ); + let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, + 'Timed out waiting for notifications after adding allow'); + + deepEqual(notifiedScopes, [ + 'https://example.info/page/2', + ], 'Wrong scopes after adding allow'); + + let record = yield db.getByKeyID('active-allow'); + equal(record.quota, 16, + 'Should reset quota for active records after adding allow'); + + record = yield db.getByKeyID('expired-allow'); + ok(!record, 'Should drop expired records after adding allow'); +}); + +add_task(function* test_permissions_allow_deleted() { + let unregisterPromise = new Promise(resolve => unregisterDefers[ + 'active-allow'] = resolve); + + yield PushService._onPermissionChange( + makePushPermission('https://example.info', 'ALLOW_ACTION'), + 'deleted' + ); + + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, + 'Timed out waiting for unregister after deleting allow'); + + let record = yield db.getByKeyID('active-allow'); + ok(record.isExpired(), + 'Should expire active record after deleting allow'); +}); + +add_task(function* test_permissions_deny_added() { + let unregisterPromise = Promise.all([ + new Promise(resolve => unregisterDefers[ + 'active-deny-added-1'] = resolve), + new Promise(resolve => unregisterDefers[ + 'active-deny-added-2'] = resolve), + ]); + + yield PushService._onPermissionChange( + makePushPermission('https://example.net', 'DENY_ACTION'), + 'added' + ); + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, + 'Timed out waiting for notifications after adding deny'); + + let isExpired = yield allExpired( + 'active-deny-added-1', + 'expired-deny-added' + ); + ok(isExpired, 'Should expire all registrations after adding deny'); +}); + +add_task(function* test_permissions_deny_deleted() { + yield PushService._onPermissionChange( + makePushPermission('https://example.net', 'DENY_ACTION'), + 'deleted' + ); + + let isExpired = yield allExpired( + 'active-deny-added-1', + 'expired-deny-added' + ); + ok(isExpired, 'Should retain expired registrations after deleting deny'); +}); + +add_task(function* test_permissions_allow_changed() { + let subChangePromise = promiseSubscriptionChanges(3); + + yield PushService._onPermissionChange( + makePushPermission('https://example.net', 'ALLOW_ACTION'), + 'changed' + ); + + let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT, + 'Timed out waiting for notifications after changing to allow'); + + deepEqual(notifiedScopes, [ + 'https://example.net/eggs', + 'https://example.net/green', + 'https://example.net/ham' + ], 'Wrong scopes after changing to allow'); + + let droppedRecords = yield Promise.all([ + db.getByKeyID('active-deny-added-1'), + db.getByKeyID('active-deny-added-2'), + db.getByKeyID('expired-deny-added'), + ]); + ok(!droppedRecords.some(Boolean), + 'Should drop all expired registrations after changing to allow'); +}); + +add_task(function* test_permissions_deny_changed() { + let unregisterPromise = new Promise(resolve => unregisterDefers[ + 'active-deny-changed'] = resolve); + + yield PushService._onPermissionChange( + makePushPermission('https://example.xyz', 'DENY_ACTION'), + 'changed' + ); + + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, + 'Timed out waiting for unregister after changing to deny'); + + let record = yield db.getByKeyID('active-deny-changed'); + ok(record.isExpired(), + 'Should expire active record after changing to allow'); +}); + +add_task(function* test_permissions_clear() { + let records = yield db.getAllKeyIDs(); + deepEqual(records.map(record => record.keyID).sort(), [ + 'active-allow', + 'active-deny-changed', + 'drop-on-clear', + 'never-expires', + ], 'Wrong records in database before clearing'); + + let unregisterPromise = new Promise(resolve => unregisterDefers[ + 'drop-on-clear'] = resolve); + + yield PushService._onPermissionChange(null, 'cleared'); + + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, + 'Timed out waiting for unregister requests after clearing permissions'); + + records = yield db.getAllKeyIDs(); + deepEqual(records.map(record => record.keyID).sort(), [ + 'never-expires', + ], 'Unrestricted registrations should not be dropped'); +}); diff --git a/dom/push/test/xpcshell/test_quota_exceeded.js b/dom/push/test/xpcshell/test_quota_exceeded.js index 188732c3c5..00a9fb0396 100644 --- a/dom/push/test/xpcshell/test_quota_exceeded.js +++ b/dom/push/test/xpcshell/test_quota_exceeded.js @@ -85,7 +85,9 @@ add_task(function* test_expiration_origin_threshold() { updates++; return updates == 6; }); - let unregisterDefer = Promise.defer(); + + let unregisterDone; + let unregisterPromise = new Promise(resolve => unregisterDone = resolve); PushService.init({ serverURI: 'wss://push.example.org/', @@ -94,10 +96,6 @@ add_task(function* test_expiration_origin_threshold() { makeWebSocket(uri) { return new MockWebSocket(uri, { onHello(request) { - deepEqual(request.channelIDs.sort(), [ - '46cc6f6a-c106-4ffa-bb7c-55c60bd50c41', - 'eb33fc90-c883-4267-b5cb-613969e8e349', - ], 'Wrong active registrations in handshake'); this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, @@ -127,7 +125,7 @@ add_task(function* test_expiration_origin_threshold() { }, onUnregister(request) { equal(request.channelID, 'eb33fc90-c883-4267-b5cb-613969e8e349', 'Unregistered wrong channel ID'); - unregisterDefer.resolve(); + unregisterDone(); }, // We expect to receive acks, but don't care about their // contents. @@ -136,7 +134,7 @@ add_task(function* test_expiration_origin_threshold() { }, }); - yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, 'Timed out waiting for unregister request'); yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, diff --git a/dom/push/test/xpcshell/test_quota_observer.js b/dom/push/test/xpcshell/test_quota_observer.js index 8f0aff599e..d840f6b1db 100644 --- a/dom/push/test/xpcshell/test_quota_observer.js +++ b/dom/push/test/xpcshell/test_quota_observer.js @@ -7,6 +7,8 @@ const {PushDB, PushService, PushServiceWebSocket} = serviceExports; const userAgentID = '28cd09e2-7506-42d8-9e50-b02785adc7ef'; +var db; + function run_test() { do_get_profile(); setPrefs({ @@ -15,12 +17,24 @@ function run_test() { run_next_test(); } +let putRecord = Task.async(function* (perm, record) { + let uri = Services.io.newURI(record.scope, null, null); + + Services.perms.add(uri, 'desktop-notification', + Ci.nsIPermissionManager[perm]); + do_register_cleanup(() => { + Services.perms.remove(uri, 'desktop-notification'); + }); + + yield db.put(record); +}); + add_task(function* test_expiration_history_observer() { - let db = PushServiceWebSocket.newPushDB(); + db = PushServiceWebSocket.newPushDB(); do_register_cleanup(() => db.drop().then(_ => db.close())); // A registration that we'll expire... - yield db.put({ + yield putRecord('ALLOW_ACTION', { channelID: '379c0668-8323-44d2-a315-4ee83f1a9ee9', pushEndpoint: 'https://example.org/push/1', scope: 'https://example.com/deals', @@ -31,11 +45,11 @@ add_task(function* test_expiration_history_observer() { quota: 16, }); - // ...And an expired registration that we'll revive later. - yield db.put({ - channelID: 'eb33fc90-c883-4267-b5cb-613969e8e349', - pushEndpoint: 'https://example.org/push/2', - scope: 'https://example.com/auctions', + // ...And a registration that we'll evict on startup. + yield putRecord('ALLOW_ACTION', { + channelID: '4cb6e454-37cf-41c4-a013-4e3a7fdd0bf1', + pushEndpoint: 'https://example.org/push/3', + scope: 'https://example.com/stuff', pushCount: 0, lastPush: 0, version: null, @@ -52,7 +66,10 @@ add_task(function* test_expiration_history_observer() { }], }); - let unregisterDefer = Promise.defer(); + let unregisterDone; + let unregisterPromise = new Promise(resolve => unregisterDone = resolve); + let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => + data == 'https://example.com/stuff'); PushService.init({ serverURI: 'wss://push.example.org/', @@ -61,9 +78,6 @@ add_task(function* test_expiration_history_observer() { makeWebSocket(uri) { return new MockWebSocket(uri, { onHello(request) { - deepEqual(request.channelIDs, [ - '379c0668-8323-44d2-a315-4ee83f1a9ee9', - ], 'Should not include expired channel IDs'); this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, @@ -79,25 +93,39 @@ add_task(function* test_expiration_history_observer() { }, onUnregister(request) { equal(request.channelID, '379c0668-8323-44d2-a315-4ee83f1a9ee9', 'Dropped wrong channel ID'); - unregisterDefer.resolve(); + unregisterDone(); }, onACK(request) {}, }); } }); - yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT, + 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'); let expiredRecord = yield db.getByKeyID('379c0668-8323-44d2-a315-4ee83f1a9ee9'); strictEqual(expiredRecord.quota, 0, 'Expired record not updated'); let notifiedScopes = []; - let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => { + subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => { notifiedScopes.push(data); return notifiedScopes.length == 2; }); + // Add an expired registration that we'll revive later. + yield putRecord('ALLOW_ACTION', { + channelID: 'eb33fc90-c883-4267-b5cb-613969e8e349', + pushEndpoint: 'https://example.org/push/2', + scope: 'https://example.com/auctions', + pushCount: 0, + lastPush: 0, + version: null, + originAttributes: '', + quota: 0, + }); + // Now visit the site... yield addVisit({ uri: 'https://example.com/another-page', diff --git a/dom/push/test/xpcshell/test_reconnect_retry.js b/dom/push/test/xpcshell/test_reconnect_retry.js new file mode 100644 index 0000000000..aa264fb5b8 --- /dev/null +++ b/dom/push/test/xpcshell/test_reconnect_retry.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const {PushDB, PushService, PushServiceWebSocket} = serviceExports; + +function run_test() { + do_get_profile(); + setPrefs({ + requestTimeout: 10000, + retryBaseInterval: 150 + }); + run_next_test(); +} + +add_task(function* test_reconnect_retry() { + let db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + let registers = 0; + let channelID; + PushService.init({ + serverURI: "wss://push.example.org/", + networkInfo: new MockDesktopNetworkInfo(), + db, + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(request) { + this.serverSendMsg(JSON.stringify({ + messageType: 'hello', + status: 200, + uaid: '083e6c17-1063-4677-8638-ab705aebebc2' + })); + }, + onRegister(request) { + registers++; + if (registers == 1) { + channelID = request.channelID; + this.serverClose(); + return; + } + if (registers == 2) { + equal(request.channelID, channelID, + 'Should retry registers after reconnect'); + } + this.serverSendMsg(JSON.stringify({ + messageType: 'register', + channelID: request.channelID, + pushEndpoint: 'https://example.org/push/' + request.channelID, + status: 200, + })); + } + }); + } + }); + + let registration = yield PushNotificationService.register( + 'https://example.com/page/1', + ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) + ); + let retryEndpoint = 'https://example.org/push/' + channelID; + equal(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for retried request'); + + registration = yield PushNotificationService.register( + 'https://example.com/page/2', + ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) + ); + notEqual(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for new request') + + equal(registers, 3, 'Wrong registration count'); +}); diff --git a/dom/push/test/xpcshell/test_register_5xxCode_http2.js b/dom/push/test/xpcshell/test_register_5xxCode_http2.js index e8e5649ca6..81b431ec18 100644 --- a/dom/push/test/xpcshell/test_register_5xxCode_http2.js +++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js @@ -79,7 +79,6 @@ add_task(function* test1() { PushService.init({ serverURI: serverURL + "/subscribe5xxCode", - service: PushServiceHttp2, db }); @@ -91,15 +90,11 @@ add_task(function* test1() { var subscriptionUri = serverURL + '/subscription'; var pushEndpoint = serverURL + '/pushEndpoint'; var pushReceiptEndpoint = serverURL + '/receiptPushEndpoint'; - equal(newRecord.subscriptionUri, subscriptionUri, - 'Wrong subscription ID in registration record'); equal(newRecord.pushEndpoint, pushEndpoint, 'Wrong push endpoint in registration record'); equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint, 'Wrong push endpoint receipt in registration record'); - equal(newRecord.scope, 'https://example.com/retry5xxCode', - 'Wrong scope in registration record'); let record = yield db.getByKeyID(subscriptionUri); equal(record.subscriptionUri, subscriptionUri, diff --git a/dom/push/test/xpcshell/test_register_case.js b/dom/push/test/xpcshell/test_register_case.js index e0ff47b910..1a922f1487 100644 --- a/dom/push/test/xpcshell/test_register_case.js +++ b/dom/push/test/xpcshell/test_register_case.js @@ -54,12 +54,8 @@ add_task(function* test_register_case() { ); equal(newRecord.pushEndpoint, 'https://example.com/update/case', 'Wrong push endpoint in registration record'); - equal(newRecord.scope, 'https://example.net/case', - 'Wrong scope in registration record'); - let record = yield db.getByKeyID(newRecord.channelID); - equal(record.pushEndpoint, 'https://example.com/update/case', - 'Wrong push endpoint in database record'); + let record = yield db.getByPushEndpoint('https://example.com/update/case'); equal(record.scope, 'https://example.net/case', 'Wrong scope in database record'); }); diff --git a/dom/push/test/xpcshell/test_register_error_http2.js b/dom/push/test/xpcshell/test_register_error_http2.js index 0b2296e617..1dbd364ef8 100644 --- a/dom/push/test/xpcshell/test_register_error_http2.js +++ b/dom/push/test/xpcshell/test_register_error_http2.js @@ -49,10 +49,7 @@ add_task(function* test_pushSubscriptionNoConnection() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Error"); - }, - 'Wrong error for not being able to establish connecion.' + 'Expected error for not being able to establish connecion.' ); let record = yield db.getAllKeyIDs(); @@ -90,10 +87,7 @@ add_task(function* test_pushSubscriptionMissingLocation() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but the answer is bogus"); - }, - 'Wrong error for the missing location header.' + 'Expected error for the missing location header.' ); let record = yield db.getAllKeyIDs(); @@ -117,10 +111,7 @@ add_task(function* test_pushSubscriptionMissingLink() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but the answer is bogus"); - }, - 'Wrong error for the missing link header.' + 'Expected error for the missing link header.' ); let record = yield db.getAllKeyIDs(); @@ -144,10 +135,7 @@ add_task(function* test_pushSubscriptionMissingLink1() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but the answer is bogus"); - }, - 'Wrong error for the missing push endpoint.' + 'Expected error for the missing push endpoint.' ); let record = yield db.getAllKeyIDs(); @@ -171,10 +159,7 @@ add_task(function* test_pushSubscriptionLocationBogus() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Return code 201, but URI is bogus."); - }, - 'Wrong error for the bogus location' + 'Expected error for the bogus location' ); let record = yield db.getAllKeyIDs(); @@ -198,10 +183,7 @@ add_task(function* test_pushSubscriptionNot2xxCode() { PushNotificationService.register( 'https://example.net/page/invalid-response', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes("Error"); - }, - 'Wrong error for not 201 responce code.' + 'Expected error for not 201 responce code.' ); let record = yield db.getAllKeyIDs(); diff --git a/dom/push/test/xpcshell/test_register_flush.js b/dom/push/test/xpcshell/test_register_flush.js index a22e9dada4..ec6474398a 100644 --- a/dom/push/test/xpcshell/test_register_flush.js +++ b/dom/push/test/xpcshell/test_register_flush.js @@ -37,8 +37,8 @@ add_task(function* test_register_flush() { let notifyPromise = promiseObserverNotification('push-notification'); - let ackDefer = Promise.defer(); - let ackDone = after(2, ackDefer.resolve); + let ackDone; + let ackPromise = new Promise(resolve => ackDone = after(2, resolve)); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -80,14 +80,12 @@ add_task(function* test_register_flush() { 'https://example.com/page/2', ''); equal(newRecord.pushEndpoint, 'https://example.org/update/2', 'Wrong push endpoint in record'); - equal(newRecord.scope, 'https://example.com/page/2', - 'Wrong scope in record'); let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT, 'Timed out waiting for notification'); equal(scope, 'https://example.com/page/1', 'Wrong notification scope'); - yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(ackPromise, DEFAULT_TIMEOUT, 'Timed out waiting for acknowledgements'); let prevRecord = yield db.getByKeyID( @@ -97,8 +95,6 @@ add_task(function* test_register_flush() { strictEqual(prevRecord.version, 3, 'Should record version updates sent before register responses'); - let registeredRecord = yield db.getByKeyID(newRecord.channelID); - equal(registeredRecord.pushEndpoint, 'https://example.org/update/2', - 'Wrong new push endpoint'); + let registeredRecord = yield db.getByPushEndpoint('https://example.org/update/2'); ok(!registeredRecord.version, 'Should not record premature updates'); }); diff --git a/dom/push/test/xpcshell/test_register_invalid_channel.js b/dom/push/test/xpcshell/test_register_invalid_channel.js index fa1686a643..88d27284d3 100644 --- a/dom/push/test/xpcshell/test_register_invalid_channel.js +++ b/dom/push/test/xpcshell/test_register_invalid_channel.js @@ -50,10 +50,7 @@ add_task(function* test_register_invalid_channel() { yield rejects( PushNotificationService.register('https://example.com/invalid-channel', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'Invalid channel ID'; - }, - 'Wrong error for invalid channel ID' + 'Expected error for invalid channel ID' ); let record = yield db.getByKeyID(channelID); diff --git a/dom/push/test/xpcshell/test_register_invalid_endpoint.js b/dom/push/test/xpcshell/test_register_invalid_endpoint.js index fc111e8cb0..85bb46a6e8 100644 --- a/dom/push/test/xpcshell/test_register_invalid_endpoint.js +++ b/dom/push/test/xpcshell/test_register_invalid_endpoint.js @@ -52,10 +52,7 @@ add_task(function* test_register_invalid_endpoint() { PushNotificationService.register( 'https://example.net/page/invalid-endpoint', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error && error.includes('Invalid pushEndpoint'); - }, - 'Wrong error for invalid endpoint' + 'Expected error for invalid endpoint' ); let record = yield db.getByKeyID(channelID); diff --git a/dom/push/test/xpcshell/test_register_invalid_json.js b/dom/push/test/xpcshell/test_register_invalid_json.js index f45d3a06d1..febf3ea494 100644 --- a/dom/push/test/xpcshell/test_register_invalid_json.js +++ b/dom/push/test/xpcshell/test_register_invalid_json.js @@ -21,8 +21,8 @@ function run_test() { } add_task(function* test_register_invalid_json() { - let helloDefer = Promise.defer(); - let helloDone = after(2, helloDefer.resolve); + let helloDone; + let helloPromise = new Promise(resolve => helloDone = after(2, resolve)); let registers = 0; PushServiceWebSocket._generateID = () => channelID; @@ -51,13 +51,10 @@ add_task(function* test_register_invalid_json() { yield rejects( PushNotificationService.register('https://example.net/page/invalid-json', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for invalid JSON response' + 'Expected error for invalid JSON response' ); - yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, 'Reconnect after invalid JSON response timed out'); 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 fac560acf8..cdf1547232 100644 --- a/dom/push/test/xpcshell/test_register_no_id.js +++ b/dom/push/test/xpcshell/test_register_no_id.js @@ -23,8 +23,8 @@ function run_test() { add_task(function* test_register_no_id() { let registers = 0; - let helloDefer = Promise.defer(); - let helloDone = after(2, helloDefer.resolve); + let helloDone; + let helloPromise = new Promise(resolve => helloDone = after(2, resolve)); PushServiceWebSocket._generateID = () => channelID; PushService.init({ @@ -55,13 +55,10 @@ add_task(function* test_register_no_id() { yield rejects( PushNotificationService.register('https://example.com/incomplete', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for incomplete register response' + 'Expected error for incomplete register response' ); - yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, 'Reconnect after incomplete register response timed out'); 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 be7b2d761b..7cc2572901 100644 --- a/dom/push/test/xpcshell/test_register_request_queue.js +++ b/dom/push/test/xpcshell/test_register_request_queue.js @@ -21,15 +21,16 @@ add_task(function* test_register_request_queue() { let db = PushServiceWebSocket.newPushDB(); do_register_cleanup(() => {return db.drop().then(_ => db.close());}); - let helloDefer = Promise.defer(); - let onHello = after(2, function onHello(request) { + let onHello; + let helloPromise = new Promise(resolve => onHello = after(2, function onHello(request) { this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, uaid: '54b08a9e-59c6-4ed7-bb54-f4fd60d6f606' })); - helloDefer.resolve(); - }); + resolve(); + })); + PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -54,14 +55,10 @@ add_task(function* test_register_request_queue() { ); yield waitForPromise(Promise.all([ - rejects(firstRegister, function(error) { - return error == 'TimeoutError'; - }, 'Should time out the first request'), - rejects(secondRegister, function(error) { - return error == 'TimeoutError'; - }, 'Should time out the second request') + 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(helloDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, 'Timed out waiting for reconnect'); }); diff --git a/dom/push/test/xpcshell/test_register_rollback.js b/dom/push/test/xpcshell/test_register_rollback.js index 4639e500c8..ddb1eb2d8e 100644 --- a/dom/push/test/xpcshell/test_register_rollback.js +++ b/dom/push/test/xpcshell/test_register_rollback.js @@ -27,7 +27,8 @@ add_task(function* test_register_rollback() { let handshakes = 0; let registers = 0; - let unregisterDefer = Promise.defer(); + let unregisterDone; + let unregisterPromise = new Promise(resolve => unregisterDone = resolve); PushServiceWebSocket._generateID = () => channelID; PushService.init({ serverURI: "wss://push.example.org/", @@ -66,7 +67,7 @@ add_task(function* test_register_rollback() { status: 200, channelID })); - unregisterDefer.resolve(); + unregisterDone(); } }); } @@ -76,14 +77,11 @@ add_task(function* test_register_rollback() { yield rejects( PushNotificationService.register('https://example.com/storage-error', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'universe has imploded'; - }, - 'Wrong error for unregister database failure' + 'Expected error for unregister database failure' ); // Should send an out-of-band unregister request. - yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, 'Unregister request timed out'); equal(handshakes, 1, 'Wrong handshake count'); equal(registers, 1, 'Wrong register count'); diff --git a/dom/push/test/xpcshell/test_register_success.js b/dom/push/test/xpcshell/test_register_success.js index ce14cffd52..82c4365188 100644 --- a/dom/push/test/xpcshell/test_register_success.js +++ b/dom/push/test/xpcshell/test_register_success.js @@ -60,22 +60,14 @@ add_task(function* test_register_success() { 'https://example.org/1', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) ); - equal(newRecord.channelID, channelID, - 'Wrong channel ID in registration record'); equal(newRecord.pushEndpoint, 'https://example.com/update/1', 'Wrong push endpoint in registration record'); - equal(newRecord.scope, 'https://example.org/1', - 'Wrong scope in registration record'); - equal(newRecord.quota, Infinity, - 'Wrong quota in registration record'); let record = yield db.getByKeyID(channelID); equal(record.channelID, channelID, 'Wrong channel ID in database record'); equal(record.pushEndpoint, 'https://example.com/update/1', 'Wrong push endpoint in database record'); - equal(record.scope, 'https://example.org/1', - 'Wrong scope in database record'); equal(record.quota, Infinity, 'Wrong quota in database record'); }); diff --git a/dom/push/test/xpcshell/test_register_success_http2.js b/dom/push/test/xpcshell/test_register_success_http2.js index a993e64e6c..7930ca7d86 100644 --- a/dom/push/test/xpcshell/test_register_success_http2.js +++ b/dom/push/test/xpcshell/test_register_success_http2.js @@ -64,15 +64,11 @@ add_task(function* test_pushSubscriptionSuccess() { var subscriptionUri = serverURL + '/pushSubscriptionSuccesss'; var pushEndpoint = serverURL + '/pushEndpointSuccess'; var pushReceiptEndpoint = serverURL + '/receiptPushEndpointSuccess'; - equal(newRecord.subscriptionUri, subscriptionUri, - 'Wrong subscription ID in registration record'); equal(newRecord.pushEndpoint, pushEndpoint, 'Wrong push endpoint in registration record'); equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint, 'Wrong push endpoint receipt in registration record'); - equal(newRecord.scope, 'https://example.org/1', - 'Wrong scope in registration record'); let record = yield db.getByKeyID(subscriptionUri); equal(record.subscriptionUri, subscriptionUri, @@ -107,15 +103,11 @@ add_task(function* test_pushSubscriptionMissingLink2() { var subscriptionUri = serverURL + '/subscriptionMissingLink2'; var pushEndpoint = serverURL + '/pushEndpointMissingLink2'; var pushReceiptEndpoint = ''; - equal(newRecord.subscriptionUri, subscriptionUri, - 'Wrong subscription ID in registration record'); equal(newRecord.pushEndpoint, pushEndpoint, 'Wrong push endpoint in registration record'); equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint, 'Wrong push endpoint receipt in registration record'); - equal(newRecord.scope, 'https://example.org/no_receiptEndpoint', - 'Wrong scope in registration record'); let record = yield db.getByKeyID(subscriptionUri); equal(record.subscriptionUri, subscriptionUri, diff --git a/dom/push/test/xpcshell/test_register_timeout.js b/dom/push/test/xpcshell/test_register_timeout.js index 31fdde999c..12e604f6b4 100644 --- a/dom/push/test/xpcshell/test_register_timeout.js +++ b/dom/push/test/xpcshell/test_register_timeout.js @@ -22,7 +22,8 @@ function run_test() { add_task(function* test_register_timeout() { let handshakes = 0; - let timeoutDefer = Promise.defer(); + let timeoutDone; + let timeoutPromise = new Promise(resolve => timeoutDone = resolve); let registers = 0; let db = PushServiceWebSocket.newPushDB(); @@ -36,23 +37,14 @@ add_task(function* test_register_timeout() { makeWebSocket(uri) { return new MockWebSocket(uri, { onHello(request) { - switch (handshakes) { - case 0: + if (handshakes === 0) { equal(request.uaid, null, 'Should not include device ID'); - deepEqual(request.channelIDs, [], - 'Should include empty channel list'); - break; - - case 1: + } else if (handshakes === 1) { // Should use the previously-issued device ID when reconnecting, // but should not include the timed-out channel ID. equal(request.uaid, userAgentID, 'Should include device ID on reconnect'); - deepEqual(request.channelIDs, [], - 'Should not include failed channel ID'); - break; - - default: + } else { ok(false, 'Unexpected reconnect attempt ' + handshakes); } handshakes++; @@ -74,7 +66,7 @@ add_task(function* test_register_timeout() { uaid: userAgentID, pushEndpoint: 'https://example.com/update/timeout', })); - timeoutDefer.resolve(); + timeoutDone(); }, 2000); registers++; } @@ -85,17 +77,14 @@ add_task(function* test_register_timeout() { yield rejects( PushNotificationService.register('https://example.net/page/timeout', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for request timeout' + 'Expected error for request timeout' ); let record = yield db.getByKeyID(channelID); ok(!record, 'Should not store records for timed-out responses'); yield waitForPromise( - timeoutDefer.promise, + timeoutPromise, DEFAULT_TIMEOUT, 'Reconnect timed out' ); diff --git a/dom/push/test/xpcshell/test_register_wrong_id.js b/dom/push/test/xpcshell/test_register_wrong_id.js index 96966ecea3..7fc47cb948 100644 --- a/dom/push/test/xpcshell/test_register_wrong_id.js +++ b/dom/push/test/xpcshell/test_register_wrong_id.js @@ -25,8 +25,8 @@ function run_test() { add_task(function* test_register_wrong_id() { // Should reconnect after the register request times out. let registers = 0; - let helloDefer = Promise.defer(); - let helloDone = after(2, helloDefer.resolve); + let helloDone; + let helloPromise = new Promise(resolve => helloDone = after(2, resolve)); PushServiceWebSocket._generateID = () => clientChannelID; PushService.init({ @@ -61,13 +61,10 @@ add_task(function* test_register_wrong_id() { yield rejects( PushNotificationService.register('https://example.com/mismatched', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for mismatched register reply' + 'Expected error for mismatched register reply' ); - yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, 'Reconnect after mismatched register reply timed out'); 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 ddedcf302a..9793d4f949 100644 --- a/dom/push/test/xpcshell/test_register_wrong_type.js +++ b/dom/push/test/xpcshell/test_register_wrong_type.js @@ -21,8 +21,8 @@ function run_test() { add_task(function* test_register_wrong_type() { let registers = 0; - let helloDefer = Promise.defer(); - let helloDone = after(2, helloDefer.resolve); + let helloDone; + let helloPromise = new Promise(resolve => helloDone = after(2, resolve)); PushService._generateID = () => '1234'; PushService.init({ @@ -52,18 +52,13 @@ add_task(function* test_register_wrong_type() { } }); - let promise = - yield rejects( PushNotificationService.register('https://example.com/mistyped', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error == 'TimeoutError'; - }, - 'Wrong error for non-string channel ID' + 'Expected error for non-string channel ID' ); - yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(helloPromise, DEFAULT_TIMEOUT, 'Reconnect after sending non-string channel ID timed out'); equal(registers, 1, 'Wrong register count'); }); diff --git a/dom/push/test/xpcshell/test_registration_missing_scope.js b/dom/push/test/xpcshell/test_registration_missing_scope.js index c32955aead..b1b4a79a02 100644 --- a/dom/push/test/xpcshell/test_registration_missing_scope.js +++ b/dom/push/test/xpcshell/test_registration_missing_scope.js @@ -21,9 +21,6 @@ add_task(function* test_registration_missing_scope() { }); yield rejects( PushNotificationService.registration('', ''), - function(error) { - return error.error == 'NotFoundError'; - }, 'Record missing page and manifest URLs' ); }); diff --git a/dom/push/test/xpcshell/test_registration_success.js b/dom/push/test/xpcshell/test_registration_success.js index 71dc1e2d1e..e4c81f3b6e 100644 --- a/dom/push/test/xpcshell/test_registration_success.js +++ b/dom/push/test/xpcshell/test_registration_success.js @@ -42,7 +42,8 @@ add_task(function* test_registration_success() { yield db.put(record); } - let handshakeDefer = Promise.defer(); + let handshakeDone; + let handshakePromise = new Promise(resolve => handshakeDone = resolve); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -50,24 +51,19 @@ add_task(function* test_registration_success() { return new MockWebSocket(uri, { onHello(request) { equal(request.uaid, userAgentID, 'Wrong device ID in handshake'); - deepEqual(request.channelIDs.sort(), [ - 'b1cf38c9-6836-4d29-8a30-a3e98d59b728', - 'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b', - 'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f', - ], 'Wrong channel list in handshake'); this.serverSendMsg(JSON.stringify({ messageType: 'hello', status: 200, uaid: userAgentID })); - handshakeDefer.resolve(); + handshakeDone(); } }); } }); yield waitForPromise( - handshakeDefer.promise, + handshakePromise, DEFAULT_TIMEOUT, 'Timed out waiting for handshake' ); diff --git a/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js b/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js index 7ce2c286e4..c8b43c3f40 100644 --- a/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js +++ b/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js @@ -83,7 +83,6 @@ add_task(function* test1() { PushService.init({ serverURI: serverURL + "/subscribe", - service: PushServiceHttp2, db }); diff --git a/dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js b/dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js index e5cd52368e..0f7739e2ff 100644 --- a/dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js +++ b/dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js @@ -93,7 +93,6 @@ add_task(function* test1() { PushService.init({ serverURI: serverURL + "/subscribe", - service: PushServiceHttp2, db }); diff --git a/dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js b/dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js index 8cd82f6760..7416862c59 100644 --- a/dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js +++ b/dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js @@ -88,7 +88,6 @@ add_task(function* test1() { PushService.init({ serverURI: serverURL + "/subscribe", - service: PushServiceHttp2, db }); diff --git a/dom/push/test/xpcshell/test_retry_ws.js b/dom/push/test/xpcshell/test_retry_ws.js new file mode 100644 index 0000000000..9287bd2ebb --- /dev/null +++ b/dom/push/test/xpcshell/test_retry_ws.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const {PushDB, PushService, PushServiceWebSocket} = serviceExports; + +const userAgentID = '05f7b940-51b6-4b6f-8032-b83ebb577ded'; + +function run_test() { + do_get_profile(); + setPrefs({ + userAgentID: userAgentID, + pingInterval: 10000, + retryBaseInterval: 25, + }); + run_next_test(); +} + +add_task(function* test_ws_retry() { + let db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + yield db.put({ + channelID: '61770ba9-2d57-4134-b949-d40404630d5b', + pushEndpoint: 'https://example.org/push/1', + scope: 'https://example.net/push/1', + version: 1, + originAttributes: '', + quota: Infinity, + }); + + let alarmDelays = []; + let setAlarm = PushService.setAlarm; + PushService.setAlarm = function(delay) { + alarmDelays.push(delay); + setAlarm.apply(this, arguments); + }; + + let handshakeDone; + let handshakePromise = new Promise(resolve => handshakeDone = resolve); + PushService.init({ + serverURI: "wss://push.example.org/", + networkInfo: new MockDesktopNetworkInfo(), + makeWebSocket(uri) { + return new MockWebSocket(uri, { + onHello(request) { + if (alarmDelays.length == 10) { + PushService.setAlarm = setAlarm; + this.serverSendMsg(JSON.stringify({ + messageType: 'hello', + status: 200, + uaid: userAgentID, + })); + handshakeDone(); + return; + } + this.serverInterrupt(); + }, + }); + }, + }); + + 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'); +}); diff --git a/dom/push/test/xpcshell/test_unregister_empty_scope.js b/dom/push/test/xpcshell/test_unregister_empty_scope.js index 63846cf7a3..c3760be121 100644 --- a/dom/push/test/xpcshell/test_unregister_empty_scope.js +++ b/dom/push/test/xpcshell/test_unregister_empty_scope.js @@ -31,9 +31,6 @@ add_task(function* test_unregister_empty_scope() { yield rejects( PushNotificationService.unregister('', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })), - function(error) { - return error.error == 'NotFoundError'; - }, - 'Wrong error for empty endpoint' + 'Expected error for empty endpoint' ); }); diff --git a/dom/push/test/xpcshell/test_unregister_error.js b/dom/push/test/xpcshell/test_unregister_error.js index d485bf3a32..752926fc8b 100644 --- a/dom/push/test/xpcshell/test_unregister_error.js +++ b/dom/push/test/xpcshell/test_unregister_error.js @@ -25,7 +25,8 @@ add_task(function* test_unregister_error() { quota: Infinity, }); - let unregisterDefer = Promise.defer(); + let unregisterDone; + let unregisterPromise = new Promise(resolve => unregisterDone = resolve); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -49,7 +50,7 @@ add_task(function* test_unregister_error() { error: 'omg, everything is exploding', channelID })); - unregisterDefer.resolve(); + unregisterDone(); } }); } @@ -62,6 +63,6 @@ add_task(function* test_unregister_error() { ok(!result, 'Deleted push record exists'); // Make sure we send a request to the server. - yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, 'Timed out waiting for unregister'); }); diff --git a/dom/push/test/xpcshell/test_unregister_invalid_json.js b/dom/push/test/xpcshell/test_unregister_invalid_json.js index dfc4730509..3344299ff4 100644 --- a/dom/push/test/xpcshell/test_unregister_invalid_json.js +++ b/dom/push/test/xpcshell/test_unregister_invalid_json.js @@ -39,8 +39,8 @@ add_task(function* test_unregister_invalid_json() { yield db.put(record); } - let unregisterDefer = Promise.defer(); - let unregisterDone = after(2, unregisterDefer.resolve); + let unregisterDone; + let unregisterPromise = new Promise(resolve => unregisterDone = after(2, resolve)); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -77,6 +77,6 @@ add_task(function* test_unregister_invalid_json() { ok(!record, 'Failed to delete unregistered record after receiving invalid JSON'); - yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, 'Timed out waiting for unregister'); }); diff --git a/dom/push/test/xpcshell/test_unregister_success.js b/dom/push/test/xpcshell/test_unregister_success.js index 247aa55bd3..4cc2a2f4e7 100644 --- a/dom/push/test/xpcshell/test_unregister_success.js +++ b/dom/push/test/xpcshell/test_unregister_success.js @@ -25,7 +25,8 @@ add_task(function* test_unregister_success() { quota: Infinity, }); - let unregisterDefer = Promise.defer(); + let unregisterDone; + let unregisterPromise = new Promise(resolve => unregisterDone = resolve); PushService.init({ serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), @@ -46,7 +47,7 @@ add_task(function* test_unregister_success() { status: 200, channelID })); - unregisterDefer.resolve(); + unregisterDone(); } }); } @@ -57,6 +58,6 @@ add_task(function* test_unregister_success() { let record = yield db.getByKeyID(channelID); ok(!record, 'Unregister did not remove record'); - yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT, + yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT, 'Timed out waiting for unregister'); }); diff --git a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js index a091b566fe..1b7b64ba0b 100644 --- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js +++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js @@ -66,7 +66,6 @@ add_task(function* test1() { PushService.init({ serverURI: serverURL + "/subscribe", - service: PushServiceHttp2, db }); diff --git a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js index 527de69437..a3b8bf5380 100644 --- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js +++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js @@ -77,8 +77,7 @@ add_task(function* test_with_data_enabled() { 'https://example.com/page/3', ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }) ); - ok(newRecord.p256dhPublicKey, 'Should generate public keys for new records'); - ok(newRecord.p256dhPrivateKey, 'Should generate private keys for new records'); + ok(newRecord.p256dhKey, 'Should generate public keys for new records'); let record = yield db.getByKeyID('eb18f12a-cc42-4f14-accb-3bfc1227f1aa'); ok(record.p256dhPublicKey, 'Should add public key to partial record'); diff --git a/dom/push/test/xpcshell/test_webapps_cleardata.js b/dom/push/test/xpcshell/test_webapps_cleardata.js deleted file mode 100644 index 4d23f18595..0000000000 --- a/dom/push/test/xpcshell/test_webapps_cleardata.js +++ /dev/null @@ -1,88 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -'use strict'; - -const {PushDB, PushService, PushServiceWebSocket} = serviceExports; - -const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f'; -const channelIDs = ['0ef2ad4a-6c49-41ad-af6e-95d2425276bf', '4818b54a-97c5-4277-ad5d-0bfe630e4e50']; -var channelIDCounter = 0; - -function run_test() { - do_get_profile(); - setPrefs({ - userAgentID, - requestTimeout: 1000, - retryBaseInterval: 150 - }); - disableServiceWorkerEvents( - 'https://example.org/1' - ); - run_next_test(); -} - -add_task(function* test_webapps_cleardata() { - let db = PushServiceWebSocket.newPushDB(); - do_register_cleanup(() => {return db.drop().then(_ => db.close());}); - - PushService.init({ - serverURI: "wss://push.example.org", - networkInfo: new MockDesktopNetworkInfo(), - db, - makeWebSocket(uri) { - return new MockWebSocket(uri, { - onHello(data) { - equal(data.messageType, 'hello', 'Handshake: wrong message type'); - equal(data.uaid, userAgentID, 'Handshake: wrong device ID'); - this.serverSendMsg(JSON.stringify({ - messageType: 'hello', - status: 200, - uaid: userAgentID - })); - }, - onRegister(data) { - equal(data.messageType, 'register', 'Register: wrong message type'); - this.serverSendMsg(JSON.stringify({ - messageType: 'register', - status: 200, - channelID: data.channelID, - uaid: userAgentID, - pushEndpoint: 'https://example.com/update/' + Math.random(), - })); - } - }); - } - }); - - let registers = yield Promise.all([ - PushNotificationService.register( - 'https://example.org/1', - ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false })), - PushNotificationService.register( - 'https://example.org/1', - ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true })), - ]); - - Services.obs.notifyObservers( - { appId: 1, browserOnly: false, - QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])}, - "webapps-clear-data", ""); - - let waitAWhile = new Promise(function(res) { - setTimeout(res, 2000); - }); - yield waitAWhile; - - let registration; - registration = yield PushNotificationService.registration( - 'https://example.org/1', - ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false })); - ok(!registration, 'Registration for { 1, false } should not exist.'); - - registration = yield PushNotificationService.registration( - 'https://example.org/1', - ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true })); - ok(registration, 'Registration for { 1, true } should still exist.'); -}); - diff --git a/dom/push/test/xpcshell/xpcshell.ini b/dom/push/test/xpcshell/xpcshell.ini index ff3a5162aa..e63acd37bc 100644 --- a/dom/push/test/xpcshell/xpcshell.ini +++ b/dom/push/test/xpcshell/xpcshell.ini @@ -4,11 +4,18 @@ tail = # Push notifications and alarms are currently disabled on Android. skip-if = toolkit == 'android' +[test_clear_origin_data.js] +[test_drop_expired.js] [test_notification_ack.js] +[test_notification_data.js] [test_notification_duplicate.js] [test_notification_error.js] [test_notification_incomplete.js] [test_notification_version_string.js] + +[test_permissions.js] +run-sequentially = This will delete all existing push subscriptions. + [test_quota_exceeded.js] [test_quota_observer.js] [test_register_case.js] @@ -32,8 +39,9 @@ skip-if = toolkit == 'android' [test_unregister_invalid_json.js] [test_unregister_not_found.js] [test_unregister_success.js] -[test_webapps_cleardata.js] [test_updateRecordNoEncryptionKeys_ws.js] +[test_reconnect_retry.js] +[test_retry_ws.js] #http2 test [test_resubscribe_4xxCode_http2.js] [test_resubscribe_5xxCode_http2.js] @@ -60,4 +68,4 @@ skip-if = !hasNode run-sequentially = node server exceptions dont replay well [test_clearAll_successful.js] skip-if = !hasNode -run-sequentially = This will delete all existing push subscritions. +run-sequentially = This will delete all existing push subscriptions. diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp index 7f3c2f0001..bbbb68b9b1 100644 --- a/dom/security/nsContentSecurityManager.cpp +++ b/dom/security/nsContentSecurityManager.cpp @@ -447,7 +447,8 @@ nsContentSecurityManager::IsURIPotentiallyTrustworthy(nsIURI* aURI, bool* aIsTru if (scheme.EqualsLiteral("https") || scheme.EqualsLiteral("file") || - scheme.EqualsLiteral("app")) { + scheme.EqualsLiteral("app") || + scheme.EqualsLiteral("wss")) { *aIsTrustWorthy = true; return NS_OK; } diff --git a/dom/simplepush/PushService.jsm b/dom/simplepush/PushService.jsm index cdbc22b071..b5c9f1f54c 100644 --- a/dom/simplepush/PushService.jsm +++ b/dom/simplepush/PushService.jsm @@ -1576,14 +1576,6 @@ this.PushService = { if (this._UAID) data["uaid"] = this._UAID; - function sendHelloMessage(ids) { - // On success, ids is an array, on error its not. - data["channelIDs"] = ids.map ? - ids.map(function(el) { return el.channelID; }) : []; - this._wsSendMessage(data); - this._currentState = STATE_WAITING_FOR_HELLO; - } - this._getNetworkState((networkState) => { if (networkState.ip) { // Opening an available UDP port. @@ -1602,8 +1594,8 @@ this.PushService = { }; } - this._db.getAllChannelIDs(sendHelloMessage.bind(this), - sendHelloMessage.bind(this)); + this._wsSendMessage(data); + this._currentState = STATE_WAITING_FOR_HELLO; }); }, diff --git a/dom/svg/test/test_SVGxxxListIndexing.xhtml b/dom/svg/test/test_SVGxxxListIndexing.xhtml index 10f6f1ba57..31544b23dd 100644 --- a/dom/svg/test/test_SVGxxxListIndexing.xhtml +++ b/dom/svg/test/test_SVGxxxListIndexing.xhtml @@ -79,13 +79,13 @@ var tests = [ { values: "foo bar baz qux", length: 4 } ] } ]; -for each (let test in tests) { +for (let test of tests) { let list = test.element; - for each (let property in test.listProperty.split(".")) { + for (let property of test.listProperty.split(".")) { list = list[property]; } - for each (let subtest in test.subtests) { + for (let subtest of test.subtests) { if (subtest.values) { test.element.setAttribute(test.attribute, subtest.values); } diff --git a/dom/svg/test/test_pathAnimInterpolation.xhtml b/dom/svg/test/test_pathAnimInterpolation.xhtml index 66b89f09f1..5269db1ca9 100644 --- a/dom/svg/test/test_pathAnimInterpolation.xhtml +++ b/dom/svg/test/test_pathAnimInterpolation.xhtml @@ -267,7 +267,7 @@ function isValidInterpolation(aFromType, aToType) // Runs the test. function run() { - for each (let additive in [false, true]) { + for (let additive of [false, true]) { let indexOfExpectedArguments = additive ? 3 : 2; // Add subtests for each combination of prefix and suffix, and additive @@ -279,7 +279,7 @@ function run() toArguments = suffixEntry[1], expectedArguments = suffixEntry[indexOfExpectedArguments]; - for each (let prefixEntry in gPrefixes) { + for (let prefixEntry of gPrefixes) { let [prefixLength, prefix] = prefixEntry; addTest(prefixLength, prefix, fromType, fromArguments, toType, toArguments, toType, expectedArguments, additive); @@ -298,9 +298,9 @@ function run() "a", [60, 70, 80, 1, 0, 90, 100], additive); // Test all pairs of segment types that cannot be interpolated between. - for each (let fromType in gTypes) { + for (let fromType of gTypes) { let fromArguments = generatePathSegmentArguments(fromType, 0); - for each (let toType in gTypes) { + for (let toType of gTypes) { if (!isValidInterpolation(fromType, toType)) { let toArguments = generatePathSegmentArguments(toType, 1000); addTest(1, "M100,100", fromType, fromArguments, @@ -314,7 +314,7 @@ function run() gSVG.setCurrentTime(4); // Inspect the results of each subtest. - for each (let test in gTests) { + for (let test of gTests) { let list = test.element.animatedPathSegList; is(list.numberOfItems, test.prefixLength + 1, "Length of animatedPathSegList for interpolation " + diff --git a/dom/system/gonk/NetworkInterfaceListService.js b/dom/system/gonk/NetworkInterfaceListService.js index 81f0934ef7..62fe046aad 100644 --- a/dom/system/gonk/NetworkInterfaceListService.js +++ b/dom/system/gonk/NetworkInterfaceListService.js @@ -50,7 +50,7 @@ NetworkInterfaceListService.prototype = { } }; -function FakeNetworkInterface(aAttributes) { +function FakeNetworkInfo(aAttributes) { this.state = aAttributes.state; this.type = aAttributes.type; this.name = aAttributes.name; @@ -58,11 +58,9 @@ function FakeNetworkInterface(aAttributes) { this.prefixLengths = aAttributes.prefixLengths; this.gateways = aAttributes.gateways; this.dnses = aAttributes.dnses; - this.httpProxyHost = aAttributes.httpProxyHost; - this.httpProxyPort = aAttributes.httpProxyPort; } -FakeNetworkInterface.prototype = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]), +FakeNetworkInfo.prototype = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]), getAddresses: function (ips, prefixLengths) { ips.value = this.ips.slice(); @@ -89,7 +87,7 @@ FakeNetworkInterface.prototype = { function NetworkInterfaceList (aInterfaceLiterals) { this._interfaces = []; for (let entry of aInterfaceLiterals) { - this._interfaces.push(new FakeNetworkInterface(entry)); + this._interfaces.push(new FakeNetworkInfo(entry)); } } @@ -100,7 +98,7 @@ NetworkInterfaceList.prototype = { return this._interfaces.length; }, - getInterface: function(index) { + getInterfaceInfo: function(index) { if (!this._interfaces) { return null; } diff --git a/dom/system/gonk/NetworkManager.js b/dom/system/gonk/NetworkManager.js index ee03ba65e5..927551e73e 100644 --- a/dom/system/gonk/NetworkManager.js +++ b/dom/system/gonk/NetworkManager.js @@ -35,6 +35,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gPACGenerator", "@mozilla.org/pac-generator;1", "nsIPACGenerator"); +XPCOMUtils.defineLazyServiceGetter(this, "gTetheringService", + "@mozilla.org/tethering/service;1", + "nsITetheringService"); + const TOPIC_INTERFACE_REGISTERED = "network-interface-registered"; const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered"; const TOPIC_ACTIVE_CHANGED = "network-active-changed"; @@ -99,6 +103,7 @@ function ExtraNetworkInfo(aNetwork) { this.dnses = aNetwork.info.getDnses(); this.httpProxyHost = aNetwork.httpProxyHost; this.httpProxyPort = aNetwork.httpProxyPort; + this.mtu = aNetwork.mtu; } ExtraNetworkInfo.prototype = { getAddresses: function(aIps, aPrefixLengths) { @@ -231,7 +236,9 @@ NetworkManager.prototype = { let excludeFota = aMsg.json.excludeFota; let interfaces = []; - for each (let i in this.networkInterfaces) { + for (let key in this.networkInterfaces) { + let network = this.networkInterfaces[key]; + let i = network.info; if ((i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS && excludeMms) || (i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL && excludeSupl) || (i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS && excludeIms) || @@ -251,9 +258,7 @@ NetworkManager.prototype = { ips: ips.value, prefixLengths: prefixLengths.value, gateways: i.getGateways(), - dnses: i.getDnses(), - httpProxyHost: i.httpProxyHost, - httpProxyPort: i.httpProxyPort + dnses: i.getDnses() }); } return interfaces; @@ -370,6 +375,13 @@ NetworkManager.prototype = { return this.setSecondaryDefaultRoute(extNetworkInfo); }) .then(() => this._addSubnetRoutes(extNetworkInfo)) + .then(() => { + if (extNetworkInfo.mtu <= 0) { + return; + } + + return this._setMtu(extNetworkInfo); + }) .then(() => this.setAndConfigureActive()) .then(() => { // Update data connection when Wifi connected/disconnected @@ -487,6 +499,18 @@ NetworkManager.prototype = { networkInterfaceLinks: null, + get allNetworkInfo() { + let allNetworkInfo = {}; + + for (let networkId in this.networkInterfaces) { + if (this.networkInterfaces.hasOwnProperty(networkId)) { + allNetworkInfo[networkId] = this.networkInterfaces[networkId].info; + } + } + + return allNetworkInfo; + }, + _preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE, get preferredNetworkType() { return this._preferredNetworkType; @@ -802,7 +826,8 @@ NetworkManager.prototype = { this._activeNetwork = null; let anyConnected = false; - for each (let network in this.networkInterfaces) { + for (let key in this.networkInterfaces) { + let network = this.networkInterfaces[key]; if (network.info.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { continue; } @@ -836,7 +861,9 @@ NetworkManager.prototype = { } if (this._manageOfflineStatus) { - Services.io.offline = !anyConnected; + Services.io.offline = !anyConnected && + (gTetheringService.state === + Ci.nsITetheringService.TETHERING_STATE_INACTIVE); } }); }, @@ -932,6 +959,18 @@ NetworkManager.prototype = { }); }, + _setMtu: function(aNetworkInfo) { + return new Promise((aResolve, aReject) => { + gNetworkService.setMtu(aNetworkInfo.name, aNetworkInfo.mtu, (aSuccess) => { + if (!aSuccess) { + debug("setMtu failed"); + } + // Always resolve. + aResolve(); + }); + }); + }, + _createNetwork: function(aInterfaceName) { return new Promise((aResolve, aReject) => { gNetworkService.createNetwork(aInterfaceName, (aSuccess) => { @@ -982,13 +1021,18 @@ NetworkManager.prototype = { }); }, - _setDefaultRouteAndProxy: function(aNetwork, aOldInterface) { + _setDefaultRouteAndProxy: function(aNetwork, aOldNetwork) { + if (aOldNetwork) { + return this._removeDefaultRoute(aOldNetwork.info) + .then(() => this._setDefaultRouteAndProxy(aNetwork, null)); + } + return new Promise((aResolve, aReject) => { let networkInfo = aNetwork.info; let gateways = networkInfo.getGateways(); - let oldInterfaceName = (aOldInterface ? aOldInterface.info.name : ""); + gNetworkService.setDefaultRoute(networkInfo.name, gateways.length, gateways, - oldInterfaceName, (aSuccess) => { + (aSuccess) => { if (!aSuccess) { gNetworkService.destroyNetwork(networkInfo.name, function() { aReject("setDefaultRoute failed"); diff --git a/dom/system/gonk/NetworkService.js b/dom/system/gonk/NetworkService.js index e441330111..e621bccc6a 100644 --- a/dom/system/gonk/NetworkService.js +++ b/dom/system/gonk/NetworkService.js @@ -269,6 +269,58 @@ NetworkService.prototype = { }); }, + setNetworkTetheringAlarm(aEnable, aInterface) { + // Method called when enabling disabling tethering, it checks if there is + // some alarm active and move from interfaceAlarm to globalAlarm because + // interfaceAlarm doens't work in tethering scenario due to forwarding. + debug("setNetworkTetheringAlarm for tethering" + aEnable); + + let filename = aEnable ? "/proc/net/xt_quota/" + aInterface + "Alert" : + "/proc/net/xt_quota/globalAlert"; + + let file = new FileUtils.File(filename); + if (!file) { + return; + } + + NetUtil.asyncFetch({ + uri: NetUtil.newURI(file), + loadUsingSystemPrincipal: true + }, (inputStream, status) => { + if (Components.isSuccessCode(status)) { + let data = NetUtil.readInputStreamToString(inputStream, inputStream.available()) + .split("\n"); + if (data) { + let threshold = parseInt(data[0], 10); + + this._setNetworkTetheringAlarm(aEnable, aInterface, threshold); + } + } + }); + }, + + _setNetworkTetheringAlarm(aEnable, aInterface, aThreshold, aCallback) { + debug("_setNetworkTetheringAlarm for tethering" + aEnable); + + let cmd = aEnable ? "setTetheringAlarm" : "removeTetheringAlarm"; + + let params = { + cmd: cmd, + ifname: aInterface, + threshold: aThreshold, + }; + + this.controlMessage(params, function(aData) { + let code = aData.resultCode; + let reason = aData.resultReason; + let enableString = aEnable ? "Enable" : "Disable"; + debug(enableString + " tethering Alarm result: Code " + code + " reason " + reason); + if (aCallback) { + aCallback.networkUsageAlarmResult(null); + } + }); + }, + setNetworkInterfaceAlarm: function(aInterfaceName, aThreshold, aCallback) { if (!aInterfaceName) { aCallback.networkUsageAlarmResult(-1); @@ -286,7 +338,26 @@ NetworkService.prototype = { return } - self._setNetworkInterfaceAlarm(aInterfaceName, aThreshold, aCallback); + // Check if tethering is enabled + let params = { + cmd: "getTetheringStatus" + }; + + self.controlMessage(params, function(aResult) { + if (isError(aResult.resultCode)) { + aCallback.networkUsageAlarmResult(aResult.reason); + return; + } + + if (aResult.resultReason.indexOf('started') == -1) { + // Tethering disabled, set interfaceAlarm + self._setNetworkInterfaceAlarm(aInterfaceName, aThreshold, aCallback); + return; + } + + // Tethering enabled, set globalAlarm + self._setNetworkTetheringAlarm(true, aInterfaceName, aThreshold, aCallback); + }); }); }, @@ -392,14 +463,11 @@ NetworkService.prototype = { }); }, - setDefaultRoute: function(aInterfaceName, aCount, aGateways, - aOldInterfaceName, aCallback) { + setDefaultRoute: function(aInterfaceName, aCount, aGateways, aCallback) { debug("Going to change default route to " + aInterfaceName); let options = { cmd: "setDefaultRoute", ifname: aInterfaceName, - oldIfname: (aOldInterfaceName && aOldInterfaceName !== aInterfaceName) ? - aOldInterfaceName : null, gateways: aGateways }; this.controlMessage(options, function(aResult) { @@ -545,7 +613,7 @@ NetworkService.prototype = { aConfig.cmd = "setWifiTethering"; // The callback function in controlMessage may not be fired immediately. - this.controlMessage(aConfig, function(aData) { + this.controlMessage(aConfig, (aData) => { let code = aData.resultCode; let reason = aData.resultReason; let enable = aData.enable; @@ -553,6 +621,8 @@ NetworkService.prototype = { debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason); + this.setNetworkTetheringAlarm(aEnable, aConfig.externalIfname); + if (isError(code)) { aCallback.wifiTetheringEnabledChange("netd command error"); } else { @@ -565,7 +635,7 @@ NetworkService.prototype = { setUSBTethering: function(aEnable, aConfig, aCallback) { aConfig.cmd = "setUSBTethering"; // The callback function in controlMessage may not be fired immediately. - this.controlMessage(aConfig, function(aData) { + this.controlMessage(aConfig, (aData) => { let code = aData.resultCode; let reason = aData.resultReason; let enable = aData.enable; @@ -573,6 +643,8 @@ NetworkService.prototype = { debug(enableString + " USB tethering result: Code " + code + " reason " + reason); + this.setNetworkTetheringAlarm(aEnable, aConfig.externalIfname); + if (isError(code)) { aCallback.usbTetheringEnabledChange("netd command error"); } else { @@ -729,6 +801,20 @@ NetworkService.prototype = { }); }); }, + + setMtu: function (aInterfaceName, aMtu, aCallback) { + debug("Set MTU on " + aInterfaceName + ": " + aMtu); + + let params = { + cmd: "setMtu", + ifname: aInterfaceName, + mtu: aMtu + }; + + this.controlMessage(params, function(aResult) { + aCallback.nativeCommandResult(!aResult.error); + }); + } }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkService]); diff --git a/dom/system/gonk/NetworkUtils.cpp b/dom/system/gonk/NetworkUtils.cpp index 4a656bdf74..9b4d7cb0de 100644 --- a/dom/system/gonk/NetworkUtils.cpp +++ b/dom/system/gonk/NetworkUtils.cpp @@ -51,6 +51,11 @@ static const char* USB_FUNCTION_ADB = "adb"; // Use this command to continue the function chain. static const char* DUMMY_COMMAND = "tether status"; +// IPV6 Tethering is not supported in AOSP, use the property to +// identify vendor specific support in IPV6. We can remove this flag +// once upstream Android support IPV6 in tethering. +static const char* IPV6_TETHERING = "ro.tethering.ipv6"; + // Retry 20 times (2 seconds) for usb state transition. static const uint32_t USB_FUNCTION_RETRY_TIMES = 20; // Check "sys.usb.state" every 100ms. @@ -81,6 +86,7 @@ static const uint32_t BUF_SIZE = 1024; static const int32_t SUCCESS = 0; static uint32_t SDK_VERSION; +static uint32_t SUPPORT_IPV6_TETHERING; struct IFProperties { char gateway[PROPERTY_VALUE_MAX]; @@ -201,6 +207,7 @@ const CommandFunc NetworkUtils::sUSBEnableChain[] = { NetworkUtils::tetheringStatus, NetworkUtils::startTethering, NetworkUtils::setDnsForwarders, + NetworkUtils::addUpstreamInterface, NetworkUtils::usbTetheringSuccess }; @@ -209,6 +216,7 @@ const CommandFunc NetworkUtils::sUSBDisableChain[] = { NetworkUtils::removeInterfaceFromLocalNetwork, NetworkUtils::preTetherInterfaceList, NetworkUtils::postTetherInterfaceList, + NetworkUtils::removeUpstreamInterface, NetworkUtils::disableNat, NetworkUtils::setIpForwardingEnabled, NetworkUtils::stopTethering, @@ -223,7 +231,9 @@ const CommandFunc NetworkUtils::sUSBFailChain[] = { const CommandFunc NetworkUtils::sUpdateUpStreamChain[] = { NetworkUtils::cleanUpStream, + NetworkUtils::removeUpstreamInterface, NetworkUtils::createUpStream, + NetworkUtils::addUpstreamInterface, NetworkUtils::updateUpStreamSuccess }; @@ -256,6 +266,23 @@ const CommandFunc NetworkUtils::sNetworkInterfaceSetAlarmChain[] = { NetworkUtils::networkInterfaceAlarmSuccess }; +const CommandFunc NetworkUtils::sTetheringInterfaceSetAlarmChain[] = { + NetworkUtils::setGlobalAlarm, + NetworkUtils::removeAlarm, + NetworkUtils::networkInterfaceAlarmSuccess +}; + +const CommandFunc NetworkUtils::sTetheringInterfaceRemoveAlarmChain[] = { + NetworkUtils::removeGlobalAlarm, + NetworkUtils::setAlarm, + NetworkUtils::networkInterfaceAlarmSuccess +}; + +const CommandFunc NetworkUtils::sTetheringGetStatusChain[] = { + NetworkUtils::tetheringStatus, + NetworkUtils::defaultAsyncSuccessHandler +}; + /** * Helper function to get the mask from given prefix length. */ @@ -710,6 +737,36 @@ void NetworkUtils::setAlarm(CommandChain* aChain, doCommand(command, aChain, aCallback); } +void NetworkUtils::removeAlarm(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char command[MAX_COMMAND_SIZE]; + PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeinterfacealert %s", GET_CHAR(mIfname)); + + doCommand(command, aChain, aCallback); +} + +void NetworkUtils::setGlobalAlarm(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char command[MAX_COMMAND_SIZE]; + + PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setglobalalert %ld", GET_FIELD(mThreshold)); + doCommand(command, aChain, aCallback); +} + +void NetworkUtils::removeGlobalAlarm(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char command[MAX_COMMAND_SIZE]; + + PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeglobalalert"); + doCommand(command, aChain, aCallback); +} + void NetworkUtils::setInterfaceUp(CommandChain* aChain, CommandCallback aCallback, NetworkResultOptions& aResult) @@ -807,12 +864,87 @@ void NetworkUtils::postTetherInterfaceList(CommandChain* aChain, char buf[BUF_SIZE]; NS_ConvertUTF16toUTF8 reason(aResult.mResultReason); - memcpy(buf, reason.get(), reason.Length() + 1); + + size_t length = reason.Length() + 1 < BUF_SIZE ? reason.Length() + 1 : BUF_SIZE; + memcpy(buf, reason.get(), length); split(buf, INTERFACE_DELIMIT, GET_FIELD(mInterfaceList)); doCommand(command, aChain, aCallback); } +bool isCommandChainIPv6(CommandChain* aChain, const char *externalInterface) { + // Check by gateway address + if (getIpType(GET_CHAR(mGateway)) == AF_INET6) { + return true; + } + + uint32_t length = GET_FIELD(mGateways).Length(); + for (uint32_t i = 0; i < length; i++) { + NS_ConvertUTF16toUTF8 autoGateway(GET_FIELD(mGateways)[i]); + if(getIpType(autoGateway.get()) == AF_INET6) { + return true; + } + } + + // Check by external inteface address + FILE *file = fopen("/proc/net/if_inet6", "r"); + if (!file) { + return false; + } + + bool isIPv6 = false; + char interface[32]; + while(fscanf(file, "%*s %*s %*s %*s %*s %32s", interface)) { + if (strcmp(interface, externalInterface) == 0) { + isIPv6 = true; + break; + } + } + + fclose(file); + return isIPv6; +} + +void NetworkUtils::addUpstreamInterface(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + nsCString interface(GET_CHAR(mExternalIfname)); + if (!interface.get()[0]) { + interface = GET_CHAR(mCurExternalIfname); + } + + if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) { + aCallback(aChain, false, aResult); + return; + } + + char command[MAX_COMMAND_SIZE]; + snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add_upstream %s", + interface.get()); + doCommand(command, aChain, aCallback); +} + +void NetworkUtils::removeUpstreamInterface(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + nsCString interface(GET_CHAR(mExternalIfname)); + if (!interface.get()[0]) { + interface = GET_CHAR(mPreExternalIfname); + } + + if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) { + aCallback(aChain, false, aResult); + return; + } + + char command[MAX_COMMAND_SIZE]; + snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove_upstream %s", + interface.get()); + doCommand(command, aChain, aCallback); +} + void NetworkUtils::setIpForwardingEnabled(CommandChain* aChain, CommandCallback aCallback, NetworkResultOptions& aResult) @@ -991,14 +1123,39 @@ void NetworkUtils::removeDefaultRoute(CommandChain* aChain, CommandCallback aCallback, NetworkResultOptions& aResult) { - char command[MAX_COMMAND_SIZE]; - // FIXME: (Bug 1121795) We only remove the first gateway to the default route. - // For dual stack (ipv4/ipv6) device, one of the gateway would - // not be added to the default route. - snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s 0.0.0.0/0 %s", - GET_FIELD(mNetId), GET_CHAR(mIfname), GET_CHAR(mGateways[0])); + if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) { + aCallback(aChain, false, aResult); + return; + } - doCommand(command, aChain, aCallback); + char command[MAX_COMMAND_SIZE]; + nsTArray& gateways = GET_FIELD(mGateways); + NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]); + + int type = getIpType(autoGateway.get()); + snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s %s/0 %s", + GET_FIELD(mNetId), GET_CHAR(mIfname), + type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get()); + + struct MyCallback { + static void callback(CommandCallback::CallbackType aOriginalCallback, + CommandChain* aChain, + bool aError, + mozilla::dom::NetworkResultOptions& aResult) + { + NS_ConvertUTF16toUTF8 reason(aResult.mResultReason); + NU_DBG("removeDefaultRoute's reason: %s", reason.get()); + if (aError && !reason.EqualsASCII("removeRoute() failed (No such process)")) { + return aOriginalCallback(aChain, aError, aResult); + } + + GET_FIELD(mLoopIndex)++; + return removeDefaultRoute(aChain, aOriginalCallback, aResult); + } + }; + + CommandCallback wrappedCallback(MyCallback::callback, aCallback); + doCommand(command, aChain, wrappedCallback); } void NetworkUtils::setInterfaceDns(CommandChain* aChain, @@ -1146,13 +1303,19 @@ void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain, CommandCallback aCallback, NetworkResultOptions& aResult) { - char command[MAX_COMMAND_SIZE]; + if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) { + aCallback(aChain, false, aResult); + return; + } - // FIXME: (Bug 1121795) We only add the first gateway to the default route. - // For dual stack (ipv4/ipv6) device, one of the gateway would - // not be added to the default route. - snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s 0.0.0.0/0 %s", - GET_FIELD(mNetId), GET_CHAR(mIfname), GET_CHAR(mGateways[0])); + char command[MAX_COMMAND_SIZE]; + nsTArray& gateways = GET_FIELD(mGateways); + NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]); + + int type = getIpType(autoGateway.get()); + snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s %s/0 %s", + GET_FIELD(mNetId), GET_CHAR(mIfname), + type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get()); struct MyCallback { static void callback(CommandCallback::CallbackType aOriginalCallback, @@ -1162,11 +1325,12 @@ void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain, { NS_ConvertUTF16toUTF8 reason(aResult.mResultReason); NU_DBG("addDefaultRouteToNetwork's reason: %s", reason.get()); - if (aError && reason.EqualsASCII("addRoute() failed (File exists)")) { - NU_DBG("Ignore \"File exists\" error when adding host route."); - return aOriginalCallback(aChain, false, aResult); + if (aError && !reason.EqualsASCII("addRoute() failed (File exists)")) { + return aOriginalCallback(aChain, aError, aResult); } - aOriginalCallback(aChain, aError, aResult); + + GET_FIELD(mLoopIndex)++; + return addDefaultRouteToNetwork(aChain, aOriginalCallback, aResult); } }; @@ -1272,6 +1436,17 @@ void NetworkUtils::disableIpv6(CommandChain* aChain, setIpv6Enabled(aChain, aCallback, aResult, false); } +void NetworkUtils::setMtu(CommandChain* aChain, + CommandCallback aCallback, + NetworkResultOptions& aResult) +{ + char command[MAX_COMMAND_SIZE]; + PR_snprintf(command, MAX_COMMAND_SIZE - 1, "interface setmtu %s %d", + GET_CHAR(mIfname), GET_FIELD(mMtu)); + + doCommand(command, aChain, aCallback); +} + #undef GET_CHAR #undef GET_FIELD @@ -1450,6 +1625,9 @@ NetworkUtils::NetworkUtils(MessageCallback aCallback) property_get("ro.build.version.sdk", value, nullptr); SDK_VERSION = atoi(value); + property_get(IPV6_TETHERING, value, "0"); + SUPPORT_IPV6_TETHERING = atoi(value); + gNetworkUtils = this; } @@ -1489,6 +1667,9 @@ void NetworkUtils::ExecuteCommand(NetworkParams aOptions) BUILD_ENTRY(setNetworkInterfaceAlarm), BUILD_ENTRY(enableNetworkInterfaceAlarm), BUILD_ENTRY(disableNetworkInterfaceAlarm), + BUILD_ENTRY(setTetheringAlarm), + BUILD_ENTRY(removeTetheringAlarm), + BUILD_ENTRY(getTetheringStatus), BUILD_ENTRY(setWifiOperationMode), BUILD_ENTRY(setDhcpServer), BUILD_ENTRY(setWifiTethering), @@ -1504,6 +1685,7 @@ void NetworkUtils::ExecuteCommand(NetworkParams aOptions) BUILD_ENTRY(createNetwork), BUILD_ENTRY(destroyNetwork), BUILD_ENTRY(getNetId), + BUILD_ENTRY(setMtu), #undef BUILD_ENTRY }; @@ -1822,8 +2004,9 @@ CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions) } aOptions.mNetId = netIdInfo.mNetId; - + aOptions.mLoopIndex = 0; runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler); + return CommandResult::Pending(); } @@ -1834,13 +2017,6 @@ CommandResult NetworkUtils::setDefaultRouteLegacy(NetworkParams& aOptions) { NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname); - if (!aOptions.mOldIfname.IsEmpty()) { - // Remove IPv4's default route. - RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(GET_CHAR(mOldIfname))); - // Remove IPv6's default route. - WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL)); - } - uint32_t length = aOptions.mGateways.Length(); if (length > 0) { for (uint32_t i = 0; i < length; i++) { @@ -1916,6 +2092,7 @@ CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions) NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname)); aOptions.mNetId = netIdInfo.mNetId; + aOptions.mLoopIndex = 0; runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler); return CommandResult::Pending(); @@ -2233,6 +2410,27 @@ CommandResult NetworkUtils::disableNetworkInterfaceAlarm(NetworkParams& aOptions return CommandResult::Pending(); } +CommandResult NetworkUtils::setTetheringAlarm(NetworkParams& aOptions) +{ + NU_DBG("setTetheringAlarm"); + runChain(aOptions, sTetheringInterfaceSetAlarmChain, networkInterfaceAlarmFail); + return CommandResult::Pending(); +} + +CommandResult NetworkUtils::removeTetheringAlarm(NetworkParams& aOptions) +{ + NU_DBG("removeTetheringAlarm"); + runChain(aOptions, sTetheringInterfaceRemoveAlarmChain, networkInterfaceAlarmFail); + return CommandResult::Pending(); +} + +CommandResult NetworkUtils::getTetheringStatus(NetworkParams& aOptions) +{ + NU_DBG("getTetheringStatus"); + runChain(aOptions, sTetheringGetStatusChain, networkInterfaceAlarmFail); + return CommandResult::Pending(); +} + /** * handling main thread's reload Wifi firmware request */ @@ -2526,6 +2724,23 @@ CommandResult NetworkUtils::getNetId(NetworkParams& aOptions) return result; } +CommandResult NetworkUtils::setMtu(NetworkParams& aOptions) +{ + // Setting/getting mtu is supported since Kitkat. + if (SDK_VERSION < 19) { + ERROR("setMtu is not supported in current SDK_VERSION."); + return -1; + } + + static CommandFunc COMMAND_CHAIN[] = { + setMtu, + defaultAsyncSuccessHandler, + }; + + runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler); + return CommandResult::Pending(); +} + void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason) { NetworkResultOptions result; diff --git a/dom/system/gonk/NetworkUtils.h b/dom/system/gonk/NetworkUtils.h index 29fbdcf0ef..d78184fcc5 100644 --- a/dom/system/gonk/NetworkUtils.h +++ b/dom/system/gonk/NetworkUtils.h @@ -110,7 +110,6 @@ public: COPY_OPT_STRING_FIELD(mIfname, EmptyString()) COPY_OPT_STRING_FIELD(mIp, EmptyString()) COPY_OPT_FIELD(mPrefixLength, 0) - COPY_OPT_STRING_FIELD(mOldIfname, EmptyString()) COPY_OPT_STRING_FIELD(mMode, EmptyString()) COPY_OPT_FIELD(mReport, false) COPY_OPT_FIELD(mEnabled, false) @@ -145,6 +144,9 @@ public: COPY_OPT_FIELD(mGateway_long, 0) COPY_OPT_FIELD(mDns1_long, 0) COPY_OPT_FIELD(mDns2_long, 0) + COPY_OPT_FIELD(mMtu, 0) + + mLoopIndex = 0; #undef COPY_SEQUENCE_FIELD #undef COPY_OPT_STRING_FIELD @@ -161,7 +163,6 @@ public: nsString mIfname; nsString mIp; uint32_t mPrefixLength; - nsString mOldIfname; nsString mMode; bool mReport; bool mEnabled; @@ -196,9 +197,11 @@ public: long mGateway_long; long mDns1_long; long mDns2_long; + long mMtu; // Auxiliary information required to carry accros command chain. - int mNetId; // A locally defined id per interface. + int mNetId; // A locally defined id per interface. + uint32_t mLoopIndex; // Loop index for adding/removing multiple gateways. }; // CommandChain store the necessary information to execute command one by one. @@ -299,6 +302,9 @@ private: CommandResult setNetworkInterfaceAlarm(NetworkParams& aOptions); CommandResult enableNetworkInterfaceAlarm(NetworkParams& aOptions); CommandResult disableNetworkInterfaceAlarm(NetworkParams& aOptions); + CommandResult setTetheringAlarm(NetworkParams& aOptions); + CommandResult removeTetheringAlarm(NetworkParams& aOptions); + CommandResult getTetheringStatus(NetworkParams& aOptions); CommandResult setWifiOperationMode(NetworkParams& aOptions); CommandResult setDhcpServer(NetworkParams& aOptions); CommandResult setWifiTethering(NetworkParams& aOptions); @@ -308,6 +314,7 @@ private: CommandResult createNetwork(NetworkParams& aOptions); CommandResult destroyNetwork(NetworkParams& aOptions); CommandResult getNetId(NetworkParams& aOptions); + CommandResult setMtu(NetworkParams& aOptions); CommandResult addHostRouteLegacy(NetworkParams& aOptions); CommandResult removeHostRouteLegacy(NetworkParams& aOptions); @@ -334,7 +341,9 @@ private: static const CommandFunc sNetworkInterfaceEnableAlarmChain[]; static const CommandFunc sNetworkInterfaceDisableAlarmChain[]; static const CommandFunc sNetworkInterfaceSetAlarmChain[]; - + static const CommandFunc sTetheringInterfaceSetAlarmChain[]; + static const CommandFunc sTetheringInterfaceRemoveAlarmChain[]; + static const CommandFunc sTetheringGetStatusChain[]; /** * Individual netd command stored in command chain. */ @@ -354,12 +363,17 @@ private: static void setQuota(PARAMS); static void removeQuota(PARAMS); static void setAlarm(PARAMS); + static void removeAlarm(PARAMS); + static void setGlobalAlarm(PARAMS); + static void removeGlobalAlarm(PARAMS); static void setInterfaceUp(PARAMS); static void tetherInterface(PARAMS); static void addInterfaceToLocalNetwork(PARAMS); static void addRouteToLocalNetwork(PARAMS); static void preTetherInterfaceList(PARAMS); static void postTetherInterfaceList(PARAMS); + static void addUpstreamInterface(PARAMS); + static void removeUpstreamInterface(PARAMS); static void setIpForwardingEnabled(PARAMS); static void tetheringStatus(PARAMS); static void stopTethering(PARAMS); @@ -391,6 +405,7 @@ private: static void modifyRouteOnInterface(PARAMS, bool aDoAdd); static void enableIpv6(PARAMS); static void disableIpv6(PARAMS); + static void setMtu(PARAMS); static void setIpv6Enabled(PARAMS, bool aEnabled); static void addRouteToSecondaryTable(PARAMS); static void removeRouteFromSecondaryTable(PARAMS); diff --git a/dom/system/gonk/TetheringService.js b/dom/system/gonk/TetheringService.js index 09707730ce..c5c4781806 100644 --- a/dom/system/gonk/TetheringService.js +++ b/dom/system/gonk/TetheringService.js @@ -39,12 +39,11 @@ XPCOMUtils.defineLazyGetter(this, "gRil", function() { return null; }); -const TOPIC_INTERFACE_REGISTERED = "network-interface-registered"; -const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered"; const TOPIC_MOZSETTINGS_CHANGED = "mozsettings-changed"; const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed"; const TOPIC_PREF_CHANGED = "nsPref:changed"; const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown"; +const PREF_MANAGE_OFFLINE_STATUS = "network.gonk.manage-offline-status"; const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled"; const POSSIBLE_USB_INTERFACE_NAME = "rndis0,usb0"; @@ -126,9 +125,15 @@ function TetheringService() { Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false); Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false); Services.obs.addObserver(this, TOPIC_CONNECTION_STATE_CHANGED, false); - Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false); - Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false); Services.prefs.addObserver(PREF_NETWORK_DEBUG_ENABLED, this, false); + Services.prefs.addObserver(PREF_MANAGE_OFFLINE_STATUS, this, false); + + try { + this._manageOfflineStatus = + Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS); + } catch(ex) { + // Ignore. + } this._dataDefaultServiceId = 0; @@ -234,6 +239,12 @@ TetheringService.prototype = { // Arguments for pending wifi tethering request. _pendingWifiTetheringRequestArgs: null, + // The state of tethering. + state: Ci.nsITetheringService.TETHERING_STATE_INACTIVE, + + // Flag to check if we can modify the Services.io.offline. + _manageOfflineStatus: true, + // nsIObserver observe: function(aSubject, aTopic, aData) { @@ -257,33 +268,24 @@ TetheringService.prototype = { " changed state to " + network.state); this.onConnectionChanged(network); break; - case TOPIC_INTERFACE_REGISTERED: - network = aSubject.QueryInterface(Ci.nsINetworkInterface); - if (network && - network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { - debug("Force setting " + SETTINGS_DUN_REQUIRED + " to true."); - this.tetheringSettings[SETTINGS_DUN_REQUIRED] = true; - } - break; - case TOPIC_INTERFACE_UNREGISTERED: - network = aSubject.QueryInterface(Ci.nsINetworkInterface); - if (network && - network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) { - this.tetheringSettings[SETTINGS_DUN_REQUIRED] = - libcutils.property_get("ro.tethering.dun_required") === "1"; - } - break; case TOPIC_XPCOM_SHUTDOWN: Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN); Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED); Services.obs.removeObserver(this, TOPIC_CONNECTION_STATE_CHANGED); - Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED); - Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED); Services.prefs.removeObserver(PREF_NETWORK_DEBUG_ENABLED, this); + Services.prefs.removeObserver(PREF_MANAGE_OFFLINE_STATUS, this); this.dunConnectTimer.cancel(); this.dunRetryTimer.cancel(); break; + case PREF_MANAGE_OFFLINE_STATUS: + try { + this._manageOfflineStatus = + Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS); + } catch(ex) { + // Ignore. + } + break; } }, @@ -361,7 +363,8 @@ TetheringService.prototype = { }, getNetworkInfo: function(aType, aServiceId) { - for each (let networkInfo in gNetworkManager.allNetworkInfo) { + for (let networkId in gNetworkManager.allNetworkInfo) { + let networkInfo = gNetworkManager.allNetworkInfo[networkId]; if (networkInfo.type == aType) { try { if (networkInfo instanceof Ci.nsIRilNetworkInfo) { @@ -604,10 +607,31 @@ TetheringService.prototype = { this._wifiTetheringRequestOngoing = true; gNetworkService.setWifiTethering(aEnable, aConfig, (aError) => { - // Disconnect dun on error or when wifi tethering is disabled. - if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] && - (!aEnable || aError)) { - this.handleDunConnection(false); + // Change the tethering state to WIFI if there is no error. + if (aEnable && !aError) { + this.state = Ci.nsITetheringService.TETHERING_STATE_WIFI; + } else { + // If wifi thethering is disable, or any error happens, + // then consider the following statements. + + // Check whether the state is USB now or not. If no then just change + // it to INACTIVE, if yes then just keep it. + // It means that don't let the disable or error of WIFI affect + // the original active state. + if (this.state != Ci.nsITetheringService.TETHERING_STATE_USB) { + this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE; + } + + // Disconnect dun on error or when wifi tethering is disabled. + if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) { + this.handleDunConnection(false); + } + } + + if (this._manageOfflineStatus) { + Services.io.offline = !this.isAnyConnected() && + (this.state === + Ci.nsITetheringService.TETHERING_STATE_INACTIVE); } let resetSettings = aError; @@ -644,6 +668,10 @@ TetheringService.prototype = { return; } + // Re-check again, test cases set this property later. + this.tetheringSettings[SETTINGS_DUN_REQUIRED] = + libcutils.property_get("ro.tethering.dun_required") === "1"; + if (!aEnable) { this.enableWifiTethering(false, aConfig, aCallback); return; @@ -738,19 +766,36 @@ TetheringService.prototype = { // Skip others request when we found an error. this._usbTetheringRequestCount = 0; this._usbTetheringAction = TETHERING_STATE_IDLE; + // If the thethering state is WIFI now, then just keep it, + // if not, just change the state to INACTIVE. + // It means that don't let the error of USB affect the original active state. + if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) { + this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE; + } if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) { this.handleDunConnection(false); } } else { if (aEnable) { this._usbTetheringAction = TETHERING_STATE_ACTIVE; + this.state = Ci.nsITetheringService.TETHERING_STATE_USB; } else { this._usbTetheringAction = TETHERING_STATE_IDLE; + // If the state is now WIFI, don't let the disable of USB affect it. + if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) { + this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE; + } if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) { this.handleDunConnection(false); } } + if (this._manageOfflineStatus) { + Services.io.offline = !this.isAnyConnected() && + (this.state === + Ci.nsITetheringService.TETHERING_STATE_INACTIVE); + } + this.handleLastUsbTetheringRequest(); } }, @@ -830,6 +875,17 @@ TetheringService.prototype = { callback.call(this); }, + + isAnyConnected: function() { + let allNetworkInfo = gNetworkManager.allNetworkInfo; + for (let networkId in allNetworkInfo) { + if (allNetworkInfo.hasOwnProperty(networkId) && + allNetworkInfo[networkId].state === Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { + return true; + } + } + return false; + }, }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TetheringService]); diff --git a/dom/system/gonk/nsINetworkInterface.idl b/dom/system/gonk/nsINetworkInterface.idl index ccd281d331..4e815819c4 100644 --- a/dom/system/gonk/nsINetworkInterface.idl +++ b/dom/system/gonk/nsINetworkInterface.idl @@ -80,7 +80,7 @@ interface nsINetworkInfo : nsISupports [array, size_is(count), retval] out wstring dnses); }; -[scriptable, uuid(9a025351-8684-4ab5-a0c1-f21a9f83d405)] +[scriptable, uuid(8b1345fa-b34c-41b3-8d21-09f961bf8887)] interface nsINetworkInterface : nsISupports { /** @@ -97,4 +97,9 @@ interface nsINetworkInterface : nsISupports * The port number of the http proxy server. */ readonly attribute long httpProxyPort; + + /* + * The Maximun Transmit Unit for this network interface, -1 if not set. + */ + readonly attribute long mtu; }; diff --git a/dom/system/gonk/nsINetworkInterfaceListService.idl b/dom/system/gonk/nsINetworkInterfaceListService.idl index 38aae438d2..0c224842e4 100644 --- a/dom/system/gonk/nsINetworkInterfaceListService.idl +++ b/dom/system/gonk/nsINetworkInterfaceListService.idl @@ -2,10 +2,11 @@ * 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 "nsINetworkManager.idl" #include "nsISupports.idl" -[scriptable, uuid(b44d74db-c9d6-41dd-98ae-a56918d6e6ad)] +interface nsINetworkInfo; + +[scriptable, uuid(55779d32-1e28-4f43-af87-09d04bc3cce9)] interface nsINetworkInterfaceList : nsISupports { /** @@ -14,10 +15,10 @@ interface nsINetworkInterfaceList : nsISupports long getNumberOfInterface(); /** - * Get the i-th interface from the list. + * Get the i-th interface info info from the list. * @param interfaceIndex index of interface, from 0 to number of interface - 1. */ - nsINetworkInterface getInterface(in long interfaceIndex); + nsINetworkInfo getInterfaceInfo(in long interfaceIndex); }; [scriptable, uuid(21d7fc8b-28c4-4a4f-a15e-1f9defbc2cec)] diff --git a/dom/system/gonk/nsINetworkService.idl b/dom/system/gonk/nsINetworkService.idl index 06451827f1..974fbd797a 100644 --- a/dom/system/gonk/nsINetworkService.idl +++ b/dom/system/gonk/nsINetworkService.idl @@ -157,7 +157,7 @@ interface nsIDhcpRequestCallback : nsISupports /** * Provide network services. */ -[scriptable, uuid(5d6b1877-890a-4c7f-8403-94d57ceba6cf)] +[scriptable, uuid(8f689d9f-30c0-4809-8bf6-87120e71f3ee)] interface nsINetworkService : nsISupports { const long MODIFY_ROUTE_ADD = 0; @@ -310,8 +310,6 @@ interface nsINetworkService : nsISupports * Number of elements in gateways. * @param gateways * Default gateways for setting default route. - * @param oldInterfaceName - * The previous network interface name. * * @param callback * Callback to notify the result of setting default route. @@ -319,7 +317,6 @@ interface nsINetworkService : nsISupports void setDefaultRoute(in DOMString interfaceName, in unsigned long count, [array, size_is(count)] in wstring gateways, - in DOMString oldInterfaceName, in nsINativeCommandCallback callback); /** @@ -485,9 +482,9 @@ interface nsINetworkService : nsISupports in nsINativeCommandCallback callback); /** - * Reset all connections + * Reset all connections on a specified network interface. * - * @param networkInterface + * @param interfaceName * The network interface name which we want to reset. * * @param callback @@ -497,25 +494,25 @@ interface nsINetworkService : nsISupports in nsINativeCommandCallback callback); /** - * Create network (required to call prior to any networking operation) + * Create network (required to call prior to any networking operation). * - * @param networkInterface - * The network interface name which we want to reset. + * @param interfaceName + * The network interface name which we want to create network for. * * @param callback - * Callback to notify the result of resetting connections. + * Callback to notify the result of creating network. */ void createNetwork(in DOMString interfaceName, in nsINativeCommandCallback callback); /** - * Destroy network (required to call prior to any networking operation) + * Destroy network. * - * @param networkInterface - * The network interface name which we want to reset. + * @param interfaceName + * The network interface name of the network we want to destroy. * * @param callback - * Callback to notify the result of resetting connections. + * Callback to notify the result of destroying network. */ void destroyNetwork(in DOMString interfaceName, in nsINativeCommandCallback callback); @@ -532,4 +529,18 @@ interface nsINetworkService : nsISupports * */ jsval getNetId(in DOMString interfaceName); + + /** + * Set maximum transmission unit on a network interface. + * + * @param interfaceName + * The name of the network interface that we want to set mtu. + * @param mtu + * Size of maximum tranmission unit. + * + * @param callback + * Callback to notify the result of setting mtu. + */ + void setMtu(in DOMString interfaceName, in long mtu, + in nsINativeCommandCallback callback); }; diff --git a/dom/system/gonk/nsITetheringService.idl b/dom/system/gonk/nsITetheringService.idl index d9492b4493..530ab0069d 100644 --- a/dom/system/gonk/nsITetheringService.idl +++ b/dom/system/gonk/nsITetheringService.idl @@ -7,11 +7,20 @@ interface nsINetworkInterface; interface nsIWifiTetheringCallback; -[scriptable, uuid(993b79df-f10e-4697-a5dc-5981cf8ff7e6)] +[scriptable, uuid(779de2d3-6d29-4ee6-b069-6251839f757a)] interface nsITetheringService : nsISupports { + const long TETHERING_STATE_INACTIVE = 0; + const long TETHERING_STATE_WIFI = 1; + const long TETHERING_STATE_USB = 2; + /** - * Enable or disable Wifi Tethering + * Current tethering state. One of the TETHERING_STATE_* constants. + */ + readonly attribute long state; + + /** + * Enable or disable Wifi Tethering. * * @param enabled * Boolean that indicates whether tethering should be enabled (true) or diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 6ca2bf7508..afc9fda45a 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -4286,7 +4286,8 @@ RilObject.prototype = { } let list = null; - for each (let ll in usedCellBroadcastConfigs) { + for (let key in usedCellBroadcastConfigs) { + let ll = usedCellBroadcastConfigs[key]; if (ll == null) { continue; } diff --git a/dom/system/gonk/tests/marionette/head.js b/dom/system/gonk/tests/marionette/head.js index 65446179be..7fa91c4cf5 100644 --- a/dom/system/gonk/tests/marionette/head.js +++ b/dom/system/gonk/tests/marionette/head.js @@ -5,6 +5,7 @@ MARIONETTE_CONTEXT = "chrome"; const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled"; const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings"; +const SETTINGS_KEY_WIFI_ENABLED = "wifi.enabled"; const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed"; const TOPIC_NETWORK_ACTIVE_CHANGED = "network-active-changed"; @@ -129,6 +130,34 @@ function waitForObserverEvent(aTopic) { return deferred.promise; } +/** + * Wait for one named event. + * + * Resolve if that named event occurs. Never reject. + * + * Fulfill params: the DOMEvent passed. + * + * @param aEventTarget + * An EventTarget object. + * @param aEventName + * A string event name. + * @param aMatchFun [optional] + * A matching function returns true or false to filter the event. + * + * @return A deferred promise. + */ +function waitForTargetEvent(aEventTarget, aEventName, aMatchFun) { + return new Promise(function(aResolve, aReject) { + aEventTarget.addEventListener(aEventName, function onevent(aEvent) { + if (!aMatchFun || aMatchFun(aEvent)) { + aEventTarget.removeEventListener(aEventName, onevent); + ok(true, "Event '" + aEventName + "' got."); + aResolve(aEvent); + } + }); + }); +} + /** * Set the default data connection enabling state, wait for * "network-connection-state-changed" event and verify state. diff --git a/dom/system/gonk/tests/marionette/manifest.ini b/dom/system/gonk/tests/marionette/manifest.ini index 96c8250516..73b86112e9 100644 --- a/dom/system/gonk/tests/marionette/manifest.ini +++ b/dom/system/gonk/tests/marionette/manifest.ini @@ -14,3 +14,4 @@ disabled = Bug 808783 [test_multiple_data_connection.js] [test_data_connection_proxy.js] [test_network_interface_list_service.js] +[test_all_network_info.js] diff --git a/dom/system/gonk/tests/marionette/test_all_network_info.js b/dom/system/gonk/tests/marionette/test_all_network_info.js new file mode 100644 index 0000000000..b1aeccbf7f --- /dev/null +++ b/dom/system/gonk/tests/marionette/test_all_network_info.js @@ -0,0 +1,106 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = "head.js"; + +let networkManager = + Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); +ok(networkManager, + "networkManager.constructor is " + networkManager.constructor); + +let wifiManager = window.navigator.mozWifiManager; +ok(wifiManager, "wifiManager.constructor is " + wifiManager.constructor); + +function setEmulatorAPN() { + let apn = [ + [{"carrier":"T-Mobile US", + "apn":"epc.tmobile.com", + "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc", + "types":["default","supl","mms","ims","dun", "fota"]}] + ]; + + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, apn); +} + +function ensureWifiEnabled(aEnabled) { + if (wifiManager.enabled === aEnabled) { + log('Already ' + (aEnabled ? 'enabled' : 'disabled')); + return Promise.resolve(); + } + return requestWifiEnabled(aEnabled); +} + +function requestWifiEnabled(aEnabled) { + let promises = []; + + promises.push(waitForTargetEvent(wifiManager, aEnabled ? 'enabled' : 'disabled', + function() { + return wifiManager.enabled === aEnabled ? true : false; + })); + promises.push(setSettings(SETTINGS_KEY_WIFI_ENABLED, aEnabled)); + + return Promise.all(promises); +} + +// Test initial State +function verifyInitialState() { + log("= verifyInitialState ="); + + // Data and wifi should be off before starting any test. + return getSettings(SETTINGS_KEY_DATA_ENABLED) + .then(value => { + is(value, false, "Data must be off"); + }) + .then(() => ensureWifiEnabled(false)); +} + +function testAllNetworkInfo(aAnyConnected) { + log("= testAllNetworkInfo = " + aAnyConnected); + + let allNetworkInfo = networkManager.allNetworkInfo; + ok(allNetworkInfo, "NetworkManager.allNetworkInfo"); + + let count = Object.keys(allNetworkInfo).length; + ok(count > 0, "NetworkManager.allNetworkInfo count"); + + let connected = false; + for (let networkId in allNetworkInfo) { + if (allNetworkInfo.hasOwnProperty(networkId)) { + let networkInfo = allNetworkInfo[networkId]; + if (networkInfo.state == Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) { + connected = true; + break; + } + } + } + + is(aAnyConnected, connected, "NetworkManager.allNetworkInfo any connected"); +} + +// Start test +startTestBase(function() { + + let origApnSettings, origWifiEnabled; + return Promise.resolve() + .then(() => { + origWifiEnabled = wifiManager.enabled; + }) + .then(() => verifyInitialState()) + .then(() => getSettings(SETTINGS_KEY_DATA_APN_SETTINGS)) + .then(value => { + origApnSettings = value; + }) + .then(() => setEmulatorAPN()) + .then(() => setDataEnabledAndWait(true)) + .then(() => testAllNetworkInfo(true)) + .then(() => setDataEnabledAndWait(false)) + .then(() => testAllNetworkInfo(false)) + // Restore original apn settings and wifi state. + .then(() => { + if (origApnSettings) { + return setSettings(SETTINGS_KEY_DATA_APN_SETTINGS, origApnSettings); + } + }) + .then(() => ensureWifiEnabled(origWifiEnabled)); +}); diff --git a/dom/system/gonk/tests/marionette/test_network_interface_list_service.js b/dom/system/gonk/tests/marionette/test_network_interface_list_service.js index 37fa913557..549940fa5b 100644 --- a/dom/system/gonk/tests/marionette/test_network_interface_list_service.js +++ b/dom/system/gonk/tests/marionette/test_network_interface_list_service.js @@ -4,7 +4,7 @@ MARIONETTE_TIMEOUT = 60000; MARIONETTE_HEAD_JS = "head.js"; -function getNetworkInterface(aType) { +function getNetworkInfo(aType) { let networkListService = Cc["@mozilla.org/network/interface-list-service;1"]. getService(Ci.nsINetworkInterfaceListService); @@ -14,9 +14,9 @@ function getNetworkInterface(aType) { // Try to get nsINetworkInterface for aType. let numberOfInterface = networkList.getNumberOfInterface(); for (let i = 0; i < numberOfInterface; i++) { - let iface = networkList.getInterface(i); - if (iface.type === aType) { - return iface; + let info = networkList.getInterfaceInfo(i); + if (info.type === aType) { + return info; } } @@ -29,27 +29,27 @@ function testGetDataInterfaceList(aMobileDataEnabled) { aMobileDataEnabled ? "enabled" : "disabled"); return setDataEnabledAndWait(aMobileDataEnabled) - .then(() => getNetworkInterface(NETWORK_TYPE_MOBILE)) - .then((networkInterface) => { - if (!networkInterface) { - ok(false, "Should get an valid nsINetworkInterface for mobile"); + .then(() => getNetworkInfo(NETWORK_TYPE_MOBILE)) + .then((networkInfo) => { + if (!networkInfo) { + ok(false, "Should get an valid nsINetworkInfo for mobile"); return; } - ok(networkInterface instanceof Ci.nsINetworkInterface, - "networkInterface should be an instance of nsINetworkInterface"); + ok(networkInfo instanceof Ci.nsINetworkInfo, + "networkInfo should be an instance of nsINetworkInfo"); let ipAddresses = {}; let prefixs = {}; let numOfGateways = {}; let numOfDnses = {}; - let numOfIpAddresses = networkInterface.getAddresses(ipAddresses, prefixs); - let gateways = networkInterface.getGateways(numOfGateways); - let dnses = networkInterface.getDnses(numOfDnses); + let numOfIpAddresses = networkInfo.getAddresses(ipAddresses, prefixs); + let gateways = networkInfo.getGateways(numOfGateways); + let dnses = networkInfo.getDnses(numOfDnses); if (aMobileDataEnabled) { // Mobile data is enabled. - is(networkInterface.state, NETWORK_STATE_CONNECTED, "check state"); + is(networkInfo.state, NETWORK_STATE_CONNECTED, "check state"); ok(numOfIpAddresses > 0, "check number of ipAddresses"); ok(ipAddresses.value.length > 0, "check ipAddresses.length"); ok(prefixs.value.length > 0, "check prefixs.length"); @@ -60,7 +60,7 @@ function testGetDataInterfaceList(aMobileDataEnabled) { ok(dnses.length > 0, "check dnses.length"); } else { // Mobile data is disabled. - is(networkInterface.state, NETWORK_STATE_DISCONNECTED, "check state"); + is(networkInfo.state, NETWORK_STATE_DISCONNECTED, "check state"); is(numOfIpAddresses, 0, "check number of ipAddresses"); is(ipAddresses.value.length, 0, "check ipAddresses.length"); is(prefixs.value.length, 0, "check prefixs.length"); diff --git a/dom/tethering/tests/marionette/head.js b/dom/tethering/tests/marionette/head.js index 0b08dc5710..d7b6cad077 100644 --- a/dom/tethering/tests/marionette/head.js +++ b/dom/tethering/tests/marionette/head.js @@ -218,6 +218,14 @@ let gTestSuite = (function() { return setSettings1(SETTINGS_KEY_DATA_APN_SETTINGS, aApnSettings, aAllowError); } + /** + * Set 'ro.tethering.dun_required' system property to 1. Note that this is a + * 'ro' property, it can only be set once. + */ + function setTetheringDunRequired() { + return runEmulatorShellSafe(['setprop', 'ro.tethering.dun_required', '1']); + } + /** * Wrap DOMRequest onsuccess/onerror events to Promise resolve/reject. * @@ -704,6 +712,7 @@ let gTestSuite = (function() { suite.setWifiTetheringEnabled = setWifiTetheringEnabled; suite.getDataApnSettings = getDataApnSettings; suite.setDataApnSettings = setDataApnSettings; + suite.setTetheringDunRequired = setTetheringDunRequired; /** diff --git a/dom/tethering/tests/marionette/manifest.ini b/dom/tethering/tests/marionette/manifest.ini index 9cd43c60da..90133b5a0e 100644 --- a/dom/tethering/tests/marionette/manifest.ini +++ b/dom/tethering/tests/marionette/manifest.ini @@ -4,4 +4,6 @@ browser = false qemu = true [test_wifi_tethering_enabled.js] +; The following test must be the last tethering test ran, as it sets the +; 'ro.tethering.dun_required' property. [test_wifi_tethering_dun.js] diff --git a/dom/tethering/tests/marionette/test_wifi_tethering_dun.js b/dom/tethering/tests/marionette/test_wifi_tethering_dun.js index e4e11c7cec..3d93cf8d34 100644 --- a/dom/tethering/tests/marionette/test_wifi_tethering_dun.js +++ b/dom/tethering/tests/marionette/test_wifi_tethering_dun.js @@ -22,6 +22,7 @@ gTestSuite.startTest(function() { "types": ["dun"] } ]]; return gTestSuite.setDataApnSettings(apnSettings); }) + .then(() => gTestSuite.setTetheringDunRequired()) .then(() => gTestSuite.startTetheringTest(function() { return gTestSuite.ensureWifiEnabled(false) .then(() => gTestSuite.setWifiTetheringEnabled(true, true)) diff --git a/dom/tv/TVSimulatorService.js b/dom/tv/TVSimulatorService.js index a9fd2fe56f..785e9d8d22 100644 --- a/dom/tv/TVSimulatorService.js +++ b/dom/tv/TVSimulatorService.js @@ -4,16 +4,31 @@ "use strict"; function debug(aMsg) { - //dump("[TVSimulatorService] " + aMsg + "\n"); + //dump("[TVSimulatorService] " + aMsg + "\n"); } const Cc = Components.classes; const Cu = Components.utils; const Ci = Components.interfaces; const Cr = Components.returnCode; + +Cu.importGlobalProperties(["File"]); + const TV_SIMULATOR_DUMMY_DIRECTORY = "dummy"; const TV_SIMULATOR_DUMMY_FILE = "settings.json"; +// See http://seanyhlin.github.io/TV-Manager-API/#idl-def-TVSourceType +const TV_SOURCE_TYPES = ["dvb-t","dvb-t2","dvb-c","dvb-c2","dvb-s", + "dvb-s2","dvb-h","dvb-sh","atsc","atsc-m/h", + "isdb-t","isdb-tb","isdb-s","isdb-c","1seg", + "dtmb","cmmb","t-dmb","s-dmb"]; +function containInvalidSourceType(aElement, aIndex, aArray) { + return !TV_SOURCE_TYPES.includes(aElement); +} + +// See http://seanyhlin.github.io/TV-Manager-API/#idl-def-TVChannelType +const TV_CHANNEL_TYPES = ["tv","radio","data"]; + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); function TVSimulatorService() { @@ -66,7 +81,7 @@ TVSimulatorService.prototype = { cstream.close(); } - let settingObj; + let settingsObj; try { /* * @@ -109,18 +124,24 @@ TVSimulatorService.prototype = { * },] * } */ - settingObj = JSON.parse(settingStr); + settingsObj = JSON.parse(settingStr); } catch(e) { debug("File load error: " + e); return; } + // validation + if (!this._validateSettings(settingsObj)) { + debug("Failed to validate settings."); + return; + } + // Key is as follow // {'tunerId':tunerId, 'sourceType':sourceType} this._internalTuners = new Map(); // TVTunerData - for each (let tunerData in settingObj.tuners) { + for (let tunerData of settingsObj.tuners) { let tuner = Cc["@mozilla.org/tv/tvtunerdata;1"] .createInstance(Ci.nsITVTunerData); tuner.id = tunerData.id; @@ -135,11 +156,11 @@ TVSimulatorService.prototype = { }; // TVSource - for each (let sourceData in tunerData.sources) { + for (let sourceData of tunerData.sources) { wrapTunerData.sourceType = sourceData.type; // TVChannel - for each (let channelData in sourceData.channels) { + for (let channelData of sourceData.channels) { let channel = Cc["@mozilla.org/tv/tvchanneldata;1"] .createInstance(Ci.nsITVChannelData); channel.networkId = channelData.networkId; @@ -158,7 +179,7 @@ TVSimulatorService.prototype = { }; // TVProgram - for each (let programData in channelData.programs) { + for (let programData of channelData.programs) { let program = Cc["@mozilla.org/tv/tvprogramdata;1"] .createInstance(Ci.nsITVProgramData); program.eventId = programData.eventId; @@ -365,7 +386,7 @@ TVSimulatorService.prototype = { return Cr.NS_ERROR_INVALID_ARG; } - for each (let program in wrapChannelData.programs) { + for (let program of wrapChannelData.programs) { programArray.appendElement(program, false); } @@ -403,7 +424,7 @@ TVSimulatorService.prototype = { } let wrapChannelData = wrapTunerData.channels.get(aChannelNumber); - if (!wrapChannelData) { + if (!wrapChannelData || !wrapChannelData.videoFilePath) { return ""; } @@ -433,6 +454,84 @@ TVSimulatorService.prototype = { return dsFile.path; }, + + _validateSettings: function TVSimValidateSettings(aSettingsObject) { + return this._validateTuners(aSettingsObject.tuners); + }, + + _validateTuners: function TVSimValidateTuners(aTunersObject) { + let tunerIds = new Array(); + for (let tuner of aTunersObject) { + if (!tuner.id || + !tuner.supportedType || + !tuner.supportedType.length || + tuner.supportedType.some(containInvalidSourceType) || + tunerIds.includes(tuner.id)) { + debug("invalid tuner data."); + return false; + } + tunerIds.push(tuner.id); + + if (!this._validateSources(tuner.sources)) { + return false; + } + } + return true; + }, + + _validateSources: function TVSimValidateSources(aSourcesObject) { + for (let source of aSourcesObject) { + if (!source.type || + !TV_SOURCE_TYPES.includes(source.type)) { + debug("invalid source data."); + return false; + } + + if (!this._validateChannels(source.channels)) { + return false; + } + } + return true; + }, + + _validateChannels: function TVSimValidateChannels(aChannelsObject) { + let channelNumbers = new Array(); + for (let channel of aChannelsObject) { + if (!channel.networkId || + !channel.transportStreamId || + !channel.serviceId || + !channel.type || + !TV_CHANNEL_TYPES.includes(channel.type) || + !channel.number || + channelNumbers.includes(channel.number) || + !channel.name) { + debug("invalid channel data."); + return false; + } + channelNumbers.push(channel.number); + + if (!this._validatePrograms(channel.programs)) { + return false; + } + } + return true; + }, + + _validatePrograms: function TVSimValidatePrograms(aProgramsObject) { + let eventIds = new Array(); + for (let program of aProgramsObject) { + if (!program.eventId || + eventIds.includes(program.eventId) || + !program.title || + !program.startTime || + !program.duration) { + debug("invalid program data."); + return false; + } + eventIds.push(program.eventId); + } + return true; + }, }; this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TVSimulatorService]); diff --git a/dom/tv/TVTuner.cpp b/dom/tv/TVTuner.cpp index 6493211a19..0431156c0b 100644 --- a/dom/tv/TVTuner.cpp +++ b/dom/tv/TVTuner.cpp @@ -285,7 +285,7 @@ TVTuner::CreateSimulatedMediaStream() currentChannelNumber, domWin, currentVideoBlobUrl); - if (NS_WARN_IF(NS_FAILED(rv))) { + if (NS_WARN_IF(NS_FAILED(rv) || currentVideoBlobUrl.IsEmpty())) { return nullptr; } diff --git a/dom/voicemail/test/marionette/head.js b/dom/voicemail/test/marionette/head.js index 69d966469a..0dd08fe938 100644 --- a/dom/voicemail/test/marionette/head.js +++ b/dom/voicemail/test/marionette/head.js @@ -128,7 +128,7 @@ let PDUBuilder = { let headerLength = 0; this.buf = ""; if (options.headers) { - for each (let header in options.headers) { + for (let header of options.headers) { headerLength += 2; // id + length octets if (header.octets) { headerLength += header.octets.length; @@ -149,12 +149,12 @@ let PDUBuilder = { if (options.headers) { this.writeHexOctet(headerLength); - for each (let header in options.headers) { + for (let header of options.headers) { this.writeHexOctet(header.id); this.writeHexOctet(header.length); if (header.octets) { - for each (let octet in header.octets) { + for (let octet of header.octets) { this.writeHexOctet(octet); } } diff --git a/dom/webidl/NetworkOptions.webidl b/dom/webidl/NetworkOptions.webidl index 07d0438b6a..ab5e679830 100644 --- a/dom/webidl/NetworkOptions.webidl +++ b/dom/webidl/NetworkOptions.webidl @@ -17,7 +17,6 @@ dictionary NetworkCommandOptions unsigned long prefixLength; // for "removeNetworkRoute". DOMString domain; // for "setDNS" sequence dnses; // for "setDNS", "setDefaultRouteAndDNS". - DOMString oldIfname; // for "setDefaultRouteAndDNS". DOMString gateway; // for "addSecondaryRoute", "removeSecondaryRoute". sequence gateways; // for "setDefaultRouteAndDNS", "removeDefaultRoute". DOMString mode; // for "setWifiOperationMode". @@ -55,6 +54,8 @@ dictionary NetworkCommandOptions long gateway_long; // for "ifc_configure". long dns1_long; // for "ifc_configure". long dns2_long; // for "ifc_configure". + + long mtu; // for "setMtu". }; /** diff --git a/dom/wifi/WifiWorker.js b/dom/wifi/WifiWorker.js index 7df032a5ab..6be09e50c3 100755 --- a/dom/wifi/WifiWorker.js +++ b/dom/wifi/WifiWorker.js @@ -2583,7 +2583,8 @@ WifiWorker.prototype = { // connect to which ever network it thinks is best, so when we select the // proper network (or fail to), we need to re-enable the rest. _enableAllNetworks: function() { - for each (let net in this.configuredNetworks) { + for (let key in this.configuredNetworks) { + let net = this.configuredNetworks[key]; WifiManager.enableNetwork(net.netId, false, function(ok) { net.disabled = ok ? 1 : 0; }); diff --git a/dom/workers/test/extensions/bootstrap/bootstrap.js b/dom/workers/test/extensions/bootstrap/bootstrap.js index 7bc77b7300..289fec8340 100644 --- a/dom/workers/test/extensions/bootstrap/bootstrap.js +++ b/dom/workers/test/extensions/bootstrap/bootstrap.js @@ -11,7 +11,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); function testForExpectedSymbols(stage, data) { const expectedSymbols = [ "Worker", "ChromeWorker" ]; - for each (var symbol in expectedSymbols) { + for (var symbol of expectedSymbols) { Services.prefs.setBoolPref("workertest.bootstrap." + stage + "." + symbol, symbol in this); } diff --git a/dom/workers/test/test_extensionBootstrap.xul b/dom/workers/test/test_extensionBootstrap.xul index 33ca229e27..18bdde1d3e 100644 --- a/dom/workers/test/test_extensionBootstrap.xul +++ b/dom/workers/test/test_extensionBootstrap.xul @@ -31,8 +31,8 @@ const stages = [ "install", "startup", "shutdown", "uninstall" ]; const symbols = [ "Worker", "ChromeWorker" ]; - for each (var stage in stages) { - for each (var symbol in symbols) { + for (var stage of stages) { + for (var symbol of symbols) { is(Services.prefs.getBoolPref("workertest.bootstrap." + stage + "." + symbol), true, diff --git a/embedding/browser/nsIWebBrowserChrome3.idl b/embedding/browser/nsIWebBrowserChrome3.idl index 938ff55e26..05e35f455a 100644 --- a/embedding/browser/nsIWebBrowserChrome3.idl +++ b/embedding/browser/nsIWebBrowserChrome3.idl @@ -12,7 +12,7 @@ interface nsIInputStream; /** * nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2. */ -[scriptable, uuid(9e6c2372-5d9d-4ce8-ab9e-c5df1494dc84)] +[scriptable, uuid(da646a9c-5788-4860-88a4-bd5d0ad853da)] interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2 { /** @@ -47,4 +47,7 @@ interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2 bool shouldLoadURI(in nsIDocShell aDocShell, in nsIURI aURI, in nsIURI aReferrer); + + bool shouldAddToSessionHistory(in nsIDocShell aDocShell, + in nsIURI aURI); }; diff --git a/gfx/tests/gtest/TestGfxWidgets.cpp b/gfx/tests/gtest/TestGfxWidgets.cpp index cc946d8ab0..830204e057 100644 --- a/gfx/tests/gtest/TestGfxWidgets.cpp +++ b/gfx/tests/gtest/TestGfxWidgets.cpp @@ -27,6 +27,35 @@ TEST(GfxWidgets, Split) { ASSERT_TRUE(SplitDriverVersion("25.4.0.8", aStr, bStr, cStr, dStr)); ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && atoi(dStr) == 8); + ASSERT_TRUE(SplitDriverVersion("424.143.84437.3", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 424 && atoi(bStr) == 143 && atoi(cStr) == 8443 && atoi(dStr) == 3); + + ASSERT_FALSE(SplitDriverVersion("25.4.0.8.", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && atoi(dStr) == 8); + + ASSERT_TRUE(SplitDriverVersion("424.143.8.3143243", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 424 && atoi(bStr) == 143 && atoi(cStr) == 8 && atoi(dStr) == 3143); + + ASSERT_FALSE(SplitDriverVersion("25.4.0.8..", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && atoi(dStr) == 8); + + ASSERT_FALSE(SplitDriverVersion("424.143.8.3143243.", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 424 && atoi(bStr) == 143 && atoi(cStr) == 8 && atoi(dStr) == 3143); + + ASSERT_FALSE(SplitDriverVersion("25.4.0.8.13", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && atoi(dStr) == 8); + + ASSERT_FALSE(SplitDriverVersion("4.1.8.13.24.35", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 4 && atoi(bStr) == 1 && atoi(cStr) == 8 && atoi(dStr) == 13); + + ASSERT_TRUE(SplitDriverVersion("28...74", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 28 && atoi(bStr) == 0 && atoi(cStr) == 0 && atoi(dStr) == 74); + + ASSERT_FALSE(SplitDriverVersion("4.1.8.13.24.35", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 4 && atoi(bStr) == 1 && atoi(cStr) == 8 && atoi(dStr) == 13); + + ASSERT_TRUE(SplitDriverVersion("35..42.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 35 && atoi(bStr) == 0 && atoi(cStr) == 42 && atoi(dStr) == 0); } TEST(GfxWidgets, Versioning) { diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg index 4f3aabc0b6..486ec6507a 100644 --- a/js/xpconnect/src/xpc.msg +++ b/js/xpconnect/src/xpc.msg @@ -156,6 +156,7 @@ XPC_MSG_DEF(NS_ERROR_ENTITY_CHANGED , "It was attempted to resum XPC_MSG_DEF(NS_ERROR_REDIRECT_LOOP , "The request failed as a result of a detected redirection loop") XPC_MSG_DEF(NS_ERROR_UNSAFE_CONTENT_TYPE , "The request failed because the content type returned by the server was not a type expected by the channel") XPC_MSG_DEF(NS_ERROR_REMOTE_XUL , "Attempt to access remote XUL document that is not in website's whitelist") +XPC_MSG_DEF(NS_ERROR_LOAD_SHOWED_ERRORPAGE , "The load caused an error page to be displayed.") XPC_MSG_DEF(NS_ERROR_FTP_LOGIN , "FTP error while logging in") XPC_MSG_DEF(NS_ERROR_FTP_CWD , "FTP error while changing directory") diff --git a/layout/xul/nsMenuPopupFrame.cpp b/layout/xul/nsMenuPopupFrame.cpp index ad45b3de19..a0db950abd 100644 --- a/layout/xul/nsMenuPopupFrame.cpp +++ b/layout/xul/nsMenuPopupFrame.cpp @@ -60,6 +60,11 @@ using namespace mozilla; using mozilla::dom::PopupBoxObject; int8_t nsMenuPopupFrame::sDefaultLevelIsTop = -1; + +// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose: +// nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml +// need to find a good place to put them together. +// if someone changes one, please also change the other. uint32_t nsMenuPopupFrame::sTimeoutOfIncrementalSearch = 1000; const char* kPrefIncrementalSearchTimeout = @@ -1170,7 +1175,7 @@ nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize, // if the newly calculated position is different than the existing // position, we flip such that the popup is to the left or top of the // anchor point instead. - nscoord newScreenPoint = startpos - aSize - aMarginBegin - aOffsetForContextMenu; + nscoord newScreenPoint = startpos - aSize - aMarginBegin - std::max(aOffsetForContextMenu, 0); if (newScreenPoint != aScreenPoint) { *aFlipSide = true; aScreenPoint = newScreenPoint; @@ -1309,7 +1314,7 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS nsRect rootScreenRect = rootFrame->GetScreenRectInAppUnits(); nsDeviceContext* devContext = presContext->DeviceContext(); - nscoord offsetForContextMenu = 0; + nsPoint offsetForContextMenu; bool isNoAutoHide = IsNoAutoHide(); nsPopupLevel popupLevel = PopupLevel(isNoAutoHide); @@ -1370,13 +1375,17 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS // coordinates. int32_t factor = devContext->AppUnitsPerDevPixelAtUnitFullZoom(); - // context menus should be offset by two pixels so that they don't appear - // directly where the cursor is. Otherwise, it is too easy to have the - // context menu close up again. + // Depending on the platform, context menus should be offset by varying amounts + // to ensure that they don't appear directly where the cursor is. Otherwise, + // it is too easy to have the context menu close up again. if (mAdjustOffsetForContextMenu) { - int32_t offsetForContextMenuDev = - nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS) / factor; - offsetForContextMenu = presContext->DevPixelsToAppUnits(offsetForContextMenuDev); + nsPoint offsetForContextMenuDev; + offsetForContextMenuDev.x = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetHorizontal)) / factor; + offsetForContextMenuDev.y = nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetVertical)) / factor; + offsetForContextMenu.x = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.x); + offsetForContextMenu.y = presContext->DevPixelsToAppUnits(offsetForContextMenuDev.y); } // next, convert into app units accounting for the zoom @@ -1387,8 +1396,8 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS anchorRect = nsRect(screenPoint, nsSize(0, 0)); // add the margins on the popup - screenPoint.MoveBy(margin.left + offsetForContextMenu, - margin.top + offsetForContextMenu); + screenPoint.MoveBy(margin.left + offsetForContextMenu.x, + margin.top + offsetForContextMenu.y); // screen positioned popups can be flipped vertically but never horizontally vFlip = FlipStyle_Outside; @@ -1432,7 +1441,7 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS } else { mRect.width = FlipOrResize(screenPoint.x, mRect.width, screenRect.x, screenRect.XMost(), anchorRect.x, anchorRect.XMost(), - margin.left, margin.right, offsetForContextMenu, hFlip, + margin.left, margin.right, offsetForContextMenu.x, hFlip, &mHFlip); } if (slideVertical) { @@ -1441,7 +1450,7 @@ nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, bool aIsMove, bool aS } else { mRect.height = FlipOrResize(screenPoint.y, mRect.height, screenRect.y, screenRect.YMost(), anchorRect.y, anchorRect.YMost(), - margin.top, margin.bottom, offsetForContextMenu, vFlip, + margin.top, margin.bottom, offsetForContextMenu.y, vFlip, &mVFlip); } @@ -2150,10 +2159,10 @@ nsMenuPopupFrame::MoveTo(const CSSIntPoint& aPos, bool aUpdateAttrs) // Workaround for bug 788189. See also bug 708278 comment #25 and following. if (mAdjustOffsetForContextMenu) { - nscoord offsetForContextMenu = - nsPresContext::CSSPixelsToAppUnits(CONTEXT_MENU_OFFSET_PIXELS); - margin.left += offsetForContextMenu; - margin.top += offsetForContextMenu; + margin.left += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetHorizontal)); + margin.top += nsPresContext::CSSPixelsToAppUnits(LookAndFeel::GetInt( + LookAndFeel::eIntID_ContextMenuOffsetVertical)); } nsPresContext* presContext = PresContext(); diff --git a/layout/xul/nsMenuPopupFrame.h b/layout/xul/nsMenuPopupFrame.h index ebea6fd401..5bc575fa72 100644 --- a/layout/xul/nsMenuPopupFrame.h +++ b/layout/xul/nsMenuPopupFrame.h @@ -124,13 +124,6 @@ enum MenuPopupAnchorType { #define POPUPPOSITION_HFLIP(v) (v ^ 1) #define POPUPPOSITION_VFLIP(v) (v ^ 2) -// XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose: -// nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml -// need to find a good place to put them together. -// if someone changes one, please also change the other. - -#define CONTEXT_MENU_OFFSET_PIXELS 2 - nsIFrame* NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); class nsView; diff --git a/media/mtransport/gonk_addrs.cpp b/media/mtransport/gonk_addrs.cpp index 31d700304c..1c69b711ec 100644 --- a/media/mtransport/gonk_addrs.cpp +++ b/media/mtransport/gonk_addrs.cpp @@ -12,7 +12,7 @@ extern "C" { #include #include -#include "nsINetworkManager.h" +#include "nsINetworkInterface.h" #include "nsINetworkInterfaceListService.h" #include "runnable_utils.h" #include "nsCOMPtr.h" @@ -58,8 +58,8 @@ GetInterfaces(std::vector* aInterfaces) aInterfaces->clear(); for (int32_t i = 0; i < listLength; i++) { - nsCOMPtr iface; - if (NS_FAILED(networkList->GetInterface(i, getter_AddRefs(iface)))) { + nsCOMPtr info; + if (NS_FAILED(networkList->GetInterfaceInfo(i, getter_AddRefs(info)))) { continue; } @@ -71,7 +71,7 @@ GetInterfaces(std::vector* aInterfaces) memset(&(interface.addr), 0, sizeof(interface.addr)); interface.addr.sin_family = AF_INET; - if (NS_FAILED(iface->GetAddresses(&ips, &prefixs, &count))) { + if (NS_FAILED(info->GetAddresses(&ips, &prefixs, &count))) { continue; } @@ -94,20 +94,20 @@ GetInterfaces(std::vector* aInterfaces) } nsAutoString ifaceName; - if (NS_FAILED(iface->GetName(ifaceName))) { + if (NS_FAILED(info->GetName(ifaceName))) { continue; } interface.name = NS_ConvertUTF16toUTF8(ifaceName).get(); int32_t type; - if (NS_FAILED(iface->GetType(&type))) { + if (NS_FAILED(info->GetType(&type))) { continue; } switch (type) { - case nsINetworkInterface::NETWORK_TYPE_WIFI: + case nsINetworkInfo::NETWORK_TYPE_WIFI: interface.type = NR_INTERFACE_TYPE_WIFI; break; - case nsINetworkInterface::NETWORK_TYPE_MOBILE: + case nsINetworkInfo::NETWORK_TYPE_MOBILE: interface.type = NR_INTERFACE_TYPE_MOBILE; break; } diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 02806b5b64..8b33fa9a34 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4747,18 +4747,19 @@ pref("dom.mozAlarms.enabled", false); pref("dom.push.enabled", false); -#if !defined(RELEASE_BUILD) -pref("dom.push.debug", true); -#endif - +pref("dom.push.loglevel", "off"); pref("dom.push.serverURL", "wss://push.services.mozilla.com/"); pref("dom.push.userAgentID", ""); -// The maximum number of notifications that a service worker can receive +// The maximum number of push messages that a service worker can receive // without user interaction. pref("dom.push.maxQuotaPerSubscription", 16); +// The delay between receiving a push message and updating the quota for a +// subscription. +pref("dom.push.quotaUpdateDelay", 3000); // 3 seconds + // Is the network connection allowed to be up? // This preference should be used in UX to enable/disable push. pref("dom.push.connection.enabled", true); diff --git a/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py b/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py index 2071a6f688..800998d7f5 100644 --- a/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py +++ b/testing/marionette/client/marionette/tests/unit/test_single_finger_desktop.py @@ -75,6 +75,8 @@ prefs.setIntPref("ui.click_hold_context_menus.delay", arguments[0]); def test_wait_with_value(self): wait_with_value(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-mouseup-click") + """ + // Skipping due to Bug 1191066 def test_context_menu(self): context_menu(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-contextmenu", "button1-mousemove-mousedown-contextmenu-mouseup-click") @@ -83,7 +85,8 @@ prefs.setIntPref("ui.click_hold_context_menus.delay", arguments[0]); def test_long_press_on_xy_action(self): long_press_on_xy_action(self.marionette, self.wait_for_condition, "button1-mousemove-mousedown-contextmenu-mouseup-click") - + """ + """ //Skipping due to Bug 865334 def test_long_press_fail(self): diff --git a/toolkit/content/tests/chrome/popup_trigger.js b/toolkit/content/tests/chrome/popup_trigger.js index 4bfa9a3cc9..c9c430e2c7 100644 --- a/toolkit/content/tests/chrome/popup_trigger.js +++ b/toolkit/content/tests/chrome/popup_trigger.js @@ -507,9 +507,11 @@ var popupTests = [ } } + var openX = 8; + var openY = 16; var rect = gMenuPopup.getBoundingClientRect(); - is(rect.left, 10, testname + " left"); - is(rect.top, 18, testname + " top"); + is(rect.left, openX + (platformIsMac() ? 1 : 2), testname + " left"); + is(rect.top, openY + (platformIsMac() ? -6 : 2), testname + " top"); ok(rect.right, testname + " right is " + rect.right); ok(rect.bottom, testname + " bottom is " + rect.bottom); } @@ -749,7 +751,7 @@ var popupTests = [ autohide: "thepopup", test: function(testname, step) { gTrigger.focus(); - synthesizeKey("VK_DOWN", { altKey: (navigator.platform.indexOf("Mac") == -1) }); + synthesizeKey("VK_DOWN", { altKey: !platformIsMac() }); }, result: function(testname, step) { checkOpen("trigger", testname); @@ -762,7 +764,7 @@ var popupTests = [ events: [ "popupshowing thepopup", "popupshown thepopup" ], test: function(testname, step) { gTrigger.focus(); - synthesizeKey("VK_UP", { altKey: (navigator.platform.indexOf("Mac") == -1) }); + synthesizeKey("VK_UP", { altKey: !platformIsMac() }); }, result: function(testname, step) { checkOpen("trigger", testname); @@ -789,7 +791,7 @@ var popupTests = [ autohide: "thepopup", test: function(testname, step) { gTrigger.focus(); - synthesizeKey((navigator.platform.indexOf("Mac") == -1) ? "VK_F4" : " ", { }); + synthesizeKey(platformIsMac() ? " " : "VK_F4", { }); }, result: function(testname, step) { checkOpen("trigger", testname); @@ -802,10 +804,10 @@ var popupTests = [ condition: function() { return gIsMenu; }, test: function(testname, step) { gTrigger.focus(); - if (navigator.platform.indexOf("Mac") == -1) - synthesizeKey("", { metaKey: true }); - else + if (platformIsMac()) synthesizeKey("VK_F4", { altKey: true }); + else + synthesizeKey("", { metaKey: true }); }, result: function(testname, step) { checkClosed("trigger", testname); @@ -850,3 +852,8 @@ var popupTests = [ } ]; + +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} diff --git a/toolkit/content/tests/chrome/test_bug624329.xul b/toolkit/content/tests/chrome/test_bug624329.xul index c37e7edd9b..46ee919e9a 100644 --- a/toolkit/content/tests/chrome/test_bug624329.xul +++ b/toolkit/content/tests/chrome/test_bug624329.xul @@ -103,8 +103,23 @@ function openContextMenu() { var x = menubox.screenX - winbox.screenX; var y = menubox.screenY - winbox.screenY; - ok(y >= mouseY, - "menu top " + y + " should be below click point " + mouseY); + + if (navigator.userAgent.indexOf("Mac") > -1) + { + // This check is alterered slightly for OSX which adds padding to the top + // and bottom of its context menus. The menu position calculation must + // be changed to allow for the pointer to be outside this padding + // when the menu opens. + // (Bug 1075089) + ok(y + 6 >= mouseY, + "menu top " + (y + 6) + " should be below click point " + mouseY); + } + else + { + ok(y >= mouseY, + "menu top " + y + " should be below click point " + mouseY); + } + ok(y <= mouseY + 20, "menu top " + y + " should not be too far below click point " + mouseY); diff --git a/toolkit/content/tests/chrome/test_contextmenu_list.xul b/toolkit/content/tests/chrome/test_contextmenu_list.xul index 9fff789a4e..157831a581 100644 --- a/toolkit/content/tests/chrome/test_contextmenu_list.xul +++ b/toolkit/content/tests/chrome/test_contextmenu_list.xul @@ -189,7 +189,7 @@ function checkContextMenu(event) // context menu from mouse click switch (gTestId) { case -1: - var expected = gSelectionStep == 2 ? 1 : (navigator.platform.indexOf("Mac") >= 0 ? 3 : 0); + var expected = gSelectionStep == 2 ? 1 : (platformIsMac() ? 3 : 0); is($(gTestElement).selectedIndex, expected, "index after click " + gSelectionStep); break; case 0: @@ -222,47 +222,57 @@ function checkContextMenuForMenu(event) function checkPopup() { var menurect = $("themenu").getBoundingClientRect(); + + // Context menus are offset by a number of pixels from the mouse click + // which activates them. This is so that they don't appear exactly + // under the mouse which can cause them to be mistakenly dismissed. + // The number of pixels depends on the platform and is defined in + // each platform's nsLookAndFeel + var contextMenuOffsetX = platformIsMac() ? 1 : 2; + var contextMenuOffsetY = platformIsMac() ? -6 : 2; if (gTestId == 0) { if (gTestElement == "list") { var itemrect = $("item3").getBoundingClientRect(); - isRoundedX(menurect.left, itemrect.left + 2, + isRoundedX(menurect.left, itemrect.left + contextMenuOffsetX, "list selection keyboard left"); - isRoundedY(menurect.top, itemrect.bottom + 2, + isRoundedY(menurect.top, itemrect.bottom + contextMenuOffsetY, "list selection keyboard top"); } else { var tree = $("tree"); var bodyrect = $("treechildren").getBoundingClientRect(); - isRoundedX(menurect.left, bodyrect.left + 2, + isRoundedX(menurect.left, bodyrect.left + contextMenuOffsetX, "tree selection keyboard left"); isRoundedY(menurect.top, bodyrect.top + - tree.treeBoxObject.rowHeight * 3 + 2, + tree.treeBoxObject.rowHeight * 3 + contextMenuOffsetY, "tree selection keyboard top"); } } else if (gTestId == 1) { - // activating a context menu with the mouse from position (7, 1). - // Add 2 pixels to these values as context menus are offset by 2 pixels - // so that they don't appear exactly only the menu making them easier to - // dismiss. See nsXULPopupListener. + // activating a context menu with the mouse from position (7, 4). var elementrect = $(gTestElement).getBoundingClientRect(); - isRoundedX(menurect.left, elementrect.left + 9, + isRoundedX(menurect.left, elementrect.left + 7 + contextMenuOffsetX, gTestElement + " mouse left"); - isRoundedY(menurect.top, elementrect.top + 6, + isRoundedY(menurect.top, elementrect.top + 4 + contextMenuOffsetY, gTestElement + " mouse top"); } else { var elementrect = $(gTestElement).getBoundingClientRect(); - isRoundedX(menurect.left, elementrect.left + 2, + isRoundedX(menurect.left, elementrect.left + contextMenuOffsetX, gTestElement + " no selection keyboard left"); - isRoundedY(menurect.top, elementrect.bottom + 2, + isRoundedY(menurect.top, elementrect.bottom + contextMenuOffsetY, gTestElement + " no selection keyboard top"); } $("themenu").hidePopup(); } +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} + ]]> diff --git a/toolkit/content/tests/chrome/window_largemenu.xul b/toolkit/content/tests/chrome/window_largemenu.xul index f404b982e5..c54b8226c6 100644 --- a/toolkit/content/tests/chrome/window_largemenu.xul +++ b/toolkit/content/tests/chrome/window_largemenu.xul @@ -214,14 +214,17 @@ function contextMenuPopupShown() var popup = document.getElementById("popup"); var rect = popup.getBoundingClientRect(); var labelrect = document.getElementById("label").getBoundingClientRect(); - - is(rect.left, labelrect.left + 6, gTests[gTestIndex] + " left"); + + // Click to open popup in popupHidden() occurs at (4,4) in label's coordinate space + var clickX = clickY = 4; + + is(rect.left, labelrect.left + clickX + (platformIsMac() ? 1 : 2), gTests[gTestIndex] + " left"); switch (gTests[gTestIndex]) { case "context menu enough space below": - is(rect.top, labelrect.top + 6, gTests[gTestIndex] + " top"); + is(rect.top, labelrect.top + clickY + (platformIsMac() ? -6 : 2), gTests[gTestIndex] + " top"); break; case "context menu more space above": - is(rect.top, labelrect.top - rect.height + 2, gTests[gTestIndex] + " top"); + is(rect.top, labelrect.top + clickY - rect.height - (platformIsMac() ? 0 : 2), gTests[gTestIndex] + " top"); break; case "context menu too big either side": [, gScreenY] = getScreenXY(document.documentElement); @@ -281,7 +284,7 @@ function testPopupMovement() var screenX, screenY, buttonScreenX, buttonScreenY; var rect = popup.getBoundingClientRect(); - var overlapOSChrome = (navigator.platform.indexOf("Mac") == -1); + var overlapOSChrome = !platformIsMac(); popup.moveTo(1, 1); [screenX, screenY] = getScreenXY(popup); @@ -345,6 +348,11 @@ function testPopupMovement() popup.hidePopup(); } +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} + window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window); ]]> diff --git a/widget/GfxDriverInfo.cpp b/widget/GfxDriverInfo.cpp index 914ce5a8a0..9d3e248a2e 100644 --- a/widget/GfxDriverInfo.cpp +++ b/widget/GfxDriverInfo.cpp @@ -266,6 +266,12 @@ const GfxDeviceFamily* GfxDriverInfo::GetDeviceFamily(DeviceFamily id) case Bug1155608: APPEND_DEVICE(0x2e22); /* IntelG45_1 */ break; + case Bug1207665: + APPEND_DEVICE(0xa001); /* Intel Media Accelerator 3150 */ + APPEND_DEVICE(0xa002); + APPEND_DEVICE(0xa011); + APPEND_DEVICE(0xa012); + break; case AMDRadeonHD5800: APPEND_DEVICE(0x6899); break; diff --git a/widget/GfxDriverInfo.h b/widget/GfxDriverInfo.h index 9b4f0b21ab..eeb8d58db6 100644 --- a/widget/GfxDriverInfo.h +++ b/widget/GfxDriverInfo.h @@ -6,7 +6,6 @@ #ifndef __mozilla_widget_GfxDriverInfo_h__ #define __mozilla_widget_GfxDriverInfo_h__ -#include "mozilla/ArrayUtils.h" // ArrayLength #include "nsString.h" // Macros for adding a blocklist item to the static list. @@ -92,6 +91,7 @@ enum DeviceFamily { Bug1137716, Bug1116812, Bug1155608, + Bug1207665, DeviceFamilyMax }; @@ -186,17 +186,20 @@ inline bool SplitDriverVersion(const char *aSource, char *aAStr, char *aBStr, ch { // sscanf doesn't do what we want here to we parse this manually. int len = strlen(aSource); + + // This "4" is hardcoded in a few places, including once as a 3. char *dest[4] = { aAStr, aBStr, aCStr, aDStr }; unsigned destIdx = 0; unsigned destPos = 0; for (int i = 0; i < len; i++) { - if (destIdx > ArrayLength(dest)) { + if (destIdx >= 4) { // Invalid format found. Ensure we don't access dest beyond bounds. return false; } if (aSource[i] == '.') { + MOZ_ASSERT (destIdx < 4 && destPos <= 4); dest[destIdx++][destPos] = 0; destPos = 0; continue; @@ -208,13 +211,20 @@ inline bool SplitDriverVersion(const char *aSource, char *aAStr, char *aBStr, ch continue; } + MOZ_ASSERT (destIdx < 4 && destPos < 4); dest[destIdx][destPos++] = aSource[i]; } + // Take care of the trailing period + if (destIdx >= 4) { + return false; + } + // Add last terminator. + MOZ_ASSERT (destIdx < 4 && destPos <= 4); dest[destIdx][destPos] = 0; - if (destIdx != ArrayLength(dest) - 1) { + if (destIdx != 3) { return false; } return true; diff --git a/widget/GfxInfoX11.cpp b/widget/GfxInfoX11.cpp index 90d0574321..a03ac43cfe 100644 --- a/widget/GfxInfoX11.cpp +++ b/widget/GfxInfoX11.cpp @@ -14,7 +14,6 @@ #include "prenv.h" #include "GfxInfoX11.h" -#include "mozilla/X11Util.h" namespace mozilla { namespace widget { @@ -505,34 +504,6 @@ GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) return NS_ERROR_FAILURE; } -nsresult -GfxInfo::FindMonitors(JSContext* aCx, JS::HandleObject aOutArray) -{ -#if defined(MOZ_WIDGET_GTK) - // No display in xpcshell mode. - if (!gdk_display_get_default()) { - return NS_OK; - } -#endif - - // Note: this doesn't support Xinerama. Two physical displays will be - // reported as one monitor covering the entire virtual screen. - Display* display = DefaultXDisplay(); - Screen* screen = DefaultScreenOfDisplay(display); - - JS::Rooted obj(aCx, JS_NewPlainObject(aCx)); - - JS::Rooted screenWidth(aCx, JS::Int32Value(WidthOfScreen(screen))); - JS_SetProperty(aCx, obj, "screenWidth", screenWidth); - - JS::Rooted screenHeight(aCx, JS::Int32Value(HeightOfScreen(screen))); - JS_SetProperty(aCx, obj, "screenHeight", screenHeight); - - JS::Rooted element(aCx, JS::ObjectValue(*obj)); - JS_SetElement(aCx, aOutArray, 0, element); - return NS_OK; -} - #ifdef DEBUG // Implement nsIGfxInfoDebug diff --git a/widget/GfxInfoX11.h b/widget/GfxInfoX11.h index d54c16235d..04e8e2e24f 100644 --- a/widget/GfxInfoX11.h +++ b/widget/GfxInfoX11.h @@ -48,8 +48,6 @@ public: NS_IMETHOD_(void) GetData() override; - nsresult FindMonitors(JSContext* cx, JS::HandleObject array) override; - #ifdef DEBUG NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIGFXINFODEBUG diff --git a/widget/LookAndFeel.h b/widget/LookAndFeel.h index e62884a6f8..97b8ca0f41 100644 --- a/widget/LookAndFeel.h +++ b/widget/LookAndFeel.h @@ -429,7 +429,14 @@ public: * Overlay scrollbar animation constants. */ eIntID_ScrollbarFadeBeginDelay, - eIntID_ScrollbarFadeDuration + eIntID_ScrollbarFadeDuration, + + /** + * Distance in pixels to offset the context menu from the cursor + * on open. + */ + eIntID_ContextMenuOffsetVertical, + eIntID_ContextMenuOffsetHorizontal }; /** diff --git a/widget/VsyncDispatcher.cpp b/widget/VsyncDispatcher.cpp index 23c1a33842..0e3896fec7 100644 --- a/widget/VsyncDispatcher.cpp +++ b/widget/VsyncDispatcher.cpp @@ -10,6 +10,11 @@ #include "mozilla/layers/Compositor.h" #include "mozilla/layers/CompositorParent.h" +#ifdef MOZ_ENABLE_PROFILER_SPS +#include "GeckoProfiler.h" +#include "ProfilerMarkers.h" +#endif + namespace mozilla { static bool sThreadAssertionsEnabled = true; @@ -32,12 +37,16 @@ CompositorVsyncDispatcher::~CompositorVsyncDispatcher() { MOZ_ASSERT(XRE_IsParentProcess()); // We auto remove this vsync dispatcher from the vsync source in the nsBaseWidget - MOZ_ASSERT(NS_IsMainThread()); } void CompositorVsyncDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp) { + // In vsync thread +#ifdef MOZ_ENABLE_PROFILER_SPS + layers::CompositorParent::PostInsertVsyncProfilerMarker(aVsyncTimestamp); +#endif + MutexAutoLock lock(mCompositorObserverLock); if (mCompositorVsyncObserver) { mCompositorVsyncObserver->NotifyVsync(aVsyncTimestamp); diff --git a/widget/cocoa/nsLookAndFeel.mm b/widget/cocoa/nsLookAndFeel.mm index ea905b233a..719190f055 100644 --- a/widget/cocoa/nsLookAndFeel.mm +++ b/widget/cocoa/nsLookAndFeel.mm @@ -474,6 +474,12 @@ nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) case eIntID_ColorPickerAvailable: aResult = 1; break; + case eIntID_ContextMenuOffsetVertical: + aResult = -6; + break; + case eIntID_ContextMenuOffsetHorizontal: + aResult = 1; + break; default: aResult = 0; res = NS_ERROR_FAILURE; diff --git a/widget/gonk/nsLookAndFeel.cpp b/widget/gonk/nsLookAndFeel.cpp index 3aae4eae86..120f257df4 100644 --- a/widget/gonk/nsLookAndFeel.cpp +++ b/widget/gonk/nsLookAndFeel.cpp @@ -392,6 +392,11 @@ nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) aResult = atoi(propValue); break; } + + case eIntID_ContextMenuOffsetVertical: + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; default: aResult = 0; diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp index 2f77a3dc0f..e1be6e7c44 100644 --- a/widget/gtk/nsLookAndFeel.cpp +++ b/widget/gtk/nsLookAndFeel.cpp @@ -679,6 +679,10 @@ nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) case eIntID_ColorPickerAvailable: aResult = 1; break; + case eIntID_ContextMenuOffsetVertical: + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; default: aResult = 0; res = NS_ERROR_FAILURE; diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp index fadfed26eb..39c54319f5 100644 --- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -221,6 +221,7 @@ void WidgetShutdownObserver::Unregister() { if (mRegistered) { + mWidget = nullptr; nsContentUtils::UnregisterShutdownObserver(this); mRegistered = false; } diff --git a/widget/nsXPLookAndFeel.cpp b/widget/nsXPLookAndFeel.cpp index 4d42be3bca..e270e6f566 100644 --- a/widget/nsXPLookAndFeel.cpp +++ b/widget/nsXPLookAndFeel.cpp @@ -117,6 +117,12 @@ nsLookAndFeelIntPref nsXPLookAndFeel::sIntPrefs[] = { "ui.physicalHomeButton", eIntID_PhysicalHomeButton, false, 0 }, + { "ui.contextMenuOffsetVertical", + eIntID_ContextMenuOffsetVertical, + false, 0 }, + { "ui.contextMenuOffsetHorizontal", + eIntID_ContextMenuOffsetHorizontal, + false, 0 } }; nsLookAndFeelFloatPref nsXPLookAndFeel::sFloatPrefs[] = diff --git a/widget/qt/nsLookAndFeel.cpp b/widget/qt/nsLookAndFeel.cpp index 890e224d19..50d943e8dc 100644 --- a/widget/qt/nsLookAndFeel.cpp +++ b/widget/qt/nsLookAndFeel.cpp @@ -373,6 +373,11 @@ nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) case eIntID_ScrollbarButtonAutoRepeatBehavior: aResult = 0; break; + + case eIntID_ContextMenuOffsetVertical: + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; default: aResult = 0; diff --git a/widget/uikit/nsLookAndFeel.mm b/widget/uikit/nsLookAndFeel.mm index c28f52c031..bb593eb51e 100644 --- a/widget/uikit/nsLookAndFeel.mm +++ b/widget/uikit/nsLookAndFeel.mm @@ -345,6 +345,10 @@ nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) case eIntID_SpellCheckerUnderlineStyle: aResult = NS_STYLE_TEXT_DECORATION_STYLE_DOTTED; break; + case eIntID_ContextMenuOffsetVertical: + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; default: aResult = 0; res = NS_ERROR_FAILURE; diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp index d947e758f8..0cda82134b 100644 --- a/widget/windows/GfxInfo.cpp +++ b/widget/windows/GfxInfo.cpp @@ -1028,6 +1028,18 @@ GfxInfo::GetGfxDriverInfo() nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions ); + /* Disable D3D11 layers on Intel GMA 3150 for failing to allocate a shared handle for textures. + * See bug 1207665. Additionally block D2D so we don't accidentally use WARP. + */ + APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Bug1207665), + nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions ); + APPEND_TO_DRIVER_BLOCKLIST2(DRIVER_OS_ALL, + (nsAString&) GfxDriverInfo::GetDeviceVendor(VendorIntel), (GfxDeviceFamily*) GfxDriverInfo::GetDeviceFamily(Bug1207665), + nsIGfxInfo::FEATURE_DIRECT2D, nsIGfxInfo::FEATURE_BLOCKED_DEVICE, + DRIVER_LESS_THAN, GfxDriverInfo::allDriverVersions ); + /* Disable D2D on AMD Catalyst 14.4 until 14.6 * See bug 984488 */ diff --git a/widget/windows/nsLookAndFeel.cpp b/widget/windows/nsLookAndFeel.cpp index d0767980d6..8301378d56 100644 --- a/widget/windows/nsLookAndFeel.cpp +++ b/widget/windows/nsLookAndFeel.cpp @@ -518,6 +518,10 @@ nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult) case eIntID_ScrollbarFadeDuration: aResult = 350; break; + case eIntID_ContextMenuOffsetVertical: + case eIntID_ContextMenuOffsetHorizontal: + aResult = 2; + break; default: aResult = 0; res = NS_ERROR_FAILURE; diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index 39d6a2d016..d68a0cb5a9 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -60,7 +60,6 @@ #include "mozilla/AutoRestore.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Telemetry.h" -#include "mozilla/dom/BindingUtils.h" #include "mozilla/DebuggerOnGCRunnable.h" #include "mozilla/dom/DOMJSClass.h" #include "mozilla/dom/Promise.h" @@ -1138,7 +1137,9 @@ CycleCollectedJSRuntime::RunInMetastableState(already_AddRefed&& aR data.mRecursionDepth = RecursionDepth(); // There must be an event running to get here. +#ifndef MOZ_WIDGET_COCOA MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth); +#endif mMetastableStateEvents.AppendElement(Move(data)); } diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index b984c0fdda..6c7b888295 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -231,6 +231,8 @@ /* The request failed because the user tried to access to a remote XUL * document from a website that is not in its white-list. */ ERROR(NS_ERROR_REMOTE_XUL, FAILURE(75)), + /* The request resulted in an error page being displayed. */ + ERROR(NS_ERROR_LOAD_SHOWED_ERRORPAGE, FAILURE(77)), /* FTP specific error codes: */ @@ -933,6 +935,16 @@ ERROR(NS_ERROR_DOM_ANIM_NO_TARGET_ERR, FAILURE(2)), #undef MODULE + /* ======================================================================= */ + /* 40: NS_ERROR_MODULE_DOM_PUSH */ + /* ======================================================================= */ +#define MODULE NS_ERROR_MODULE_DOM_PUSH + ERROR(NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR, FAILURE(1)), + ERROR(NS_ERROR_DOM_PUSH_DENIED_ERR, FAILURE(2)), + ERROR(NS_ERROR_DOM_PUSH_ABORT_ERR, FAILURE(3)), + ERROR(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE, FAILURE(4)), +#undef MODULE + /* ======================================================================= */ /* 51: NS_ERROR_MODULE_GENERAL */ /* ======================================================================= */ diff --git a/xpcom/base/nsError.h b/xpcom/base/nsError.h index 7fccef1f8d..cc8f47d0b8 100644 --- a/xpcom/base/nsError.h +++ b/xpcom/base/nsError.h @@ -77,6 +77,7 @@ #define NS_ERROR_MODULE_DOM_BLUETOOTH 37 #define NS_ERROR_MODULE_SIGNED_APP 38 #define NS_ERROR_MODULE_DOM_ANIM 39 +#define NS_ERROR_MODULE_DOM_PUSH 40 /* NS_ERROR_MODULE_GENERAL should be used by modules that do not * care if return code values overlap. Callers of methods that diff --git a/xpfe/appshell/nsContentTreeOwner.cpp b/xpfe/appshell/nsContentTreeOwner.cpp index 2ba93eca03..f2e2bf14c6 100644 --- a/xpfe/appshell/nsContentTreeOwner.cpp +++ b/xpfe/appshell/nsContentTreeOwner.cpp @@ -483,6 +483,24 @@ NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell, return NS_OK; } +NS_IMETHODIMP +nsContentTreeOwner::ShouldAddToSessionHistory(nsIDocShell *aDocShell, + nsIURI *aURI, + bool *_retval) +{ + NS_ENSURE_STATE(mXULWindow); + + nsCOMPtr xulBrowserWindow; + mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow)); + + if (xulBrowserWindow) { + return xulBrowserWindow->ShouldAddToSessionHistory(aDocShell, aURI, _retval); + } + + *_retval = true; + return NS_OK; +} + //***************************************************************************** // nsContentTreeOwner::nsIWebBrowserChrome2 //***************************************************************************** diff --git a/xpfe/appshell/nsIXULBrowserWindow.idl b/xpfe/appshell/nsIXULBrowserWindow.idl index de69319f12..a3e8602731 100644 --- a/xpfe/appshell/nsIXULBrowserWindow.idl +++ b/xpfe/appshell/nsIXULBrowserWindow.idl @@ -19,7 +19,7 @@ interface nsITabParent; * internals of the browser area to tell the containing xul window to update * its ui. */ -[scriptable, uuid(db89f748-9736-40b2-a172-3928aa1194b2)] +[scriptable, uuid(8974a499-d49b-43e1-8b32-c9b3ed81be3f)] interface nsIXULBrowserWindow : nsISupports { /** @@ -62,6 +62,9 @@ interface nsIXULBrowserWindow : nsISupports bool shouldLoadURI(in nsIDocShell aDocShell, in nsIURI aURI, in nsIURI aReferrer); + + bool shouldAddToSessionHistory(in nsIDocShell aDocShell, + in nsIURI aURI); /** * Show/hide a tooltip (when the user mouses over a link, say). */