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