diff --git a/b2g/components/ProcessGlobal.js b/b2g/components/ProcessGlobal.js index 78c5558cce4e..275d7c1bd649 100644 --- a/b2g/components/ProcessGlobal.js +++ b/b2g/components/ProcessGlobal.js @@ -166,7 +166,8 @@ ProcessGlobal.prototype = { let args = message.arguments; let stackTrace = ''; - if (message.level == 'assert' || message.level == 'error' || message.level == 'trace') { + if (message.stacktrace && + (message.level == 'assert' || message.level == 'error' || message.level == 'trace')) { stackTrace = Array.map(message.stacktrace, formatStackFrame).join('\n'); } else { stackTrace = formatStackFrame(message); diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 8481f79aba19..ee6b22569686 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index aeda75e985fa..9e3363ab8973 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 9f6699bda7b9..5ce83149184e 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 778ebc765b86..f477bc37e3fb 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index c7aa11e73d26..6323ee8d6fa9 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index aeda75e985fa..9e3363ab8973 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index ce59bd3d6712..d4803d3f4e96 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index b9c3deb3e54b..0e981d051bbc 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index bd04babe2535..0ead6ddc7227 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "16383ec2bf3ed46f893b15b3fab2892e9fadc4e7", + "git_revision": "e370e6beecd28785beef8c7ff299cf788693f0cc", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "2b1fea55c45ae4b59eb077840bfe5361ecd48d54", + "revision": "e4abf3a2e6a68bafbeec52cdc2a388ff7b9adf3d", "repo_path": "integration/gaia-central" } diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index c3b7255ca708..84c6aac8823e 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 16e45d7eda30..a0a6acf40227 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/browser/app/blocklist.xml b/browser/app/blocklist.xml index 4bcb1e1892c7..87c0cd205cdc 100644 --- a/browser/app/blocklist.xml +++ b/browser/app/blocklist.xml @@ -2982,6 +2982,14 @@ WINNT 5.1 0x8086 DIRECT3D_9_LAYERS, WEBGL_ANGLE BLOCKED_DRIVER_VERSION 6.14.10.5218 LESS_THAN + + + D9UltDPl4XVfSSqQOvdiwQ== + + + STMAjg== + + \ No newline at end of file diff --git a/dom/apps/tests/mochitest.ini b/dom/apps/tests/mochitest.ini index c4c61c0dba46..ababfd7db5b2 100644 --- a/dom/apps/tests/mochitest.ini +++ b/dom/apps/tests/mochitest.ini @@ -54,6 +54,8 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only [test_signed_pkg_install.html] [test_uninstall_errors.html] [test_theme_role.html] +[test_third_party_homescreen.html] +skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app [test_web_app_install.html] [test_widget.html] skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app diff --git a/dom/apps/tests/test_third_party_homescreen.html b/dom/apps/tests/test_third_party_homescreen.html new file mode 100644 index 000000000000..d3ead7ddcf8b --- /dev/null +++ b/dom/apps/tests/test_third_party_homescreen.html @@ -0,0 +1,203 @@ + + + + + Test for Bug {1097468} + + + + + + +Mozilla Bug {1097468} + + + + + diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp index 3a675dbe661e..b6455f9d3b23 100644 --- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -23,6 +23,7 @@ #include "xpcprivate.h" #include "nsContentUtils.h" #include "nsDocShell.h" +#include "nsProxyRelease.h" #include "nsIConsoleAPIStorage.h" #include "nsIDOMWindowUtils.h" @@ -152,6 +153,8 @@ static const JSStructuredCloneCallbacks gConsoleCallbacks = { class ConsoleCallData final { public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData) + ConsoleCallData() : mMethodName(Console::MethodLog) , mPrivate(false) @@ -238,6 +241,10 @@ public: Maybe mTopStackFrame; Maybe> mReifiedStack; nsCOMPtr mStack; + +private: + ~ConsoleCallData() + { } }; // This class is used to clear any exception at the end of this method. @@ -284,16 +291,13 @@ public: return false; } - AutoSyncLoopHolder syncLoop(mWorkerPrivate); - mSyncLoopTarget = syncLoop.EventTarget(); - if (NS_FAILED(NS_DispatchToMainThread(this))) { JS_ReportError(cx, "Failed to dispatch to main thread for the Console API!"); return false; } - return syncLoop.Run(); + return true; } private: @@ -314,14 +318,6 @@ private: RunWithWindow(window); } - nsRefPtr response = - new MainThreadStopSyncLoopRunnable(mWorkerPrivate, - mSyncLoopTarget.forget(), - true); - if (!response->Dispatch(nullptr)) { - NS_WARNING("Failed to dispatch response!"); - } - return NS_OK; } @@ -388,9 +384,6 @@ protected: // Raw pointer because this method is async and this object is kept alive by // the caller. Console* mConsole; - -private: - nsCOMPtr mSyncLoopTarget; }; // This runnable appends a CallData object into the Console queue running on @@ -406,7 +399,30 @@ public: private: ~ConsoleCallDataRunnable() - { } + { + class ReleaseCallData final : public nsRunnable + { + public: + explicit ReleaseCallData(nsRefPtr& aCallData) + { + mCallData.swap(aCallData); + } + + NS_IMETHOD Run() override + { + mCallData = nullptr; + return NS_OK; + } + + private: + nsRefPtr mCallData; + }; + + nsRefPtr runnable = new ReleaseCallData(mCallData); + if(NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_WARNING("Failed to dispatch a ReleaseCallData runnable. Leaking."); + } + } bool PreDispatch(JSContext* aCx) override @@ -514,7 +530,7 @@ private: mConsole->ProcessCallData(mCallData); } - ConsoleCallData* mCallData; + nsRefPtr mCallData; JSAutoStructuredCloneBuffer mArguments; ConsoleStructuredCloneData mData; @@ -566,6 +582,7 @@ private: return false; } + mArguments.Clear(); return true; } @@ -959,7 +976,7 @@ Console::Method(JSContext* aCx, MethodName aMethodName, const nsAString& aMethodString, const Sequence& aData) { - nsAutoPtr callData(new ConsoleCallData()); + nsRefPtr callData(new ConsoleCallData()); ClearException ce(aCx); @@ -1078,8 +1095,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, return; } - // Note: we can pass the reference of callData because this runnable calls - // ProcessCallData() synchronously. nsRefPtr runnable = new ConsoleCallDataRunnable(this, callData); runnable->Dispatch(); diff --git a/dom/base/URLSearchParams.h b/dom/base/URLSearchParams.h index 173628fb58ce..ee7ff84e8dd8 100644 --- a/dom/base/URLSearchParams.h +++ b/dom/base/URLSearchParams.h @@ -80,6 +80,17 @@ public: Serialize(aRetval); } + typedef void (*ParamFunc)(const nsString& aName, const nsString& aValue, + void* aClosure); + + void + ForEach(ParamFunc aFunc, void* aClosure) + { + for (uint32_t i = 0; i < mSearchParams.Length(); ++i) { + aFunc(mSearchParams[i].mKey, mSearchParams[i].mValue, aClosure); + } + } + private: void AppendInternal(const nsAString& aName, const nsAString& aValue); diff --git a/dom/base/test/test_bug345339.html b/dom/base/test/test_bug345339.html index 2a5d05f24f9a..095696762f5f 100644 --- a/dom/base/test/test_bug345339.html +++ b/dom/base/test/test_bug345339.html @@ -17,11 +17,14 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=345339 src="http://mochi.test:8888/tests/dom/base/test/345339_iframe.html">
-
 
diff --git a/dom/bindings/Errors.msg b/dom/bindings/Errors.msg index bdf0318e3045..2c828efa1026 100644 --- a/dom/bindings/Errors.msg +++ b/dom/bindings/Errors.msg @@ -73,3 +73,4 @@ MSG_DEF(MSG_INVALID_RESPONSE_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid respo MSG_DEF(MSG_INVALID_REDIRECT_STATUSCODE_ERROR, 0, JSEXN_RANGEERR, "Invalid redirect status code.") MSG_DEF(MSG_INVALID_URL_SCHEME, 2, JSEXN_TYPEERR, "{0} URL {1} must be either http:// or https://.") MSG_DEF(MSG_RESPONSE_URL_IS_NULL, 0, JSEXN_TYPEERR, "Cannot set Response.finalURL when Response.url is null.") +MSG_DEF(MSG_BAD_FORMDATA, 0, JSEXN_TYPEERR, "Could not parse content as FormData.") diff --git a/dom/bluetooth/BluetoothProfileController.cpp b/dom/bluetooth/BluetoothProfileController.cpp index 4fb1e8215602..53b858a00ded 100644 --- a/dom/bluetooth/BluetoothProfileController.cpp +++ b/dom/bluetooth/BluetoothProfileController.cpp @@ -176,6 +176,16 @@ BluetoothProfileController::SetupProfiles(bool aAssignServiceClass) bool isRemoteControl = IS_REMOTE_CONTROL(mTarget.cod); bool isKeyboard = IS_KEYBOARD(mTarget.cod); bool isPointingDevice = IS_POINTING_DEVICE(mTarget.cod); + bool isInvalid = IS_INVALID_COD(mTarget.cod); + + // The value of CoD is invalid. Since the device didn't declare its class of + // device properly, we assume the device may support all of these profiles. + if (isInvalid) { + AddProfile(BluetoothHfpManager::Get()); + AddProfile(BluetoothA2dpManager::Get()); + AddProfile(BluetoothHidManager::Get()); + return; + } NS_ENSURE_TRUE_VOID(hasAudio || hasRendering || isPeripheral); diff --git a/dom/bluetooth/BluetoothProfileController.h b/dom/bluetooth/BluetoothProfileController.h index 5cdbe1bbf559..72a6a076b003 100644 --- a/dom/bluetooth/BluetoothProfileController.h +++ b/dom/bluetooth/BluetoothProfileController.h @@ -53,6 +53,17 @@ BEGIN_BLUETOOTH_NAMESPACE // Pointing device: sub-field of minor device class (Bit 7) #define IS_POINTING_DEVICE(cod) ((GET_MINOR_DEVICE_CLASS(cod) & 0x20) >> 5) +/** + * Check whether the value of CoD is invalid. (i.e. Bit 31 ~ Bit 24 != 0x0) + * + * According to Bluetooth core spec v4.1. Vol 2, Sec. 7.3, the data length of + * CoD (class of device) is 3 bytes. The two least significant bits are used to + * indicate 'format type'. The following 22 bits are used to indicate category + * of service class and device type. The remaining 8 bits (Bit 31 ~ Bit 24) + * should be unassigned bits, since BlueDroid uses uint32_t to store CoD. + */ +#define IS_INVALID_COD(cod) (cod >> 24) + class BluetoothProfileManagerBase; class BluetoothReplyRunnable; typedef void (*BluetoothProfileControllerCallback)(); diff --git a/dom/bluetooth2/BluetoothProfileController.cpp b/dom/bluetooth2/BluetoothProfileController.cpp index a939a653908b..72ba1b09262b 100644 --- a/dom/bluetooth2/BluetoothProfileController.cpp +++ b/dom/bluetooth2/BluetoothProfileController.cpp @@ -173,6 +173,16 @@ BluetoothProfileController::SetupProfiles(bool aAssignServiceClass) bool isRemoteControl = IS_REMOTE_CONTROL(mTarget.cod); bool isKeyboard = IS_KEYBOARD(mTarget.cod); bool isPointingDevice = IS_POINTING_DEVICE(mTarget.cod); + bool isInvalid = IS_INVALID_COD(mTarget.cod); + + // The value of CoD is invalid. Since the device didn't declare its class of + // device properly, we assume the device may support all of these profiles. + if (isInvalid) { + AddProfile(BluetoothHfpManager::Get()); + AddProfile(BluetoothA2dpManager::Get()); + AddProfile(BluetoothHidManager::Get()); + return; + } NS_ENSURE_TRUE_VOID(hasAudio || hasRendering || isPeripheral); diff --git a/dom/bluetooth2/BluetoothProfileController.h b/dom/bluetooth2/BluetoothProfileController.h index e4b12840a8a7..39b47d6a799e 100644 --- a/dom/bluetooth2/BluetoothProfileController.h +++ b/dom/bluetooth2/BluetoothProfileController.h @@ -53,6 +53,17 @@ BEGIN_BLUETOOTH_NAMESPACE // Pointing device: sub-field of minor device class (Bit 7) #define IS_POINTING_DEVICE(cod) ((GET_MINOR_DEVICE_CLASS(cod) & 0x20) >> 5) +/** + * Check whether the value of CoD is invalid. (i.e. Bit 31 ~ Bit 24 != 0x0) + * + * According to Bluetooth core spec v4.1. Vol 2, Sec. 7.3, the data length of + * CoD (class of device) is 3 bytes. The two least significant bits are used to + * indicate 'format type'. The following 22 bits are used to indicate category + * of service class and device type. The remaining 8 bits (Bit 31 ~ Bit 24) + * should be unassigned bits, since BlueDroid uses uint32_t to store CoD. + */ +#define IS_INVALID_COD(cod) (cod >> 24) + class BluetoothProfileManagerBase; class BluetoothReplyRunnable; typedef void (*BluetoothProfileControllerCallback)(); diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp index 8ea52e231432..b78ad61e877f 100644 --- a/dom/broadcastchannel/BroadcastChannel.cpp +++ b/dom/broadcastchannel/BroadcastChannel.cpp @@ -56,53 +56,68 @@ GetOrigin(nsIPrincipal* aPrincipal, nsAString& aOrigin, ErrorResult& aRv) { MOZ_ASSERT(aPrincipal); - uint16_t appStatus = aPrincipal->GetAppStatus(); + bool unknownAppId; + aRv = aPrincipal->GetUnknownAppId(&unknownAppId); + if (NS_WARN_IF(aRv.Failed())) { + return; + } - if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED) { - nsAutoString tmp; - aRv = nsContentUtils::GetUTFOrigin(aPrincipal, tmp); + if (!unknownAppId) { + uint32_t appId; + aRv = aPrincipal->GetAppId(&appId); if (NS_WARN_IF(aRv.Failed())) { return; } - aOrigin = tmp; - if (aOrigin.EqualsASCII("null")) { - nsCOMPtr uri; - aRv = aPrincipal->GetURI(getter_AddRefs(uri)); - if (NS_WARN_IF(aRv.Failed())) { + if (appId != nsIScriptSecurityManager::NO_APP_ID) { + // If we are in "app code", use manifest URL as unique origin since + // multiple apps can share the same origin but not same broadcast + // messages. + nsresult rv; + nsCOMPtr appsService = + do_GetService("@mozilla.org/AppsService;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); return; } - if (NS_WARN_IF(!uri)) { - aRv.Throw(NS_ERROR_FAILURE); - return; - } - - nsAutoCString spec; - aRv = uri->GetSpec(spec); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - - aOrigin = NS_ConvertUTF8toUTF16(spec); + appsService->GetManifestURLByLocalId(appId, aOrigin); + return; } + } + nsAutoString tmp; + aRv = nsContentUtils::GetUTFOrigin(aPrincipal, tmp); + if (NS_WARN_IF(aRv.Failed())) { return; } - uint32_t appId = aPrincipal->GetAppId(); + // 'null' means an unknown origin (it can be chrome code or it can be some + // about: page). - // If we are in "app code", use manifest URL as unique origin since - // multiple apps can share the same origin but not same broadcast messages. - nsresult rv; - nsCOMPtr appsService = - do_GetService("@mozilla.org/AppsService;1", &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(rv); + aOrigin = tmp; + if (!aOrigin.EqualsASCII("null")) { return; } - appsService->GetManifestURLByLocalId(appId, aOrigin); + nsCOMPtr uri; + aRv = aPrincipal->GetURI(getter_AddRefs(uri)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + if (NS_WARN_IF(!uri)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + nsAutoCString spec; + aRv = uri->GetSpec(spec); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + aOrigin = NS_ConvertUTF8toUTF16(spec); } nsIPrincipal* diff --git a/dom/broadcastchannel/tests/chrome.ini b/dom/broadcastchannel/tests/chrome.ini index 52a82302a612..a61b9b5f5269 100644 --- a/dom/broadcastchannel/tests/chrome.ini +++ b/dom/broadcastchannel/tests/chrome.ini @@ -1,5 +1,5 @@ [DEFAULT] -skip-if = buildapp == 'b2g' +skip-if = e10s || buildapp == 'b2g' support-files = blank.html diff --git a/dom/broadcastchannel/tests/file_mozbrowser.html b/dom/broadcastchannel/tests/file_mozbrowser.html new file mode 100644 index 000000000000..5f69021320fc --- /dev/null +++ b/dom/broadcastchannel/tests/file_mozbrowser.html @@ -0,0 +1,20 @@ + + + + + MozBrowser iframe + + +
+ + + diff --git a/dom/broadcastchannel/tests/file_mozbrowser2.html b/dom/broadcastchannel/tests/file_mozbrowser2.html new file mode 100644 index 000000000000..85abce7bff1b --- /dev/null +++ b/dom/broadcastchannel/tests/file_mozbrowser2.html @@ -0,0 +1,21 @@ + + + + + MozBrowser iframe + + +
+ + + diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser.html b/dom/broadcastchannel/tests/iframe_mozbrowser.html new file mode 100644 index 000000000000..dbcf63d8831b --- /dev/null +++ b/dom/broadcastchannel/tests/iframe_mozbrowser.html @@ -0,0 +1,15 @@ + + + + + MozBrowser iframe + + + + + diff --git a/dom/broadcastchannel/tests/iframe_mozbrowser2.html b/dom/broadcastchannel/tests/iframe_mozbrowser2.html new file mode 100644 index 000000000000..dbcf63d8831b --- /dev/null +++ b/dom/broadcastchannel/tests/iframe_mozbrowser2.html @@ -0,0 +1,15 @@ + + + + + MozBrowser iframe + + + + + diff --git a/dom/broadcastchannel/tests/manifest.webapp b/dom/broadcastchannel/tests/manifest.webapp new file mode 100644 index 000000000000..1f334f394e48 --- /dev/null +++ b/dom/broadcastchannel/tests/manifest.webapp @@ -0,0 +1,6 @@ +{ + "name": "BroadcastChannel", + "description": "BroadcastChannel app", + "launch_path": "/tests/dom/broadcastchannel/tests/TESTTOKEN", + "icons": { "128": "default_icon" } +} diff --git a/dom/broadcastchannel/tests/mochitest.ini b/dom/broadcastchannel/tests/mochitest.ini index ab8869a45367..8ba17d77cfdf 100644 --- a/dom/broadcastchannel/tests/mochitest.ini +++ b/dom/broadcastchannel/tests/mochitest.ini @@ -6,6 +6,12 @@ support-files = broadcastchannel_worker.js broadcastchannel_worker_alive.js broadcastchannel_worker_any.js + file_mozbrowser.html + file_mozbrowser2.html + iframe_mozbrowser.html + iframe_mozbrowser2.html + server.sjs + manifest.webapp [test_broadcastchannel_any.html] [test_broadcastchannel_basic.html] @@ -15,3 +21,7 @@ support-files = [test_broadcastchannel_sharedWorker.html] [test_broadcastchannel_worker.html] [test_broadcastchannel_worker_alive.html] +[test_broadcastchannel_mozbrowser.html] +skip-if = e10s || buildapp == 'b2g' +[test_broadcastchannel_mozbrowser2.html] +skip-if = e10s || buildapp == 'b2g' diff --git a/dom/broadcastchannel/tests/server.sjs b/dom/broadcastchannel/tests/server.sjs new file mode 100644 index 000000000000..303e0ece6a14 --- /dev/null +++ b/dom/broadcastchannel/tests/server.sjs @@ -0,0 +1,56 @@ +var gBasePath = "tests/dom/broadcastchannel/tests/"; + +function handleRequest(request, response) { + var query = getQuery(request); + + var testToken = ''; + if ('testToken' in query) { + testToken = query.testToken; + } + + var template = 'manifest.webapp'; + if ('template' in query) { + template = query.template; + } + var template = gBasePath + template; + response.setHeader("Content-Type", "application/x-web-app-manifest+json", false); + response.write(readTemplate(template).replace(/TESTTOKEN/g, testToken)); +} + +// Copy-pasted incantations. There ought to be a better way to synchronously read +// a file into a string, but I guess we're trying to discourage that. +function readTemplate(path) { + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("CurWorkD", Components.interfaces.nsILocalFile); + var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. + createInstance(Components.interfaces.nsIFileInputStream); + var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + var split = path.split("/"); + for(var i = 0; i < split.length; ++i) { + file.append(split[i]); + } + fis.init(file, -1, -1, false); + cis.init(fis, "UTF-8", 0, 0); + + var data = ""; + let str = {}; + let read = 0; + do { + read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value + data += str.value; + } while (read != 0); + cis.close(); + return data; +} + +function getQuery(request) { + var query = {}; + request.queryString.split('&').forEach(function (val) { + var [name, value] = val.split('='); + query[name] = unescape(value); + }); + return query; +} + diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_mozbrowser.html b/dom/broadcastchannel/tests/test_broadcastchannel_mozbrowser.html new file mode 100644 index 000000000000..f5748bc8f49b --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_mozbrowser.html @@ -0,0 +1,137 @@ + + + + Test for BroadcastChannel - iframe mozbrowser + + + + + +
+ + + + diff --git a/dom/broadcastchannel/tests/test_broadcastchannel_mozbrowser2.html b/dom/broadcastchannel/tests/test_broadcastchannel_mozbrowser2.html new file mode 100644 index 000000000000..52a0a15ff461 --- /dev/null +++ b/dom/broadcastchannel/tests/test_broadcastchannel_mozbrowser2.html @@ -0,0 +1,137 @@ + + + + Test for BroadcastChannel - iframe mozbrowser + + + + + +
+ + + + diff --git a/dom/browser-element/BrowserElementChildPreload.js b/dom/browser-element/BrowserElementChildPreload.js index 157cc746cfbb..d272235e8559 100644 --- a/dom/browser-element/BrowserElementChildPreload.js +++ b/dom/browser-element/BrowserElementChildPreload.js @@ -172,6 +172,11 @@ BrowserElementChild.prototype = { /* useCapture = */ true, /* wantsUntrusted = */ false); + addEventListener('click', + this._ClickHandler.bind(this), + /* useCapture = */ false, + /* wantsUntrusted = */ false); + // This listens to unload events from our message manager, but /not/ from // the |content| window. That's because the window's unload event doesn't // bubble, and we're not using a capturing listener. If we'd used @@ -579,6 +584,18 @@ BrowserElementChild.prototype = { sendAsyncMsg('scrollviewchange', detail); }, + _ClickHandler: function(e) { + let elem = e.target; + if (elem instanceof Ci.nsIDOMHTMLAnchorElement && elem.href) { + // Open in a new tab if middle click or ctrl/cmd-click. + if ((Services.appinfo.OS == 'Darwin' && e.metaKey) || + (Services.appinfo.OS != 'Darwin' && e.ctrlKey) || + e.button == 1) { + sendAsyncMsg('opentab', {url: elem.href}); + } + } + }, + _selectionStateChangedHandler: function(e) { e.stopPropagation(); @@ -1085,7 +1102,7 @@ BrowserElementChild.prototype = { _updateVisibility: function() { var visible = this._forcedVisible && this._ownerVisible; - if (docShell.isActive !== visible) { + if (docShell && docShell.isActive !== visible) { docShell.isActive = visible; sendAsyncMsg('visibilitychange', {visible: visible}); } diff --git a/dom/browser-element/BrowserElementParent.js b/dom/browser-element/BrowserElementParent.js index 4ca38f206ce9..2c592e197739 100644 --- a/dom/browser-element/BrowserElementParent.js +++ b/dom/browser-element/BrowserElementParent.js @@ -214,7 +214,8 @@ BrowserElementParent.prototype = { "metachange": this._fireEventFromMsg, "resize": this._fireEventFromMsg, "activitydone": this._fireEventFromMsg, - "scroll": this._fireEventFromMsg + "scroll": this._fireEventFromMsg, + "opentab": this._fireEventFromMsg }; this._mm.addMessageListener('browser-element-api:call', function(aMsg) { diff --git a/dom/browser-element/mochitest/browserElement_OpenTab.js b/dom/browser-element/mochitest/browserElement_OpenTab.js new file mode 100644 index 000000000000..22c35166a99a --- /dev/null +++ b/dom/browser-element/mochitest/browserElement_OpenTab.js @@ -0,0 +1,69 @@ +/* Any copyright is dedicated to the public domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Bug 1144015 - test middle/ctrl/cmd-click on a link. + +"use strict"; +SimpleTest.waitForExplicitFinish(); +browserElementTestHelpers.setEnabledPref(true); +browserElementTestHelpers.addPermission(); + +function runTest() { + let iframe = document.createElement('iframe'); + iframe.setAttribute('mozbrowser', 'true'); + document.body.appendChild(iframe); + + let x = 2; + let y = 2; + // First we force a reflow so that getChildProcessOffset actually returns + // meaningful data. + iframe.getBoundingClientRect(); + // We need to make sure the event coordinates are actually inside the iframe, + // relative to the chome window. + let tabParent = SpecialPowers.wrap(iframe) + .QueryInterface(SpecialPowers.Ci.nsIFrameLoaderOwner) + .frameLoader.tabParent; + if (tabParent) { + let offsetX = {}; + let offsetY = {}; + tabParent.getChildProcessOffset(offsetX, offsetY); + x -= offsetX.value; + y -= offsetY.value; + } + + let sendCtrlClick = () => { + let nsIDOMWindowUtils = SpecialPowers.Ci.nsIDOMWindowUtils; + let mod = nsIDOMWindowUtils.MODIFIER_META | + nsIDOMWindowUtils.MODIFIER_CONTROL; + iframe.sendMouseEvent('mousedown', x, y, 0, 1, mod); + iframe.sendMouseEvent('mouseup', x, y, 0, 1, mod); + } + + let onCtrlClick = e => { + is(e.detail.url, 'http://example.com/', 'URL matches'); + iframe.removeEventListener('mozbrowseropentab', onCtrlClick); + iframe.addEventListener('mozbrowseropentab', onMiddleClick); + sendMiddleClick(); + } + + let sendMiddleClick = () => { + iframe.sendMouseEvent('mousedown', x, y, 1, 1, 0); + iframe.sendMouseEvent('mouseup', x, y, 1, 1, 0); + } + + let onMiddleClick= e => { + is(e.detail.url, 'http://example.com/', 'URL matches'); + iframe.removeEventListener('mozbrowseropentab', onMiddleClick); + SimpleTest.finish(); + } + + iframe.addEventListener('mozbrowserloadend', e => { + iframe.addEventListener('mozbrowseropentab', onCtrlClick); + sendCtrlClick(); + }); + + + iframe.src = 'data:text/html,click here'; +} + +addEventListener('testready', runTest); diff --git a/dom/browser-element/mochitest/mochitest-oop.ini b/dom/browser-element/mochitest/mochitest-oop.ini index 8b3f0c085c77..ff8e7f660769 100644 --- a/dom/browser-element/mochitest/mochitest-oop.ini +++ b/dom/browser-element/mochitest/mochitest-oop.ini @@ -7,6 +7,7 @@ skip-if = os == "android" || (toolkit == "cocoa" && debug) || buildapp == 'mulet support-files = browserElement_OpenMixedProcess.js file_browserElement_OpenMixedProcess.html + browserElement_OpenTab.js [test_browserElement_oop_ThemeColor.html] [test_browserElement_inproc_ErrorSecurity.html] @@ -60,6 +61,8 @@ skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_OpenWindowRejected.html] skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_oop_Opensearch.html] +[test_browserElement_oop_OpenTab.html] +skip-if = (toolkit == 'gonk') # Disabled on emulator. See bug 1144015 comment 8 [test_browserElement_oop_PrivateBrowsing.html] [test_browserElement_oop_PromptCheck.html] [test_browserElement_oop_PromptConfirm.html] diff --git a/dom/browser-element/mochitest/mochitest.ini b/dom/browser-element/mochitest/mochitest.ini index 577e6e569d8d..b263bd50f00f 100644 --- a/dom/browser-element/mochitest/mochitest.ini +++ b/dom/browser-element/mochitest/mochitest.ini @@ -38,6 +38,7 @@ support-files = browserElement_Metachange.js browserElement_NextPaint.js browserElement_OpenNamed.js + browserElement_OpenTab.js browserElement_OpenWindow.js browserElement_OpenWindowDifferentOrigin.js browserElement_OpenWindowInFrame.js @@ -168,6 +169,8 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only [test_browserElement_inproc_NextPaint.html] [test_browserElement_inproc_OpenNamed.html] skip-if = (toolkit == 'gonk' && !debug) +[test_browserElement_inproc_OpenTab.html] +disabled = won't work as Firefox desktop will intercept ctrl-click [test_browserElement_inproc_OpenWindow.html] skip-if = (toolkit == 'gonk' && !debug) [test_browserElement_inproc_OpenWindowDifferentOrigin.html] diff --git a/dom/browser-element/mochitest/test_browserElement_inproc_OpenTab.html b/dom/browser-element/mochitest/test_browserElement_inproc_OpenTab.html new file mode 100644 index 000000000000..85b979bd0a7e --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_inproc_OpenTab.html @@ -0,0 +1,19 @@ + + + + + Test for Bug 1144015 + + + + + +Mozilla Bug 1144015 + + + + + diff --git a/dom/browser-element/mochitest/test_browserElement_oop_OpenTab.html b/dom/browser-element/mochitest/test_browserElement_oop_OpenTab.html new file mode 100644 index 000000000000..85b979bd0a7e --- /dev/null +++ b/dom/browser-element/mochitest/test_browserElement_oop_OpenTab.html @@ -0,0 +1,19 @@ + + + + + Test for Bug 1144015 + + + + + +Mozilla Bug 1144015 + + + + + diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 4f74a0046da9..76f9cb05ab4e 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -113,6 +113,7 @@ #include "nsSVGLength2.h" #include "nsDeviceContext.h" #include "nsFontMetrics.h" +#include "Units.h" #undef free // apparently defined by some windows header, clashing with a free() // method in SkTypes.h @@ -1349,7 +1350,9 @@ CanvasRenderingContext2D::EnsureTarget(RenderingMode aRenderingMode) } if (layerManager) { - if (mode == RenderingMode::OpenGLBackendMode && CheckSizeForSkiaGL(size)) { + if (mode == RenderingMode::OpenGLBackendMode && + gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas() && + CheckSizeForSkiaGL(size)) { DemoteOldestContextIfNecessary(); #if USE_SKIA_GPU @@ -4461,7 +4464,8 @@ CanvasRenderingContext2D::DrawDirectlyToCanvas( // FLAG_CLAMP is added for increased performance, since we never tile here. uint32_t modifiedFlags = image.mDrawingFlags | imgIContainer::FLAG_CLAMP; - SVGImageContext svgContext(scaledImageSize, Nothing(), CurrentState().globalAlpha); + CSSIntSize sz(scaledImageSize.width, scaledImageSize.height); // XXX hmm is scaledImageSize really in CSS pixels? + SVGImageContext svgContext(sz, Nothing(), CurrentState().globalAlpha); auto result = image.mImgContainer-> Draw(context, scaledImageSize, diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp index bad7918727de..0e66279d6e12 100644 --- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -12,8 +12,10 @@ #include "nsIUnicodeDecoder.h" #include "nsIUnicodeEncoder.h" +#include "nsCharSeparatedTokenizer.h" #include "nsDOMString.h" #include "nsNetUtil.h" +#include "nsReadableUtils.h" #include "nsStreamUtils.h" #include "nsStringStream.h" @@ -515,6 +517,382 @@ ExtractFromURLSearchParams(const URLSearchParams& aParams, aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8"); return NS_NewStringInputStream(aStream, serialized); } + +void +FillFormData(const nsString& aName, const nsString& aValue, void* aFormData) +{ + MOZ_ASSERT(aFormData); + nsFormData* fd = static_cast(aFormData); + fd->Append(aName, aValue); +} + +/** + * A simple multipart/form-data parser as defined in RFC 2388 and RFC 2046. + * This does not respect any encoding specified per entry, using UTF-8 + * throughout. This is as the Fetch spec states in the consume body algorithm. + * Borrows some things from Necko's nsMultiMixedConv, but is simpler since + * unlike Necko we do not have to deal with receiving incomplete chunks of data. + * + * This parser will fail the entire parse on any invalid entry, so it will + * never return a partially filled FormData. + * The content-disposition header is used to figure out the name and filename + * entries. The inclusion of the filename parameter decides if the entry is + * inserted into the nsFormData as a string or a File. + * + * File blobs are copies of the underlying data string since we cannot adopt + * char* chunks embedded within the larger body without significant effort. + * FIXME(nsm): Bug 1127552 - We should add telemetry to calls to formData() and + * friends to figure out if Fetch ends up copying big blobs to see if this is + * worth optimizing. + */ +class MOZ_STACK_CLASS FormDataParser +{ +private: + nsRefPtr mFormData; + nsCString mMimeType; + nsCString mData; + + // Entry state, reset in START_PART. + nsCString mName; + nsCString mFilename; + nsCString mContentType; + + enum + { + START_PART, + PARSE_HEADER, + PARSE_BODY, + } mState; + + nsIGlobalObject* mParentObject; + + // Reads over a boundary and sets start to the position after the end of the + // boundary. Returns false if no boundary is found immediately. + bool + PushOverBoundary(const nsACString& aBoundaryString, + nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd) + { + // We copy the end iterator to keep the original pointing to the real end + // of the string. + nsACString::const_iterator end(aEnd); + const char* beginning = aStart.get(); + if (FindInReadable(aBoundaryString, aStart, end)) { + // We either should find the body immediately, or after 2 chars with the + // 2 chars being '-', everything else is failure. + if ((aStart.get() - beginning) == 0) { + aStart.advance(aBoundaryString.Length()); + return true; + } + + if ((aStart.get() - beginning) == 2) { + if (*(--aStart) == '-' && *(--aStart) == '-') { + aStart.advance(aBoundaryString.Length() + 2); + return true; + } + } + } + + return false; + } + + // Reads over a CRLF and positions start after it. + bool + PushOverLine(nsACString::const_iterator& aStart) + { + if (*aStart == nsCRT::CR && (aStart.size_forward() > 1) && *(++aStart) == nsCRT::LF) { + ++aStart; // advance to after CRLF + return true; + } + + return false; + } + + bool + FindCRLF(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd) + { + nsACString::const_iterator end(aEnd); + return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end); + } + + bool + ParseHeader(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd, + bool* aWasEmptyHeader) + { + MOZ_ASSERT(aWasEmptyHeader); + // Set it to a valid value here so we don't forget later. + *aWasEmptyHeader = false; + + const char* beginning = aStart.get(); + nsACString::const_iterator end(aEnd); + if (!FindCRLF(aStart, end)) { + return false; + } + + if (aStart.get() == beginning) { + *aWasEmptyHeader = true; + return true; + } + + nsAutoCString header(beginning, aStart.get() - beginning); + + nsACString::const_iterator headerStart, headerEnd; + header.BeginReading(headerStart); + header.EndReading(headerEnd); + if (!FindCharInReadable(':', headerStart, headerEnd)) { + return false; + } + + nsAutoCString headerName(StringHead(header, headerStart.size_backward())); + headerName.CompressWhitespace(); + if (!NS_IsValidHTTPToken(headerName)) { + return false; + } + + nsAutoCString headerValue(Substring(++headerStart, headerEnd)); + if (!NS_IsReasonableHTTPHeaderValue(headerValue)) { + return false; + } + headerValue.CompressWhitespace(); + + if (headerName.LowerCaseEqualsLiteral("content-disposition")) { + nsCCharSeparatedTokenizer tokenizer(headerValue, ';'); + bool seenFormData = false; + while (tokenizer.hasMoreTokens()) { + const nsDependentCSubstring& token = tokenizer.nextToken(); + if (token.IsEmpty()) { + continue; + } + + if (token.EqualsLiteral("form-data")) { + seenFormData = true; + continue; + } + + if (seenFormData && + StringBeginsWith(token, NS_LITERAL_CSTRING("name="))) { + mName = StringTail(token, token.Length() - 5); + mName.Trim(" \""); + continue; + } + + if (seenFormData && + StringBeginsWith(token, NS_LITERAL_CSTRING("filename="))) { + mFilename = StringTail(token, token.Length() - 9); + mFilename.Trim(" \""); + continue; + } + } + + if (mName.IsVoid()) { + // Could not parse a valid entry name. + return false; + } + } else if (headerName.LowerCaseEqualsLiteral("content-type")) { + mContentType = headerValue; + } + + return true; + } + + // The end of a body is marked by a CRLF followed by the boundary. So the + // CRLF is part of the boundary and not the body, but any prior CRLFs are + // part of the body. This will position the iterator at the beginning of the + // boundary (after the CRLF). + bool + ParseBody(const nsACString& aBoundaryString, + nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd) + { + const char* beginning = aStart.get(); + + // Find the boundary marking the end of the body. + nsACString::const_iterator end(aEnd); + if (!FindInReadable(aBoundaryString, aStart, end)) { + return false; + } + + // We found a boundary, strip the just prior CRLF, and consider + // everything else the body section. + if (aStart.get() - beginning < 2) { + // Only the first entry can have a boundary right at the beginning. Even + // an empty body will have a CRLF before the boundary. So this is + // a failure. + return false; + } + + // Check that there is a CRLF right before the boundary. + aStart.advance(-2); + + // Skip optional hyphens. + if (*aStart == '-' && *(aStart.get()+1) == '-') { + if (aStart.get() - beginning < 2) { + return false; + } + + aStart.advance(-2); + } + + if (*aStart != nsCRT::CR || *(aStart.get()+1) != nsCRT::LF) { + return false; + } + + nsAutoCString body(beginning, aStart.get() - beginning); + + // Restore iterator to after the \r\n as we promised. + // We do not need to handle the extra hyphens case since our boundary + // parser in PushOverBoundary() + aStart.advance(2); + + if (!mFormData) { + mFormData = new nsFormData(); + } + + NS_ConvertUTF8toUTF16 name(mName); + + if (mFilename.IsVoid()) { + mFormData->Append(name, NS_ConvertUTF8toUTF16(body)); + } else { + // Unfortunately we've to copy the data first since all our strings are + // going to free it. We also need fallible alloc, so we can't just use + // ToNewCString(). + char* copy = static_cast(NS_Alloc(body.Length())); + if (!copy) { + NS_WARNING("Failed to copy File entry body."); + return false; + } + nsCString::const_iterator bodyIter, bodyEnd; + body.BeginReading(bodyIter); + body.EndReading(bodyEnd); + char *p = copy; + while (bodyIter != bodyEnd) { + *p++ = *bodyIter++; + } + p = nullptr; + + nsRefPtr file = + File::CreateMemoryFile(mParentObject, + reinterpret_cast(copy), body.Length(), + NS_ConvertUTF8toUTF16(mFilename), + NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0); + Optional dummy; + mFormData->Append(name, *file, dummy); + } + + return true; + } + +public: + FormDataParser(const nsACString& aMimeType, const nsACString& aData, nsIGlobalObject* aParent) + : mMimeType(aMimeType), mData(aData), mState(START_PART), mParentObject(aParent) + { + } + + bool + Parse() + { + // Determine boundary from mimetype. + const char* boundaryId = nullptr; + boundaryId = strstr(mMimeType.BeginWriting(), "boundary"); + if (!boundaryId) { + return false; + } + + boundaryId = strchr(boundaryId, '='); + if (!boundaryId) { + return false; + } + + // Skip over '='. + boundaryId++; + + char *attrib = (char *) strchr(boundaryId, ';'); + if (attrib) *attrib = '\0'; + + nsAutoCString boundaryString(boundaryId); + if (attrib) *attrib = ';'; + + boundaryString.Trim(" \""); + + if (boundaryString.Length() == 0) { + return false; + } + + nsACString::const_iterator start, end; + mData.BeginReading(start); + // This should ALWAYS point to the end of data. + // Helpers make copies. + mData.EndReading(end); + + while (start != end) { + switch(mState) { + case START_PART: + mName.SetIsVoid(true); + mFilename.SetIsVoid(true); + mContentType = NS_LITERAL_CSTRING("text/plain"); + + // MUST start with boundary. + if (!PushOverBoundary(boundaryString, start, end)) { + return false; + } + + if (start != end && *start == '-') { + // End of data. + if (!mFormData) { + mFormData = new nsFormData(); + } + return true; + } + + if (!PushOverLine(start)) { + return false; + } + mState = PARSE_HEADER; + break; + + case PARSE_HEADER: + bool emptyHeader; + if (!ParseHeader(start, end, &emptyHeader)) { + return false; + } + + if (!PushOverLine(start)) { + return false; + } + + mState = emptyHeader ? PARSE_BODY : PARSE_HEADER; + break; + + case PARSE_BODY: + if (mName.IsVoid()) { + NS_WARNING("No content-disposition header with a valid name was " + "found. Failing at body parse."); + return false; + } + + if (!ParseBody(boundaryString, start, end)) { + return false; + } + + mState = START_PART; + break; + + default: + MOZ_CRASH("Invalid case"); + } + } + + NS_NOTREACHED("Should never reach here."); + return false; + } + + already_AddRefed FormData() + { + return mFormData.forget(); + } +}; } // anonymous namespace nsresult @@ -1142,6 +1520,56 @@ FetchBody::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength autoFree.Reset(); return; } + case CONSUME_FORMDATA: { + nsCString data; + data.Adopt(reinterpret_cast(aResult), aResultLength); + autoFree.Reset(); + + NS_NAMED_LITERAL_CSTRING(formDataMimeType, NS_LITERAL_CSTRING("multipart/form-data")); + + // Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary= + // but disallow multipart/form-datafoobar. + bool isValidFormDataMimeType = StringBeginsWith(mMimeType, formDataMimeType); + + if (isValidFormDataMimeType && mMimeType.Length() > formDataMimeType.Length()) { + isValidFormDataMimeType = mMimeType[formDataMimeType.Length()] == ';'; + } + + if (isValidFormDataMimeType) { + FormDataParser parser(mMimeType, data, DerivedClass()->GetParentObject()); + if (!parser.Parse()) { + ErrorResult result; + result.ThrowTypeError(MSG_BAD_FORMDATA); + localPromise->MaybeReject(result); + return; + } + + nsRefPtr fd = parser.FormData(); + MOZ_ASSERT(fd); + localPromise->MaybeResolve(fd); + } else { + NS_NAMED_LITERAL_CSTRING(urlDataMimeType, NS_LITERAL_CSTRING("application/x-www-form-urlencoded")); + bool isValidUrlEncodedMimeType = StringBeginsWith(mMimeType, urlDataMimeType); + + if (isValidUrlEncodedMimeType && mMimeType.Length() > urlDataMimeType.Length()) { + isValidUrlEncodedMimeType = mMimeType[urlDataMimeType.Length()] == ';'; + } + + if (isValidUrlEncodedMimeType) { + nsRefPtr params = new URLSearchParams(); + params->ParseInput(data, /* aObserver */ nullptr); + + nsRefPtr fd = new nsFormData(DerivedClass()->GetParentObject()); + params->ForEach(FillFormData, static_cast(fd)); + localPromise->MaybeResolve(fd); + } else { + ErrorResult result; + result.ThrowTypeError(MSG_BAD_FORMDATA); + localPromise->MaybeReject(result); + } + } + return; + } case CONSUME_TEXT: // fall through handles early exit. case CONSUME_JSON: { @@ -1216,15 +1644,15 @@ FetchBody::ConsumeBody(ConsumeType aType, ErrorResult& aRv); template void -FetchBody::SetMimeType(ErrorResult& aRv) +FetchBody::SetMimeType() { // Extract mime type. + ErrorResult result; nsTArray contentTypeValues; MOZ_ASSERT(DerivedClass()->GetInternalHeaders()); - DerivedClass()->GetInternalHeaders()->GetAll(NS_LITERAL_CSTRING("Content-Type"), contentTypeValues, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return; - } + DerivedClass()->GetInternalHeaders()->GetAll(NS_LITERAL_CSTRING("Content-Type"), + contentTypeValues, result); + MOZ_ALWAYS_TRUE(!result.Failed()); // HTTP ABNF states Content-Type may have only one value. // This is from the "parse a header value" of the fetch spec. @@ -1236,10 +1664,10 @@ FetchBody::SetMimeType(ErrorResult& aRv) template void -FetchBody::SetMimeType(ErrorResult& aRv); +FetchBody::SetMimeType(); template void -FetchBody::SetMimeType(ErrorResult& aRv); +FetchBody::SetMimeType(); } // namespace dom } // namespace mozilla diff --git a/dom/fetch/Fetch.h b/dom/fetch/Fetch.h index 33e75bf69db4..d83225ee5b5b 100644 --- a/dom/fetch/Fetch.h +++ b/dom/fetch/Fetch.h @@ -113,6 +113,12 @@ public: return ConsumeBody(CONSUME_BLOB, aRv); } + already_AddRefed + FormData(ErrorResult& aRv) + { + return ConsumeBody(CONSUME_FORMDATA, aRv); + } + already_AddRefed Json(ErrorResult& aRv) { @@ -154,13 +160,13 @@ protected: virtual ~FetchBody(); void - SetMimeType(ErrorResult& aRv); + SetMimeType(); private: enum ConsumeType { CONSUME_ARRAYBUFFER, CONSUME_BLOB, - // FormData not supported right now, + CONSUME_FORMDATA, CONSUME_JSON, CONSUME_TEXT, }; diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp index 685ad3c9700c..78f3e32dc861 100644 --- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -34,6 +34,7 @@ Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest) , mOwner(aOwner) , mRequest(aRequest) { + SetMimeType(); } Request::~Request() @@ -264,7 +265,7 @@ Request::Constructor(const GlobalObject& aGlobal, } nsRefPtr domRequest = new Request(global, request); - domRequest->SetMimeType(aRv); + domRequest->SetMimeType(); return domRequest.forget(); } diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp index ab3a8dde0469..730b9394ef3c 100644 --- a/dom/fetch/Response.cpp +++ b/dom/fetch/Response.cpp @@ -38,6 +38,7 @@ Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse , mOwner(aGlobal) , mInternalResponse(aInternalResponse) { + SetMimeType(); } Response::~Response() @@ -188,7 +189,7 @@ Response::Constructor(const GlobalObject& aGlobal, } } - r->SetMimeType(aRv); + r->SetMimeType(); return r.forget(); } diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 4e75db6e5700..0881cb4072f6 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -1831,7 +1831,7 @@ HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv) } Sequence list; list.AppendElement(aValue); - MozSetFileNameArray(list); + MozSetFileNameArray(list, aRv); return; } else { @@ -2352,8 +2352,13 @@ HTMLInputElement::MozSetFileArray(const Sequence>& aFiles) } void -HTMLInputElement::MozSetFileNameArray(const Sequence< nsString >& aFileNames) +HTMLInputElement::MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv) { + if (XRE_GetProcessType() == GeckoProcessType_Content) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + nsTArray> files; for (uint32_t i = 0; i < aFileNames.Length(); ++i) { nsCOMPtr file; @@ -2397,8 +2402,9 @@ HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, uint32_t aLen list.AppendElement(nsDependentString(aFileNames[i])); } - MozSetFileNameArray(list); - return NS_OK; + ErrorResult rv; + MozSetFileNameArray(list, rv); + return rv.ErrorCode(); } bool @@ -2445,8 +2451,9 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) { Sequence list; list.AppendElement(aValue); - MozSetFileNameArray(list); - return NS_OK; + ErrorResult rv; + MozSetFileNameArray(list, rv); + return rv.ErrorCode(); } else { nsresult rv = SetValueInternal(aValue, true, true); NS_ENSURE_SUCCESS(rv, rv); diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 8c648ae9fb5f..3e431325f078 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -715,7 +715,7 @@ public: void MozGetFileNameArray(nsTArray< nsString >& aFileNames); - void MozSetFileNameArray(const Sequence< nsString >& aFileNames); + void MozSetFileNameArray(const Sequence< nsString >& aFileNames, ErrorResult& aRv); void MozSetFileArray(const Sequence>& aFiles); HTMLInputElement* GetOwnerNumberControl(); diff --git a/dom/html/reftests/reftest.list b/dom/html/reftests/reftest.list index f03161b527e9..06b6f3196799 100644 --- a/dom/html/reftests/reftest.list +++ b/dom/html/reftests/reftest.list @@ -39,7 +39,7 @@ skip-if(Android||B2G) == 649134-2.html 649134-2-ref.html # (Fuzzy necessary due to pixel-wise comparison of different JPEGs. # The vast majority of the fuzziness comes from Linux and WinXP.) fuzzy(1,149) == bug917595-iframe-1.html bug917595-1-ref.html -skip-if(B2G) fuzzy-if(!B2G,3,640) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg # bug 1060869 +skip-if(B2G||Mulet) fuzzy-if((!B2G&&!Mulet),3,640) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg # bug 1060869 # Bug 1150490 disabling on Mulet as on B2G # Test support for SVG-as-image in elements. pref(dom.image.picture.enabled,true) pref(dom.image.srcset.enabled,true) == bug1106522-1.html bug1106522-ref.html diff --git a/dom/html/test/formSubmission_chrome.js b/dom/html/test/formSubmission_chrome.js new file mode 100644 index 000000000000..97fd4f18c2dc --- /dev/null +++ b/dom/html/test/formSubmission_chrome.js @@ -0,0 +1,6 @@ +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +Cu.importGlobalProperties(["File"]); + +addMessageListener("files.open", function (message) { + sendAsyncMessage("files.opened", message.map(path => new File(path))); +}); diff --git a/dom/html/test/forms/mochitest.ini b/dom/html/test/forms/mochitest.ini index cc2ae1da8327..0e4a1d8d361c 100644 --- a/dom/html/test/forms/mochitest.ini +++ b/dom/html/test/forms/mochitest.ini @@ -68,12 +68,10 @@ skip-if = buildapp == 'mulet' [test_label_control_attribute.html] [test_label_input_controls.html] [test_max_attribute.html] -skip-if = e10s [test_maxlength_attribute.html] [test_meter_element.html] [test_meter_pseudo-classes.html] [test_min_attribute.html] -skip-if = e10s [test_mozistextfield.html] [test_novalidate_attribute.html] [test_option_disabled.html] @@ -85,14 +83,12 @@ skip-if = e10s [test_radio_in_label.html] [test_radio_radionodelist.html] [test_required_attribute.html] -skip-if = e10s [test_restore_form_elements.html] [test_save_restore_radio_groups.html] [test_select_selectedOptions.html] [test_select_validation.html] [test_set_range_text.html] [test_step_attribute.html] -skip-if = e10s [test_stepup_stepdown.html] [test_textarea_attributes_reflection.html] [test_validation.html] diff --git a/dom/html/test/forms/test_max_attribute.html b/dom/html/test/forms/test_max_attribute.html index 15acb74541b7..37aa12487314 100644 --- a/dom/html/test/forms/test_max_attribute.html +++ b/dom/html/test/forms/test_max_attribute.html @@ -175,14 +175,9 @@ for (var test of data) { checkValidity(input, true, apply, apply); break; case 'file': - var dirSvc = SpecialPowers.Cc["@mozilla.org/file/directory_service;1"] - .getService(SpecialPowers.Ci.nsIProperties); - var file = dirSvc.get("ProfD", SpecialPowers.Ci.nsIFile); - file.append('635499_file'); - // Only the file's path is used, so it doesn't need to be created. - // See also bug 1058977. + var file = new File([''], '635499_file'); - SpecialPowers.wrap(input).value = file.path; + SpecialPowers.wrap(input).mozSetFileArray([file]); checkValidity(input, true, apply, apply); break; diff --git a/dom/html/test/forms/test_min_attribute.html b/dom/html/test/forms/test_min_attribute.html index b114ff928b1a..0539c0a83eba 100644 --- a/dom/html/test/forms/test_min_attribute.html +++ b/dom/html/test/forms/test_min_attribute.html @@ -173,14 +173,9 @@ for (var test of data) { checkValidity(input, true, apply, apply); break; case 'file': - var dirSvc = SpecialPowers.Cc["@mozilla.org/file/directory_service;1"] - .getService(SpecialPowers.Ci.nsIProperties); - var file = dirSvc.get("ProfD", SpecialPowers.Ci.nsIFile); - file.append('635499_file'); - // Only the file's path is used, so it doesn't need to be created. - // See also bug 1058977. + var file = new File([''], '635499_file'); - SpecialPowers.wrap(input).value = file.path; + SpecialPowers.wrap(input).mozSetFileArray([file]); checkValidity(input, true, apply, apply); break; diff --git a/dom/html/test/forms/test_pattern_attribute.html b/dom/html/test/forms/test_pattern_attribute.html index 2a0fbaa81aa3..92531764202f 100644 --- a/dom/html/test/forms/test_pattern_attribute.html +++ b/dom/html/test/forms/test_pattern_attribute.html @@ -41,9 +41,12 @@ function completeValidityCheck(element, alwaysValid, isBarred) } else if (element.type == 'url') { element.pattern = "http://.*\\.com$"; element.value = "http://mozilla.com"; + } else if (element.type == 'file') { + element.pattern = "foo"; + SpecialPowers.wrap(element).mozSetFileArray([new File(["foo"], "foo")]); } else { element.pattern = "foo"; - SpecialPowers.wrap(element).value = "foo"; + element.value = "foo"; } checkValidPattern(element, true, isBarred); @@ -56,9 +59,12 @@ function completeValidityCheck(element, alwaysValid, isBarred) } else if (element.type == 'url') { element.pattern = "http://.*\\.com$"; element.value = "http://mozilla.org"; + } else if (element.type == 'file') { + element.pattern = "foo"; + SpecialPowers.wrap(element).mozSetFileArray([new File(["bar"], "bar")]); } else { element.pattern = "foo"; - SpecialPowers.wrap(element).value = "bar"; + element.value = "bar"; } if (!alwaysValid) { diff --git a/dom/html/test/forms/test_required_attribute.html b/dom/html/test/forms/test_required_attribute.html index e0dbfa44a081..aba583fbbc2d 100644 --- a/dom/html/test/forms/test_required_attribute.html +++ b/dom/html/test/forms/test_required_attribute.html @@ -314,18 +314,7 @@ function checkInputRequiredValidityForFile() element.type = 'file' document.forms[0].appendChild(element); - function createFile(fileName) { - var dirSvc = SpecialPowers.Cc["@mozilla.org/file/directory_service;1"] - .getService(SpecialPowers.Ci.nsIProperties); - var testFile = dirSvc.get("ProfD", SpecialPowers.Ci.nsIFile); - testFile.append(fileName); - // Only the file's path is used, so it doesn't need to be created. - // See also bug 1058977. - - return testFile; - } - - var file = createFile("345822_file"); + var file = new File([""], "345822_file"); SpecialPowers.wrap(element).value = ""; element.required = false; @@ -334,7 +323,7 @@ function checkInputRequiredValidityForFile() element.required = true; checkSufferingFromBeingMissing(element, true); - SpecialPowers.wrap(element).value = file.path; + SpecialPowers.wrap(element).mozSetFileArray([file]); checkNotSufferingFromBeingMissing(element); SpecialPowers.wrap(element).value = ""; @@ -344,7 +333,7 @@ function checkInputRequiredValidityForFile() checkNotSufferingFromBeingMissing(element); element.focus(); - SpecialPowers.wrap(element).value = file.path; + SpecialPowers.wrap(element).mozSetFileArray([file]); element.required = true; element.blur(); element.form.reset(); diff --git a/dom/html/test/forms/test_step_attribute.html b/dom/html/test/forms/test_step_attribute.html index 2bcd21fbbfbc..523f0a735ae3 100644 --- a/dom/html/test/forms/test_step_attribute.html +++ b/dom/html/test/forms/test_step_attribute.html @@ -128,14 +128,9 @@ for (var test of data) { checkValidity(input, true, apply); break; case 'file': - var dirSvc = SpecialPowers.Cc["@mozilla.org/file/directory_service;1"] - .getService(SpecialPowers.Ci.nsIProperties); - var file = dirSvc.get("ProfD", SpecialPowers.Ci.nsIFile); - file.append('635499_file'); - // Only the file's path is used, so it doesn't need to be created. - // See also bug 1058977. + var file = new File([''], '635499_file'); - SpecialPowers.wrap(input).value = file.path; + SpecialPowers.wrap(input).mozSetFileArray([file]); checkValidity(input, true, apply); break; diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini index 6b539726d493..18dd1c535761 100644 --- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -178,6 +178,7 @@ support-files = file_srcdoc.html file_window_open_close_outer.html file_window_open_close_inner.html + formSubmission_chrome.js form_submit_server.sjs formData_worker.js formData_test.js @@ -459,7 +460,7 @@ skip-if = (toolkit == 'gonk' && debug) || e10s #debug-only failure [test_embed_attributes_reflection.html] [test_formData.html] [test_formSubmission.html] -skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT # b2g(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) b2g-debug(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) b2g-desktop(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) +skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) b2g-debug(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) b2g-desktop(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) [test_formSubmission2.html] skip-if = toolkit == 'android' [test_formelements.html] diff --git a/dom/html/test/test_bug143220.html b/dom/html/test/test_bug143220.html index 0aa0ca24468c..355e91ea2369 100644 --- a/dom/html/test/test_bug143220.html +++ b/dom/html/test/test_bug143220.html @@ -18,49 +18,53 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=143220
-
 
diff --git a/dom/html/test/test_formSubmission.html b/dom/html/test/test_formSubmission.html index c7897051b169..db7da4ed840a 100644 --- a/dom/html/test/test_formSubmission.html +++ b/dom/html/test/test_formSubmission.html @@ -399,41 +399,59 @@ t_6_v SimpleTest.waitForExplicitFinish(); -var myFile1, myFile2, emptyFile; +const placeholder_myFile1 = {}; +const placeholder_myFile2 = {}; +const placeholder_emptyFile = {}; -(function setFileNames() { +var myFile1, myFile2, emptyFile; +let openerURL = SimpleTest.getTestFileURL("formSubmission_chrome.js"); +let opener = SpecialPowers.loadChromeScript(openerURL); + +{ let xhr = new XMLHttpRequest; xhr.open("GET", "/dynamic/getMyDirectory.sjs", false); xhr.send(); - var basePath = xhr.responseText; + let basePath = xhr.responseText; - singleFile = basePath + "file_formSubmission_text.txt"; - multiFile = [basePath + "file_formSubmission_text.txt", - basePath + "file_formSubmission_img.jpg"]; + opener.addMessageListener("files.opened", onFilesOpened); + opener.sendAsyncMessage("files.open", [ + basePath + "file_formSubmission_text.txt", + basePath + "file_formSubmission_img.jpg", + ]); +} + +function onFilesOpened(files) { + let [textFile, imageFile] = files; + opener.destroy(); + + let singleFile = textFile; + let multiFile = [textFile, imageFile]; var addList = document.getElementsByClassName("setfile"); let i = 0; var input; while (input = addList[i++]) { if (input.classList.contains("multi")) { - SpecialPowers.wrap(input).mozSetFileNameArray(multiFile, multiFile.length); + SpecialPowers.wrap(input).mozSetFileArray(multiFile); } else { - SpecialPowers.wrap(input).value = singleFile; + SpecialPowers.wrap(input).mozSetFileArray([singleFile]); } } input = document.createElement("input"); input.type = "file"; input.multiple = true; - SpecialPowers.wrap(input).mozSetFileNameArray(multiFile, multiFile.length); + SpecialPowers.wrap(input).mozSetFileArray(multiFile); myFile1 = input.files[0]; myFile2 = input.files[1]; is(myFile1.size, 20, "File1 size"); is(myFile2.size, 2711, "File2 size"); emptyFile = { name: "", type: "application/octet-stream" }; -})(); + // Now, actually run the tests; see below. + onFilesSet(); +}; var expectedSub = [ // Default input @@ -495,13 +513,13 @@ var expectedSub = [ // Reset button // Unknown button // File - { name: "file_1", value: myFile1 }, - { name: "file_2", value: emptyFile }, + { name: "file_1", value: placeholder_myFile1 }, + { name: "file_2", value: placeholder_emptyFile }, // Multiple file - { name: "file_3", value: myFile1 }, - { name: "file_4", value: myFile1 }, - { name: "file_4", value: myFile2 }, - { name: "file_5", value: emptyFile }, + { name: "file_3", value: placeholder_myFile1 }, + { name: "file_4", value: placeholder_myFile1 }, + { name: "file_4", value: placeholder_myFile2 }, + { name: "file_5", value: placeholder_emptyFile }, // Textarea { name: "t1", value: "t_1_v" }, { name: "t2", value: "" }, @@ -562,12 +580,12 @@ var expectedSub = [ { name: "msel_31", value: "msel_31_v1" }, ]; -expectedAugment = [ +var expectedAugment = [ { name: "aName", value: "aValue" }, //{ name: "aNameBool", value: "false" }, { name: "aNameNum", value: "9.2" }, - { name: "aNameFile1", value: myFile1 }, - { name: "aNameFile2", value: myFile2 }, + { name: "aNameFile1", value: placeholder_myFile1 }, + { name: "aNameFile2", value: placeholder_myFile2 }, //{ name: "aNameObj", value: "[object XMLHttpRequest]" }, //{ name: "aNameNull", value: "null" }, //{ name: "aNameUndef", value: "undefined" }, @@ -661,10 +679,13 @@ function setDisabled(list, state) { }); } -var gen = runTest(); -addLoadEvent(function() { - gen.next(); -}); +var gen; +function onFilesSet() { + gen = runTest(); + addLoadEvent(function() { + gen.next(); + }); +} function runTest() { // Set up the expectedSub array @@ -676,17 +697,17 @@ function runTest() { yield undefined; // Wait for both FileReaders. We don't care which order they finish. yield undefined; function fileFixup(o) { - if (o.value == myFile1) { + if (o.value === placeholder_myFile1) { o.value = fileReader1.result; o.fileName = myFile1.name; o.contentType = myFile1.type; } - else if (o.value == myFile2) { + else if (o.value === placeholder_myFile2) { o.value = fileReader2.result; o.fileName = myFile2.name; o.contentType = myFile2.type; } - else if (o.value == emptyFile) { + else if (o.value === placeholder_emptyFile) { o.value = ""; o.fileName = emptyFile.name; o.contentType = emptyFile.type; diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index cfe534db61f2..1cc64f9221e8 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1235,13 +1235,14 @@ StartMacOSContentSandbox() MacSandboxInfo info; info.type = MacSandboxType_Content; - info.appPath.Assign(appPath); - info.appBinaryPath.Assign(appBinaryPath); - info.appDir.Assign(appDir); + info.level = Preferences::GetInt("security.sandbox.content.level"); + info.appPath.assign(appPath.get()); + info.appBinaryPath.assign(appBinaryPath.get()); + info.appDir.assign(appDir.get()); - nsAutoCString err; + std::string err; if (!mozilla::StartMacSandbox(info, err)) { - NS_WARNING(err.get()); + NS_WARNING(err.c_str()); MOZ_CRASH("sandbox_init() failed"); } } diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 29d6887bccb2..2515602fb2b5 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -1579,13 +1579,14 @@ TabParent::RecvNotifyIMEFocus(const bool& aFocus, nsIMEUpdatePreference* aPreference, uint32_t* aSeqno) { + *aSeqno = mIMESeqno; + nsCOMPtr widget = GetWidget(); if (!widget) { *aPreference = nsIMEUpdatePreference(); return true; } - *aSeqno = mIMESeqno; mIMETabParent = aFocus ? this : nullptr; mIMESelectionAnchor = 0; mIMESelectionFocus = 0; diff --git a/dom/ipc/moz.build b/dom/ipc/moz.build index b90d87de5d42..55fc03e567a4 100644 --- a/dom/ipc/moz.build +++ b/dom/ipc/moz.build @@ -111,6 +111,12 @@ FAIL_ON_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' + +if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_TARGET'] == 'Darwin': + USE_LIBS += [ + 'mozsandbox', + ] + LOCAL_INCLUDES += [ '/caps', '/chrome', diff --git a/dom/media/gmp/GMPChild.cpp b/dom/media/gmp/GMPChild.cpp index 3209371f1d99..fbd1577d5f3a 100644 --- a/dom/media/gmp/GMPChild.cpp +++ b/dom/media/gmp/GMPChild.cpp @@ -228,32 +228,31 @@ GetAppPaths(nsCString &aAppPath, nsCString &aAppBinaryPath) return true; } -void -GMPChild::StartMacSandbox() +bool +GMPChild::SetMacSandboxInfo() { + if (!mGMPLoader) { + return false; + } nsAutoCString pluginDirectoryPath, pluginFilePath; if (!GetPluginPaths(mPluginPath, pluginDirectoryPath, pluginFilePath)) { - MOZ_CRASH("Error scanning plugin path"); + return false; } nsAutoCString appPath, appBinaryPath; if (!GetAppPaths(appPath, appBinaryPath)) { - MOZ_CRASH("Error resolving child process path"); + return false; } MacSandboxInfo info; info.type = MacSandboxType_Plugin; info.pluginInfo.type = MacSandboxPluginType_GMPlugin_Default; - info.pluginInfo.pluginPath.Assign(pluginDirectoryPath); - mPluginBinaryPath.Assign(pluginFilePath); - info.pluginInfo.pluginBinaryPath.Assign(pluginFilePath); - info.appPath.Assign(appPath); - info.appBinaryPath.Assign(appBinaryPath); + info.pluginInfo.pluginPath.assign(pluginDirectoryPath.get()); + info.pluginInfo.pluginBinaryPath.assign(pluginFilePath.get()); + info.appPath.assign(appPath.get()); + info.appBinaryPath.assign(appBinaryPath.get()); - nsAutoCString err; - if (!mozilla::StartMacSandbox(info, err)) { - NS_WARNING(err.get()); - MOZ_CRASH("sandbox_init() failed"); - } + mGMPLoader->SetSandboxInfo(&info); + return true; } #endif // XP_MACOSX && MOZ_GMP_SANDBOX @@ -364,24 +363,6 @@ GMPChild::PreLoadLibraries(const std::string& aPluginPath) } #endif -#if defined(MOZ_GMP_SANDBOX) - -#if defined(XP_MACOSX) -class MacOSXSandboxStarter : public SandboxStarter { -public: - explicit MacOSXSandboxStarter(GMPChild* aGMPChild) - : mGMPChild(aGMPChild) - {} - virtual void Start(const char* aLibPath) override { - mGMPChild->StartMacSandbox(); - } -private: - GMPChild* mGMPChild; -}; -#endif - -#endif // MOZ_GMP_SANDBOX - bool GMPChild::GetLibPath(nsACString& aOutLibPath) { @@ -427,8 +408,10 @@ GMPChild::RecvStartPlugin() } #if defined(MOZ_GMP_SANDBOX) && defined(XP_MACOSX) - nsAutoPtr starter(new MacOSXSandboxStarter(this)); - mGMPLoader->SetStartSandboxStarter(starter); + if (!SetMacSandboxInfo()) { + NS_WARNING("Failed to set Mac GMP sandbox info"); + return false; + } #endif if (!mGMPLoader->Load(libPath.get(), diff --git a/dom/media/gmp/GMPChild.h b/dom/media/gmp/GMPChild.h index c3134b4dc118..eb724b2a26ca 100644 --- a/dom/media/gmp/GMPChild.h +++ b/dom/media/gmp/GMPChild.h @@ -44,7 +44,7 @@ public: void ShutdownComplete() override; #if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX) - void StartMacSandbox(); + bool SetMacSandboxInfo(); #endif private: @@ -89,9 +89,6 @@ private: MessageLoop* mGMPMessageLoop; std::string mPluginPath; std::string mVoucherPath; -#if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX) - nsCString mPluginBinaryPath; -#endif std::string mNodeId; GMPLoader* mGMPLoader; nsTArray mPluginVoucher; diff --git a/dom/media/gmp/GMPLoader.cpp b/dom/media/gmp/GMPLoader.cpp index 67e5c602ca0f..a3b6d4dec4ea 100644 --- a/dom/media/gmp/GMPLoader.cpp +++ b/dom/media/gmp/GMPLoader.cpp @@ -80,10 +80,8 @@ public: virtual void Shutdown() override; -#ifdef SANDBOX_NOT_STATICALLY_LINKED_INTO_PLUGIN_CONTAINER - virtual void SetStartSandboxStarter(SandboxStarter* aStarter) override { - mSandboxStarter = aStarter; - } +#if defined(XP_MACOSX) + virtual void SetSandboxInfo(MacSandboxInfo* aSandboxInfo) override; #endif private: @@ -220,8 +218,8 @@ GMPLoaderImpl::Load(const char* aLibPath, // Start the sandbox now that we've generated the device bound node id. // This must happen after the node id is bound to the device id, as // generating the device id requires privileges. - if (mSandboxStarter) { - mSandboxStarter->Start(aLibPath); + if (mSandboxStarter && !mSandboxStarter->Start(aLibPath)) { + return false; } // Load the GMP. @@ -277,6 +275,15 @@ GMPLoaderImpl::Shutdown() } } +#if defined(XP_MACOSX) +void +GMPLoaderImpl::SetSandboxInfo(MacSandboxInfo* aSandboxInfo) +{ + if (mSandboxStarter) { + mSandboxStarter->SetSandboxInfo(aSandboxInfo); + } +} +#endif } // namespace gmp } // namespace mozilla diff --git a/dom/media/gmp/GMPLoader.h b/dom/media/gmp/GMPLoader.h index 934ffa4000fe..4a6afe286c26 100644 --- a/dom/media/gmp/GMPLoader.h +++ b/dom/media/gmp/GMPLoader.h @@ -10,18 +10,24 @@ #include #include "gmp-entrypoints.h" +#if defined(XP_MACOSX) +#include "mozilla/Sandbox.h" +#endif + namespace mozilla { namespace gmp { class SandboxStarter { public: virtual ~SandboxStarter() {} - virtual void Start(const char* aLibPath) = 0; -}; - + virtual bool Start(const char* aLibPath) = 0; #if defined(XP_MACOSX) -#define SANDBOX_NOT_STATICALLY_LINKED_INTO_PLUGIN_CONTAINER 1 + // On OS X we need to set Mac-specific sandbox info just before we start the + // sandbox, which we don't yet know when the GMPLoader and SandboxStarter + // objects are created. + virtual void SetSandboxInfo(MacSandboxInfo* aSandboxInfo) = 0; #endif +}; // Encapsulates generating the device-bound node id, activating the sandbox, // loading the GMP, and passing the node id to the GMP (in that order). @@ -59,10 +65,11 @@ public: // plugin library. virtual void Shutdown() = 0; -#ifdef SANDBOX_NOT_STATICALLY_LINKED_INTO_PLUGIN_CONTAINER - // Encapsulates starting the sandbox on Linux and MacOSX. - // TODO: Remove this, and put sandbox in plugin-container on all platforms. - virtual void SetStartSandboxStarter(SandboxStarter* aStarter) = 0; +#if defined(XP_MACOSX) + // On OS X we need to set Mac-specific sandbox info just before we start the + // sandbox, which we don't yet know when the GMPLoader and SandboxStarter + // objects are created. + virtual void SetSandboxInfo(MacSandboxInfo* aSandboxInfo) = 0; #endif }; diff --git a/dom/svg/SVGContentUtils.cpp b/dom/svg/SVGContentUtils.cpp index 8fd24867b8e3..7321c0557d61 100644 --- a/dom/svg/SVGContentUtils.cpp +++ b/dom/svg/SVGContentUtils.cpp @@ -144,9 +144,6 @@ GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions, // stroke to essentially be continuous or to be nonexistent in which case // we can avoid expensive stroking operations (the underlying platform // graphics libraries don't seem to optimize for this). - if (totalLengthOfDashes <= 0 && totalLengthOfGaps <= 0) { - return eNoStroke; - } if (totalLengthOfGaps <= 0) { return eContinuousStroke; } diff --git a/dom/system/gonk/NetworkUtils.cpp b/dom/system/gonk/NetworkUtils.cpp index 71aacbbfbdd4..4735ace510eb 100644 --- a/dom/system/gonk/NetworkUtils.cpp +++ b/dom/system/gonk/NetworkUtils.cpp @@ -992,14 +992,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, @@ -1147,13 +1172,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, @@ -1163,11 +1194,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); } }; @@ -1810,8 +1842,9 @@ CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions) } aOptions.mNetId = netIdInfo.mNetId; - + aOptions.mLoopIndex = 0; runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler); + return CommandResult::Pending(); } @@ -1893,6 +1926,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(); diff --git a/dom/system/gonk/NetworkUtils.h b/dom/system/gonk/NetworkUtils.h index 367cc1a05872..e1bef5cc021c 100644 --- a/dom/system/gonk/NetworkUtils.h +++ b/dom/system/gonk/NetworkUtils.h @@ -146,6 +146,8 @@ public: COPY_OPT_FIELD(mDns1_long, 0) COPY_OPT_FIELD(mDns2_long, 0) + mLoopIndex = 0; + #undef COPY_SEQUENCE_FIELD #undef COPY_OPT_STRING_FIELD #undef COPY_OPT_FIELD @@ -198,7 +200,8 @@ public: long mDns2_long; // 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. diff --git a/dom/tests/mochitest/fetch/fetch_test_framework.js b/dom/tests/mochitest/fetch/fetch_test_framework.js index 88c05761303d..8c6c6936eacf 100644 --- a/dom/tests/mochitest/fetch/fetch_test_framework.js +++ b/dom/tests/mochitest/fetch/fetch_test_framework.js @@ -47,40 +47,3 @@ function testScript(script) { }); } -// Utilities -// ========= - -// Helper that uses FileReader or FileReaderSync based on context and returns -// a Promise that resolves with the text or rejects with error. -function readAsText(blob) { - if (typeof FileReader !== "undefined") { - return new Promise(function(resolve, reject) { - var fs = new FileReader(); - fs.onload = function() { - resolve(fs.result); - } - fs.onerror = reject; - fs.readAsText(blob); - }); - } else { - var fs = new FileReaderSync(); - return Promise.resolve(fs.readAsText(blob)); - } -} - -function readAsArrayBuffer(blob) { - if (typeof FileReader !== "undefined") { - return new Promise(function(resolve, reject) { - var fs = new FileReader(); - fs.onload = function() { - resolve(fs.result); - } - fs.onerror = reject; - fs.readAsArrayBuffer(blob); - }); - } else { - var fs = new FileReaderSync(); - return Promise.resolve(fs.readAsArrayBuffer(blob)); - } -} - diff --git a/dom/tests/mochitest/fetch/mochitest.ini b/dom/tests/mochitest/fetch/mochitest.ini index cc380c2d68ab..b0a1fda21561 100644 --- a/dom/tests/mochitest/fetch/mochitest.ini +++ b/dom/tests/mochitest/fetch/mochitest.ini @@ -4,9 +4,11 @@ support-files = test_fetch_basic.js test_fetch_basic_http.js test_fetch_cors.js + test_formdataparsing.js test_headers_common.js test_request.js test_response.js + utils.js worker_wrapper.js [test_headers.html] @@ -14,5 +16,6 @@ support-files = [test_fetch_basic.html] [test_fetch_basic_http.html] [test_fetch_cors.html] +[test_formdataparsing.html] [test_request.html] [test_response.html] diff --git a/dom/tests/mochitest/fetch/test_fetch_basic.html b/dom/tests/mochitest/fetch/test_fetch_basic.html index 2655a0e833f1..ce7b63abae31 100644 --- a/dom/tests/mochitest/fetch/test_fetch_basic.html +++ b/dom/tests/mochitest/fetch/test_fetch_basic.html @@ -13,6 +13,7 @@


+
 
 
 
 
 
 
+  
+
+
+

+ +

+
+
+
+
+
+
diff --git a/dom/tests/mochitest/fetch/test_formdataparsing.js b/dom/tests/mochitest/fetch/test_formdataparsing.js
new file mode 100644
index 000000000000..bc7eb21a603a
--- /dev/null
+++ b/dom/tests/mochitest/fetch/test_formdataparsing.js
@@ -0,0 +1,283 @@
+var boundary = "1234567891011121314151617";
+
+// fn(body) should create a Body subclass with content body treated as
+// FormData and return it.
+function testFormDataParsing(fn) {
+
+  function makeTest(shouldPass, input, testFn) {
+    var obj = fn(input);
+    return obj.formData().then(function(fd) {
+      ok(shouldPass, "Expected test to be valid FormData for " + input);
+      if (testFn) {
+        return testFn(fd);
+      }
+    }, function(e) {
+      if (shouldPass) {
+        ok(false, "Expected test to pass for " + input);
+      } else {
+        ok(e.name == "TypeError", "Error should be a TypeError.");
+      }
+    });
+  }
+
+  // [shouldPass?, input, testFn]
+  var tests =
+    [
+      [ true,
+
+        boundary +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          is(fd.get("greeting"), '"hello"');
+        }
+      ],
+      [ false,
+
+        // Invalid disposition.
+        boundary +
+        '\r\nContent-Disposition: form-datafoobar; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ true,
+
+        '--' +
+        boundary +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          is(fd.get("greeting"), '"hello"');
+        }
+      ],
+      [ false,
+        boundary + "\r\n\r\n" + boundary + '-',
+      ],
+      [ false,
+        // No valid ending.
+        boundary + "\r\n\r\n" + boundary,
+      ],
+      [ false,
+
+        // One '-' prefix is not allowed. 2 or none.
+        '-' +
+        boundary +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        'invalid' +
+        boundary +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary + 'suffix' +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary + 'suffix' +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        // Partial boundary
+        boundary.substr(3) +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary +
+        // Missing '\n' at beginning.
+        '\rContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary +
+        // No form-data.
+        '\r\nContent-Disposition: mixed; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary +
+        // No headers.
+        '\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary +
+        // No content-disposition.
+        '\r\nContent-Dispositypo: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary +
+        // No name.
+        '\r\nContent-Disposition: form-data;\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary +
+        // Missing empty line between headers and body.
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        // Empty entry followed by valid entry.
+        boundary + "\r\n\r\n" + boundary +
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n"hello"\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+
+        boundary +
+        // Header followed by empty line, but empty body not followed by
+        // newline.
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
+        boundary + '-',
+      ],
+      [ true,
+
+        boundary +
+        // Empty body followed by newline.
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          is(fd.get("greeting"), "", "Empty value is allowed.");
+        }
+      ],
+      [ false,
+        boundary +
+        // Value is boundary itself.
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
+        boundary + '\r\n' +
+        boundary + '-',
+      ],
+      [ false,
+        boundary +
+        // Variant of above with no valid ending boundary.
+        '\r\nContent-Disposition: form-data; name="greeting"\r\n\r\n' +
+        boundary
+      ],
+      [ true,
+        boundary +
+        // Unquoted filename with empty body.
+        '\r\nContent-Disposition: form-data; name="file"; filename=file1.txt\r\n\r\n\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          var f = fd.get("file");
+          ok(f instanceof File, "Entry with filename attribute should be read as File.");
+          is(f.name, "file1.txt", "Filename should match.");
+          is(f.type, "text/plain", "Default content-type should be text/plain.");
+          return readAsText(f).then(function(text) {
+            is(text, "", "File should be empty.");
+          });
+        }
+      ],
+      [ true,
+        boundary +
+        // Quoted filename with empty body.
+        '\r\nContent-Disposition: form-data; name="file"; filename="file1.txt"\r\n\r\n\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          var f = fd.get("file");
+          ok(f instanceof File, "Entry with filename attribute should be read as File.");
+          is(f.name, "file1.txt", "Filename should match.");
+          is(f.type, "text/plain", "Default content-type should be text/plain.");
+          return readAsText(f).then(function(text) {
+            is(text, "", "File should be empty.");
+          });
+        }
+      ],
+      [ false,
+        boundary +
+        // Invalid filename
+        '\r\nContent-Disposition: form-data; name="file"; filename="[\n@;xt"\r\n\r\n\r\n' +
+        boundary + '-',
+      ],
+      [ true,
+        boundary +
+        '\r\nContent-Disposition: form-data; name="file"; filename="[@;xt"\r\n\r\n\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          var f = fd.get("file");
+          ok(f instanceof File, "Entry with filename attribute should be read as File.");
+          is(f.name, "[@", "Filename should match.");
+        }
+      ],
+      [ true,
+        boundary +
+        '\r\nContent-Disposition: form-data; name="file"; filename="file with   spaces"\r\n\r\n\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          var f = fd.get("file");
+          ok(f instanceof File, "Entry with filename attribute should be read as File.");
+          is(f.name, "file with spaces", "Filename should match.");
+        }
+      ],
+      [ true,
+        boundary + '\r\n' +
+        'Content-Disposition: form-data; name="file"; filename="xml.txt"\r\n' +
+        'content-type       : application/xml\r\n' +
+        '\r\n' +
+        'foobar\r\n\r\n\r\n' +
+        boundary + '-',
+
+        function(fd) {
+          var f = fd.get("file");
+          ok(f instanceof File, "Entry with filename attribute should be read as File.");
+          is(f.name, "xml.txt", "Filename should match.");
+          is(f.type, "application/xml", "content-type should be application/xml.");
+          return readAsText(f).then(function(text) {
+            is(text, "foobar\r\n\r\n", "File should have correct text.");
+          });
+        }
+      ],
+    ];
+
+  var promises = [];
+  for (var i = 0; i < tests.length; ++i) {
+    var test = tests[i];
+    promises.push(makeTest(test[0], test[1], test[2]));
+  }
+
+  return Promise.all(promises);
+}
+
+function makeRequest(body) {
+  var req = new Request("", { method: 'post', body: body,
+                              headers: {
+                                'Content-Type': 'multipart/form-data; boundary=' + boundary
+                              }});
+  return req;
+}
+
+function makeResponse(body) {
+  var res = new Response(body, { headers: {
+                                   'Content-Type': 'multipart/form-data; boundary=' + boundary
+                                 }});
+  return res;
+}
+
+function runTest() {
+  return Promise.all([testFormDataParsing(makeRequest),
+                      testFormDataParsing(makeResponse)]);
+}
diff --git a/dom/tests/mochitest/fetch/test_headers.html b/dom/tests/mochitest/fetch/test_headers.html
index d8b4713f7713..f13f53425d58 100644
--- a/dom/tests/mochitest/fetch/test_headers.html
+++ b/dom/tests/mochitest/fetch/test_headers.html
@@ -8,6 +8,7 @@
   
 
 
+