diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index b722fe5d6a22..2c6d21987c03 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 bd8cc343b0f5..6b2f0cc64ae9 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 ca9c9df7c229..d78008501632 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -12,10 +12,10 @@ - + - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index b722fe5d6a22..2c6d21987c03 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 72977aa8145c..2c5b3437fa5a 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 27803c5ab07f..c9de47d7f9b6 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "65cb0c455f058c49aead7de9a245d90aa890276c", + "revision": "82aa7db46a22bc99998468aa433eafb33d739ed6", "repo_path": "/integration/gaia-central" } diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index afc6997c6602..8ff1f01f73c9 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 9c892d022906..97609c31e223 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index a67df032230d..2ba9d465959d 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index c42b1867f7c4..c63af70f59a6 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index d0ace118a4c9..75fcada64b54 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index 4f4d83a74eac..47e556f49aff 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/dom/base/MessagePort.cpp b/dom/base/MessagePort.cpp index 1e053120996b..643561c99df6 100644 --- a/dom/base/MessagePort.cpp +++ b/dom/base/MessagePort.cpp @@ -4,18 +4,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MessagePort.h" +#include "MessageEvent.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/MessageChannel.h" #include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/MessagePortList.h" #include "mozilla/dom/StructuredCloneTags.h" -#include "nsGlobalWindow.h" #include "nsContentUtils.h" +#include "nsGlobalWindow.h" #include "nsPresContext.h" #include "nsIDocument.h" #include "nsIDOMFile.h" #include "nsIDOMFileList.h" -#include "nsIDOMMessageEvent.h" #include "nsIPresShell.h" namespace mozilla { @@ -91,6 +92,7 @@ struct StructuredCloneInfo { PostMessageRunnable* mEvent; MessagePort* mPort; + nsRefPtrHashtable, MessagePortBase> mPorts; }; static JSObject* @@ -100,9 +102,6 @@ PostMessageReadStructuredClone(JSContext* cx, uint32_t data, void* closure) { - StructuredCloneInfo* scInfo = static_cast(closure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - if (tag == SCTAG_DOM_BLOB || tag == SCTAG_DOM_FILELIST) { NS_ASSERTION(!data, "Data should be empty"); @@ -119,22 +118,6 @@ PostMessageReadStructuredClone(JSContext* cx, } } - if (tag == SCTAG_DOM_MESSAGEPORT) { - NS_ASSERTION(!data, "Data should be empty"); - - MessagePort* port; - if (JS_ReadBytes(reader, &port, sizeof(port))) { - JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); - if (global) { - JS::Rooted obj(cx, port->WrapObject(cx, global)); - if (JS_WrapObject(cx, &obj)) { - port->BindToOwner(scInfo->mPort->GetOwner()); - return obj; - } - } - } - } - const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(cx); @@ -178,20 +161,6 @@ PostMessageWriteStructuredClone(JSContext* cx, } } - MessagePortBase* port = nullptr; - nsresult rv = UNWRAP_OBJECT(MessagePort, obj, port); - if (NS_SUCCEEDED(rv)) { - nsRefPtr newPort = port->Clone(); - - if (!newPort) { - return false; - } - - return JS_WriteUint32Pair(writer, SCTAG_DOM_MESSAGEPORT, 0) && - JS_WriteBytes(writer, &newPort, sizeof(newPort)) && - scInfo->mEvent->StoreISupports(newPort); - } - const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(cx); @@ -202,14 +171,108 @@ PostMessageWriteStructuredClone(JSContext* cx, return false; } +static bool +PostMessageReadTransferStructuredClone(JSContext* aCx, + JSStructuredCloneReader* reader, + uint32_t tag, void* data, + uint64_t unused, + void* aClosure, + JS::MutableHandle returnObject) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (tag == SCTAG_DOM_MAP_MESSAGEPORT) { + MessagePort* port = static_cast(data); + port->BindToOwner(scInfo->mPort->GetOwner()); + scInfo->mPorts.Put(port, nullptr); + + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + if (global) { + JS::Rooted obj(aCx, port->WrapObject(aCx, global)); + if (JS_WrapObject(aCx, &obj)) { + MOZ_ASSERT(port->GetOwner() == scInfo->mPort->GetOwner()); + returnObject.set(obj); + } + } + return true; + } + + return false; +} + +static bool +PostMessageTransferStructuredClone(JSContext* aCx, + JS::Handle aObj, + void* aClosure, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t *aExtraData) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + MessagePortBase *port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); + if (NS_SUCCEEDED(rv)) { + nsRefPtr newPort; + if (scInfo->mPorts.Get(port, getter_AddRefs(newPort))) { + // No duplicate. + return false; + } + + newPort = port->Clone(); + scInfo->mPorts.Put(port, newPort); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = newPort; + *aExtraData = 0; + + return true; + } + + return false; +} + +static void +PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership, + void* aData, + uint64_t aExtraData, + void* aClosure) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + MOZ_ASSERT(aOwnership == JS::SCTAG_TMO_CUSTOM); + nsRefPtr port(static_cast(aData)); + scInfo->mPorts.Remove(port); + } +} + JSStructuredCloneCallbacks kPostMessageCallbacks = { PostMessageReadStructuredClone, PostMessageWriteStructuredClone, - nullptr + nullptr, + PostMessageReadTransferStructuredClone, + PostMessageTransferStructuredClone, + PostMessageFreeTransferStructuredClone }; } // anonymous namespace +static PLDHashOperator +PopulateMessagePortList(MessagePortBase* aKey, MessagePortBase* aValue, void* aClosure) +{ + nsTArray > *array = + static_cast > *>(aClosure); + + array->AppendElement(aKey); + return PL_DHASH_NEXT; +} + NS_IMETHODIMP PostMessageRunnable::Run() { @@ -226,45 +289,32 @@ PostMessageRunnable::Run() // Deserialize the structured clone data JS::Rooted messageData(cx); - { - StructuredCloneInfo scInfo; - scInfo.mEvent = this; - scInfo.mPort = mPort; + StructuredCloneInfo scInfo; + scInfo.mEvent = this; + scInfo.mPort = mPort; - if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { - return NS_ERROR_DOM_DATA_CLONE_ERR; - } + if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; } // Create the event - nsIDocument* doc = mPort->GetOwner()->GetExtantDoc(); - if (!doc) { - return NS_OK; - } + nsCOMPtr eventTarget = + do_QueryInterface(mPort->GetOwner()); + nsRefPtr event = + new MessageEvent(eventTarget, nullptr, nullptr); - ErrorResult error; - nsRefPtr event = - doc->CreateEvent(NS_LITERAL_STRING("MessageEvent"), error); - if (error.Failed()) { - return NS_OK; - } + event->InitMessageEvent(NS_LITERAL_STRING("message"), false /* non-bubbling */, + false /* cancelable */, messageData, EmptyString(), + EmptyString(), nullptr); + event->SetTrusted(true); + event->SetSource(mPort); - nsCOMPtr message = do_QueryInterface(event); - nsresult rv = message->InitMessageEvent(NS_LITERAL_STRING("message"), - false /* non-bubbling */, - true /* cancelable */, - messageData, - EmptyString(), - EmptyString(), - mPort->GetOwner()); - if (NS_FAILED(rv)) { - return NS_OK; - } - - message->SetTrusted(true); + nsTArray > ports; + scInfo.mPorts.EnumerateRead(PopulateMessagePortList, &ports); + event->SetPorts(new MessagePortList(static_cast(event.get()), ports)); bool status; - mPort->DispatchEvent(event, &status); + mPort->DispatchEvent(static_cast(event.get()), &status); return status ? NS_OK : NS_ERROR_FAILURE; } diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index 8c9523c052ac..4278be6e9c91 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -28,7 +28,7 @@ enum StructuredCloneTags { // These tags are used for both main thread and workers. SCTAG_DOM_IMAGEDATA, - SCTAG_DOM_MESSAGEPORT, + SCTAG_DOM_MAP_MESSAGEPORT, SCTAG_DOM_FUNCTION, diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 6e8cb13a2d96..1ac6ee0b8bf4 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -56,6 +56,7 @@ #include "nsLayoutStatics.h" #include "nsCCUncollectableMarker.h" #include "mozilla/dom/workers/Workers.h" +#include "mozilla/dom/MessagePortList.h" #include "nsJSPrincipals.h" #include "mozilla/Attributes.h" #include "mozilla/Debug.h" @@ -63,6 +64,7 @@ #include "mozilla/EventStates.h" #include "mozilla/MouseEvents.h" #include "AudioChannelService.h" +#include "MessageEvent.h" // Interfaces Needed #include "nsIFrame.h" @@ -82,7 +84,6 @@ #include "nsIDOMDocument.h" #include "nsIDOMElement.h" #include "nsIDOMEvent.h" -#include "nsIDOMMessageEvent.h" #include "nsIDOMPopupBlockedEvent.h" #include "nsIDOMPopStateEvent.h" #include "nsIDOMHashChangeEvent.h" @@ -7645,6 +7646,7 @@ struct StructuredCloneInfo { PostMessageEvent* event; bool subsumes; nsPIDOMWindow* window; + nsRefPtrHashtable, MessagePortBase> ports; }; static JSObject* @@ -7654,9 +7656,6 @@ PostMessageReadStructuredClone(JSContext* cx, uint32_t data, void* closure) { - StructuredCloneInfo* scInfo = static_cast(closure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - if (tag == SCTAG_DOM_BLOB || tag == SCTAG_DOM_FILELIST) { NS_ASSERTION(!data, "Data should be empty"); @@ -7673,22 +7672,6 @@ PostMessageReadStructuredClone(JSContext* cx, } } - if (MessageChannel::PrefEnabled() && tag == SCTAG_DOM_MESSAGEPORT) { - NS_ASSERTION(!data, "Data should be empty"); - - MessagePortBase* port; - if (JS_ReadBytes(reader, &port, sizeof(port))) { - JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); - if (global) { - JS::Rooted obj(cx, port->WrapObject(cx, global)); - if (JS_WrapObject(cx, &obj)) { - port->BindToOwner(scInfo->window); - return obj; - } - } - } - } - const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(cx); @@ -7729,22 +7712,6 @@ PostMessageWriteStructuredClone(JSContext* cx, scInfo->event->StoreISupports(supports); } - if (MessageChannel::PrefEnabled()) { - MessagePortBase* port = nullptr; - nsresult rv = UNWRAP_OBJECT(MessagePort, obj, port); - if (NS_SUCCEEDED(rv) && scInfo->subsumes) { - nsRefPtr newPort = port->Clone(); - - if (!newPort) { - return false; - } - - return JS_WriteUint32Pair(writer, SCTAG_DOM_MESSAGEPORT, 0) && - JS_WriteBytes(writer, &newPort, sizeof(newPort)) && - scInfo->event->StoreISupports(newPort); - } - } - const JSStructuredCloneCallbacks* runtimeCallbacks = js::GetContextStructuredCloneCallbacks(cx); @@ -7755,14 +7722,108 @@ PostMessageWriteStructuredClone(JSContext* cx, return false; } +static bool +PostMessageReadTransferStructuredClone(JSContext* aCx, + JSStructuredCloneReader* reader, + uint32_t tag, void* aData, + uint64_t aExtraData, + void* aClosure, + JS::MutableHandle returnObject) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (MessageChannel::PrefEnabled() && tag == SCTAG_DOM_MAP_MESSAGEPORT) { + MessagePort* port = static_cast(aData); + port->BindToOwner(scInfo->window); + scInfo->ports.Put(port, nullptr); + + JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); + if (global) { + JS::Rooted obj(aCx, port->WrapObject(aCx, global)); + if (JS_WrapObject(aCx, &obj)) { + MOZ_ASSERT(port->GetOwner() == scInfo->window); + returnObject.set(obj); + } + } + + return true; + } + + return false; +} + +static bool +PostMessageTransferStructuredClone(JSContext* aCx, + JS::Handle aObj, + void* aClosure, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (MessageChannel::PrefEnabled()) { + MessagePortBase* port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); + if (NS_SUCCEEDED(rv)) { + nsRefPtr newPort; + if (scInfo->ports.Get(port, getter_AddRefs(newPort))) { + // No duplicate. + return false; + } + + newPort = port->Clone(); + scInfo->ports.Put(port, newPort); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = newPort; + *aExtraData = 0; + + return true; + } + } + + return false; +} + +void +PostMessageFreeTransferStructuredClone(uint32_t aTag, JS::TransferableOwnership aOwnership, + void *aContent, uint64_t aExtraData, void* aClosure) +{ + StructuredCloneInfo* scInfo = static_cast(aClosure); + NS_ASSERTION(scInfo, "Must have scInfo!"); + + if (MessageChannel::PrefEnabled() && aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + nsRefPtr port(static_cast(aContent)); + scInfo->ports.Remove(port); + } +} + JSStructuredCloneCallbacks kPostMessageCallbacks = { PostMessageReadStructuredClone, PostMessageWriteStructuredClone, - nullptr + nullptr, + PostMessageReadTransferStructuredClone, + PostMessageTransferStructuredClone, + PostMessageFreeTransferStructuredClone }; } // anonymous namespace +static PLDHashOperator +PopulateMessagePortList(MessagePortBase* aKey, MessagePortBase* aValue, void* aClosure) +{ + nsTArray > *array = + static_cast > *>(aClosure); + + array->AppendElement(aKey); + return PL_DHASH_NEXT; +} + NS_IMETHODIMP PostMessageEvent::Run() { @@ -7817,38 +7878,27 @@ PostMessageEvent::Run() // Deserialize the structured clone data JS::Rooted messageData(cx); - { - StructuredCloneInfo scInfo; - scInfo.event = this; - scInfo.window = targetWindow; + StructuredCloneInfo scInfo; + scInfo.event = this; + scInfo.window = targetWindow; - if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { - return NS_ERROR_DOM_DATA_CLONE_ERR; - } + if (!mBuffer.read(cx, &messageData, &kPostMessageCallbacks, &scInfo)) { + return NS_ERROR_DOM_DATA_CLONE_ERR; } // Create the event - nsIDocument* doc = targetWindow->mDoc; - if (!doc) - return NS_OK; + nsCOMPtr eventTarget = + do_QueryInterface(static_cast(targetWindow.get())); + nsRefPtr event = + new MessageEvent(eventTarget, nullptr, nullptr); - nsCOMPtr domDoc = do_QueryInterface(doc); - nsCOMPtr event; - domDoc->CreateEvent(NS_LITERAL_STRING("MessageEvent"), getter_AddRefs(event)); - if (!event) - return NS_OK; - - nsCOMPtr message = do_QueryInterface(event); - nsresult rv = message->InitMessageEvent(NS_LITERAL_STRING("message"), - false /* non-bubbling */, - true /* cancelable */, - messageData, - mCallerOrigin, - EmptyString(), - mSource); - if (NS_FAILED(rv)) - return NS_OK; + event->InitMessageEvent(NS_LITERAL_STRING("message"), false /*non-bubbling */, + false /*cancelable */, messageData, mCallerOrigin, + EmptyString(), mSource); + nsTArray > ports; + scInfo.ports.EnumerateRead(PopulateMessagePortList, &ports); + event->SetPorts(new MessagePortList(static_cast(event.get()), ports)); // We can't simply call dispatchEvent on the window because doing so ends // up flipping the trusted bit on the event, and we don't want that to @@ -7860,14 +7910,14 @@ PostMessageEvent::Run() if (shell) presContext = shell->GetPresContext(); - message->SetTrusted(mTrustedCaller); - WidgetEvent* internalEvent = message->GetInternalNSEvent(); + event->SetTrusted(mTrustedCaller); + WidgetEvent* internalEvent = event->GetInternalNSEvent(); nsEventStatus status = nsEventStatus_eIgnore; EventDispatcher::Dispatch(static_cast(mTargetWindow), presContext, internalEvent, - message, + static_cast(event.get()), &status); return NS_OK; } diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 1a3d4eca4beb..d7394c5368d9 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2881,7 +2881,10 @@ nsJSContext::EnsureStatics() static JSStructuredCloneCallbacks cloneCallbacks = { NS_DOMReadStructuredClone, NS_DOMWriteStructuredClone, - NS_DOMStructuredCloneError + NS_DOMStructuredCloneError, + nullptr, + nullptr, + nullptr }; JS_SetStructuredCloneCallbacks(sRuntime, &cloneCallbacks); diff --git a/dom/base/nsStructuredCloneContainer.cpp b/dom/base/nsStructuredCloneContainer.cpp index 8b65109701a9..0aa29f88e766 100644 --- a/dom/base/nsStructuredCloneContainer.cpp +++ b/dom/base/nsStructuredCloneContainer.cpp @@ -64,7 +64,7 @@ nsStructuredCloneContainer::InitFromJSVal(JS::Handle aData, mSize = 0; mVersion = 0; - JS_ClearStructuredClone(jsBytes, mSize); + JS_ClearStructuredClone(jsBytes, mSize, nullptr, nullptr); return NS_ERROR_FAILURE; } else { @@ -73,7 +73,7 @@ nsStructuredCloneContainer::InitFromJSVal(JS::Handle aData, memcpy(mData, jsBytes, mSize); - JS_ClearStructuredClone(jsBytes, mSize); + JS_ClearStructuredClone(jsBytes, mSize, nullptr, nullptr); return NS_OK; } diff --git a/dom/base/test/iframe_messageChannel_cloning.html b/dom/base/test/iframe_messageChannel_cloning.html index ee53e855a730..de03cead9606 100644 --- a/dom/base/test/iframe_messageChannel_cloning.html +++ b/dom/base/test/iframe_messageChannel_cloning.html @@ -13,7 +13,7 @@ ok (evt.data.port instanceof MessagePort, "Data contains a MessagePort"); var a = new MessageChannel(); - window.parent.postMessage({ status: "FINISH", port: a.port2 }, '*'); + window.parent.postMessage({ status: "FINISH", port: a.port2 }, '*', [a.port2]); } diff --git a/dom/base/test/iframe_messageChannel_pingpong.html b/dom/base/test/iframe_messageChannel_pingpong.html index 2cd0b79da589..d83d75c48e3e 100644 --- a/dom/base/test/iframe_messageChannel_pingpong.html +++ b/dom/base/test/iframe_messageChannel_pingpong.html @@ -16,7 +16,7 @@ if (counter++ == 0) { ok(!(evt.data % 2), "The number " + evt.data + " has been received correctly by the iframe"); - window.parent.postMessage({ type: 'PORT', port: port }, '*'); + window.parent.postMessage({ type: 'PORT', port: port }, '*', [port]); } else { ok(false, "Wrong message!"); diff --git a/dom/base/test/test_bug913761.html b/dom/base/test/test_bug913761.html index d8ec0cc9eab6..c4451e8c561d 100644 --- a/dom/base/test/test_bug913761.html +++ b/dom/base/test/test_bug913761.html @@ -33,7 +33,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=913761 } } - transportChannel.port2.postMessage({ port: serviceChannel.port2} /* TODO: [serviceChannel.port2] */); + transportChannel.port2.postMessage({ port: serviceChannel.port2}, [serviceChannel.port2]); } SimpleTest.waitForExplicitFinish(); diff --git a/dom/base/test/test_messageChannel_cloning.html b/dom/base/test/test_messageChannel_cloning.html index 8f8958303144..802e248f1469 100644 --- a/dom/base/test/test_messageChannel_cloning.html +++ b/dom/base/test/test_messageChannel_cloning.html @@ -45,7 +45,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 div.appendChild(ifr); function iframeLoaded() { - ifr.contentWindow.postMessage({ port: a.port2 }, '*'); + ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]); } } diff --git a/dom/base/test/test_messageChannel_pingpong.html b/dom/base/test/test_messageChannel_pingpong.html index 372e87b07e52..ddf4f26f8200 100644 --- a/dom/base/test/test_messageChannel_pingpong.html +++ b/dom/base/test/test_messageChannel_pingpong.html @@ -40,7 +40,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 ok(evt.data % 2, "The number " + evt.data + " has been received correctly by the main window"); if (evt.data < MAX - 1) { - ifr.contentWindow.postMessage({ type: 'PORT', port: port }, '*'); + ifr.contentWindow.postMessage({ type: 'PORT', port: port }, '*', [port]); } else { SimpleTest.finish(); } @@ -66,7 +66,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 div.appendChild(ifr); function iframeLoaded() { - ifr.contentWindow.postMessage({ type: 'PORT', port: a.port2 }, '*'); + ifr.contentWindow.postMessage({ type: 'PORT', port: a.port2 }, '*', [a.port2]); } } diff --git a/dom/base/test/test_messageChannel_post.html b/dom/base/test/test_messageChannel_post.html index b9517c22ac8d..423732f57fb7 100644 --- a/dom/base/test/test_messageChannel_post.html +++ b/dom/base/test/test_messageChannel_post.html @@ -38,7 +38,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 div.appendChild(ifr); function iframeLoaded() { - ifr.contentWindow.postMessage({ port: a.port2 }, '*'); + ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]); } var tests = [ 42, diff --git a/dom/base/test/test_messageChannel_start.html b/dom/base/test/test_messageChannel_start.html index b739a732a20b..7abd750c211b 100644 --- a/dom/base/test/test_messageChannel_start.html +++ b/dom/base/test/test_messageChannel_start.html @@ -213,7 +213,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 } } - postMessage(a.port2, '*'); + postMessage(a.port2, '*', [a.port2]); } var tests = [ diff --git a/dom/base/test/test_messageChannel_transferable.html b/dom/base/test/test_messageChannel_transferable.html index 2750d5324382..c1e661ead89e 100644 --- a/dom/base/test/test_messageChannel_transferable.html +++ b/dom/base/test/test_messageChannel_transferable.html @@ -38,7 +38,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=677638 div.appendChild(ifr); function iframeLoaded() { - ifr.contentWindow.postMessage({ port: a.port2 }, '*'); + ifr.contentWindow.postMessage({ port: a.port2 }, '*', [a.port2]); } a.port1.addEventListener('message', receivePortMessage, false); diff --git a/dom/base/test/test_messagePort.html b/dom/base/test/test_messagePort.html new file mode 100644 index 000000000000..5b4a996eef4a --- /dev/null +++ b/dom/base/test/test_messagePort.html @@ -0,0 +1,115 @@ + + + + + + Test for Bug 912456 - port cloning + + + + +Mozilla Bug 912456 + + + + diff --git a/dom/events/MessageEvent.h b/dom/events/MessageEvent.h index a83814e4718e..f823888002fe 100644 --- a/dom/events/MessageEvent.h +++ b/dom/events/MessageEvent.h @@ -9,6 +9,7 @@ #include "mozilla/dom/Event.h" #include "nsCycleCollectionParticipant.h" #include "nsIDOMMessageEvent.h" +#include "mozilla/dom/MessagePortList.h" namespace mozilla { namespace dom { @@ -57,6 +58,17 @@ public: void SetPorts(MessagePortList* aPorts); + // Non WebIDL methods + void SetSource(mozilla::dom::MessagePort* aPort) + { + mPortSource = aPort; + } + + void SetSource(nsPIDOMWindow* aWindow) + { + mWindowSource = aWindow; + } + static already_AddRefed Constructor(const GlobalObject& aGlobal, JSContext* aCx, const nsAString& aType, diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 5f119d362949..07ce7a632196 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -1343,6 +1343,9 @@ IDBObjectStore::DeserializeValue(JSContext* aCx, JSStructuredCloneCallbacks callbacks = { IDBObjectStore::StructuredCloneReadCallback, nullptr, + nullptr, + nullptr, + nullptr, nullptr }; @@ -1364,6 +1367,9 @@ IDBObjectStore::SerializeValue(JSContext* aCx, JSStructuredCloneCallbacks callbacks = { nullptr, StructuredCloneWriteCallback, + nullptr, + nullptr, + nullptr, nullptr }; @@ -4529,6 +4535,9 @@ CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection) JSStructuredCloneCallbacks callbacks = { IDBObjectStore::StructuredCloneReadCallback, nullptr, + nullptr, + nullptr, + nullptr, nullptr }; diff --git a/dom/ipc/StructuredCloneUtils.cpp b/dom/ipc/StructuredCloneUtils.cpp index 750f36fed777..853608ae9170 100644 --- a/dom/ipc/StructuredCloneUtils.cpp +++ b/dom/ipc/StructuredCloneUtils.cpp @@ -152,7 +152,10 @@ Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, JSStructuredCloneCallbacks gCallbacks = { Read, Write, - Error + Error, + nullptr, + nullptr, + nullptr }; } // anonymous namespace diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index d8fcefaeb0b2..38eb5d34069a 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -2512,20 +2512,23 @@ RadioInterface.prototype = { }).bind(this)); }, - setCellBroadcastSearchList: function(newSearchListStr) { - if (newSearchListStr == this._cellBroadcastSearchListStr) { + setCellBroadcastSearchList: function(newSearchList) { + if ((newSearchList == this._cellBroadcastSearchList) || + (newSearchList && this._cellBroadcastSearchList && + newSearchList.gsm == this._cellBroadcastSearchList.gsm && + newSearchList.cdma == this._cellBroadcastSearchList.cdma)) { return; } this.workerMessenger.send("setCellBroadcastSearchList", - { searchListStr: newSearchListStr }, + { searchList: newSearchList }, (function callback(response) { if (!response.success) { let lock = gSettingsService.createLock(); lock.set(kSettingsCellBroadcastSearchList, - this._cellBroadcastSearchListStr, null); + this._cellBroadcastSearchList, null); } else { - this._cellBroadcastSearchListStr = response.searchListStr; + this._cellBroadcastSearchList = response.searchList; } return false; @@ -3384,7 +3387,7 @@ RadioInterface.prototype = { _sntp: null, // Cell Broadcast settings values. - _cellBroadcastSearchListStr: null, + _cellBroadcastSearchList: null, // Operator's mcc-mnc. _lastKnownNetwork: null, @@ -3471,9 +3474,12 @@ RadioInterface.prototype = { break; case kSettingsCellBroadcastSearchList: if (DEBUG) { - this.debug("'" + kSettingsCellBroadcastSearchList + "' is now " + aResult); + this.debug("'" + kSettingsCellBroadcastSearchList + + "' is now " + JSON.stringify(aResult)); } - this.setCellBroadcastSearchList(aResult); + // TODO: Set searchlist for Multi-SIM. See Bug 921326. + let result = Array.isArray(aResult) ? aResult[0] : aResult; + this.setCellBroadcastSearchList(result); break; } }, diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 8d0ff0afb9e4..bb29bc4b28b8 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -1832,8 +1832,19 @@ RilObject.prototype = { }, setCellBroadcastSearchList: function(options) { + let getSearchListStr = function(aSearchList) { + if (typeof aSearchList === "string" || aSearchList instanceof String) { + return aSearchList; + } + + // TODO: Set search list for CDMA/GSM individually. Bug 990926 + let prop = this._isCdma ? "cdma" : "gsm"; + + return aSearchList && aSearchList[prop]; + }.bind(this); + try { - let str = options.searchListStr; + let str = getSearchListStr(options.searchList); this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str); options.success = true; } catch (e) { diff --git a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js index d782860c8d19..3f20e8708c74 100644 --- a/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js +++ b/dom/system/gonk/tests/test_ril_worker_cellbroadcast_config.js @@ -137,3 +137,37 @@ add_test(function test_ril_worker_cellbroadcast_merge_config() { run_next_test(); }); +add_test(function test_ril_worker_cellbroadcast_set_search_list() { + let worker = newWorker({ + postRILMessage: function(id, parcel) { + // Do nothing + }, + postMessage: function(message) { + // Do nothing + } + }); + + let context = worker.ContextPool._contexts[0]; + + function test(aIsCdma, aSearchList, aExpected) { + context.RIL._isCdma = aIsCdma; + + let options = { searchList: aSearchList }; + context.RIL.setCellBroadcastSearchList(options); + // Enforce the MMI result to string for comparison. + do_check_eq("" + context.RIL.cellBroadcastConfigs.MMI, aExpected); + do_check_eq(options.success, true); + } + + let searchListStr = "1,2,3,4"; + let searchList = { gsm: "1,2,3,4", cdma: "5,6,7,8" }; + + test(false, searchListStr, "1,2,2,3,3,4,4,5"); + test(true, searchListStr, "1,2,2,3,3,4,4,5"); + test(false, searchList, "1,2,2,3,3,4,4,5"); + test(true, searchList, "5,6,6,7,7,8,8,9"); + test(false, null, "null"); + test(true, null, "null"); + + run_next_test(); +}); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index 165b2142cc5e..cb2cd82d96e2 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -447,7 +447,10 @@ struct WorkerStructuredCloneCallbacks JSStructuredCloneCallbacks gWorkerStructuredCloneCallbacks = { WorkerStructuredCloneCallbacks::Read, WorkerStructuredCloneCallbacks::Write, - WorkerStructuredCloneCallbacks::Error + WorkerStructuredCloneCallbacks::Error, + nullptr, + nullptr, + nullptr }; struct MainThreadWorkerStructuredCloneCallbacks @@ -606,7 +609,10 @@ struct MainThreadWorkerStructuredCloneCallbacks JSStructuredCloneCallbacks gMainThreadWorkerStructuredCloneCallbacks = { MainThreadWorkerStructuredCloneCallbacks::Read, MainThreadWorkerStructuredCloneCallbacks::Write, - MainThreadWorkerStructuredCloneCallbacks::Error + MainThreadWorkerStructuredCloneCallbacks::Error, + nullptr, + nullptr, + nullptr }; struct ChromeWorkerStructuredCloneCallbacks @@ -636,7 +642,10 @@ struct ChromeWorkerStructuredCloneCallbacks JSStructuredCloneCallbacks gChromeWorkerStructuredCloneCallbacks = { ChromeWorkerStructuredCloneCallbacks::Read, ChromeWorkerStructuredCloneCallbacks::Write, - ChromeWorkerStructuredCloneCallbacks::Error + ChromeWorkerStructuredCloneCallbacks::Error, + nullptr, + nullptr, + nullptr }; struct MainThreadChromeWorkerStructuredCloneCallbacks @@ -694,7 +703,10 @@ struct MainThreadChromeWorkerStructuredCloneCallbacks JSStructuredCloneCallbacks gMainThreadChromeWorkerStructuredCloneCallbacks = { MainThreadChromeWorkerStructuredCloneCallbacks::Read, MainThreadChromeWorkerStructuredCloneCallbacks::Write, - MainThreadChromeWorkerStructuredCloneCallbacks::Error + MainThreadChromeWorkerStructuredCloneCallbacks::Error, + nullptr, + nullptr, + nullptr }; class MainThreadReleaseRunnable MOZ_FINAL : public nsRunnable diff --git a/ipc/unixsocket/UnixSocket.cpp b/ipc/unixsocket/UnixSocket.cpp index 6bcc2cc5fe29..be23891ddd0d 100644 --- a/ipc/unixsocket/UnixSocket.cpp +++ b/ipc/unixsocket/UnixSocket.cpp @@ -64,8 +64,7 @@ public: MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!mShuttingDownOnIOThread); - RemoveWatchers(READ_WATCHER|WRITE_WATCHER); - + Close(); // will also remove fd from I/O loop mShuttingDownOnIOThread = true; } @@ -643,7 +642,7 @@ void UnixSocketImpl::OnSocketCanReceiveWithoutBlocking() { MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop()); - MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); + MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); // see bug 990984 // Read all of the incoming data. while (true) { @@ -691,7 +690,7 @@ void UnixSocketImpl::OnSocketCanSendWithoutBlocking() { MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop()); - MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); + MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); // see bug 990984 // Try to write the bytes of mCurrentRilRawData. If all were written, continue. // @@ -739,6 +738,8 @@ UnixSocketConsumer::UnixSocketConsumer() : mImpl(nullptr) UnixSocketConsumer::~UnixSocketConsumer() { + MOZ_ASSERT(mConnectionStatus == SOCKET_DISCONNECTED); + MOZ_ASSERT(!mImpl); } bool diff --git a/ipc/unixsocket/UnixSocket.h b/ipc/unixsocket/UnixSocket.h index 3d08c02b85c9..46b3553803f2 100644 --- a/ipc/unixsocket/UnixSocket.h +++ b/ipc/unixsocket/UnixSocket.h @@ -135,7 +135,7 @@ enum SocketConnectionStatus { SOCKET_CONNECTED = 3 }; -class UnixSocketConsumer : public RefCounted +class UnixSocketConsumer : public AtomicRefCounted { public: MOZ_DECLARE_REFCOUNTED_TYPENAME(UnixSocketConsumer) diff --git a/js/public/StructuredClone.h b/js/public/StructuredClone.h index f83fd4e83b51..c431040aa7dc 100644 --- a/js/public/StructuredClone.h +++ b/js/public/StructuredClone.h @@ -23,6 +23,32 @@ struct JSStructuredCloneWriter; // API for the HTML5 internal structured cloning algorithm. +namespace JS { +enum TransferableOwnership { + // Transferable data has not been filled in yet + SCTAG_TMO_UNFILLED = 0, + + // Structured clone buffer does not yet own the data + SCTAG_TMO_UNOWNED = 1, + + // All values at least this large are owned by the clone buffer + SCTAG_TMO_FIRST_OWNED = 2, + + // Data is a pointer that can be freed + SCTAG_TMO_ALLOC_DATA = 2, + + // Data is a SharedArrayBufferObject's buffer + SCTAG_TMO_SHARED_BUFFER = 3, + + // Data is embedding-specific. The engine can free it by calling the + // freeTransfer op. The embedding can also use SCTAG_TMO_USER_MIN and + // greater, up to 32 bits, to distinguish specific ownership variants. + SCTAG_TMO_CUSTOM = 4, + + SCTAG_TMO_USER_MIN +}; +} /* namespace JS */ + // Read structured data from the reader r. This hook is used to read a value // previously serialized by a call to the WriteStructuredCloneOp hook. // @@ -43,13 +69,52 @@ typedef JSObject *(*ReadStructuredCloneOp)(JSContext *cx, JSStructuredCloneReade // // Return true on success, false on error/exception. typedef bool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w, - JS::HandleObject obj, void *closure); + JS::HandleObject obj, void *closure); // This is called when JS_WriteStructuredClone is given an invalid transferable. // To follow HTML5, the application must throw a DATA_CLONE_ERR DOMException // with error set to one of the JS_SCERR_* values. typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32_t errorid); +// This is called when JS_ReadStructuredClone receives a transferable object +// not known to the engine. If this hook does not exist or returns false, the +// JS engine calls the reportError op if set, otherwise it throws a +// DATA_CLONE_ERR DOM Exception. This method is called before any other +// callback and must return a non-null object in returnObject on success. +typedef bool (*ReadTransferStructuredCloneOp)(JSContext *cx, JSStructuredCloneReader *r, + uint32_t tag, void *content, uint64_t extraData, + void *closure, + JS::MutableHandleObject returnObject); + +// Called when JS_WriteStructuredClone receives a transferable object not +// handled by the engine. If this hook does not exist or returns false, the JS +// engine will call the reportError hook or fall back to throwing a +// DATA_CLONE_ERR DOM Exception. This method is called before any other +// callback. +// +// tag: indicates what type of transferable this is. Must be greater than +// 0xFFFF0201 (value of the internal SCTAG_TRANSFER_MAP_PENDING_ENTRY) +// +// ownership: see TransferableOwnership, above. Used to communicate any needed +// ownership info to the FreeTransferStructuredCloneOp. +// +// content, extraData: what the ReadTransferStructuredCloneOp will receive +// +typedef bool (*TransferStructuredCloneOp)(JSContext *cx, + JS::Handle obj, + void *closure, + // Output: + uint32_t *tag, + JS::TransferableOwnership *ownership, + void **content, + uint64_t *extraData); + +// Called when JS_ClearStructuredClone has to free an unknown transferable +// object. Note that it should never trigger a garbage collection (and will +// assert in a debug build if it does.) +typedef void (*FreeTransferStructuredCloneOp)(uint32_t tag, JS::TransferableOwnership ownership, + void *content, uint64_t extraData, void *closure); + // The maximum supported structured-clone serialization format version. Note // that this does not need to be bumped for Transferable-only changes, since // they are never saved to persistent storage. @@ -59,6 +124,9 @@ struct JSStructuredCloneCallbacks { ReadStructuredCloneOp read; WriteStructuredCloneOp write; StructuredCloneErrorOp reportError; + ReadTransferStructuredCloneOp readTransfer; + TransferStructuredCloneOp writeTransfer; + FreeTransferStructuredCloneOp freeTransfer; }; // Note: if the *data contains transferable objects, it can be read only once. @@ -68,14 +136,16 @@ JS_ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, uint32_t ve const JSStructuredCloneCallbacks *optionalCallbacks, void *closure); // Note: On success, the caller is responsible for calling -// JS_ClearStructuredClone(*datap, nbytesp). +// JS_ClearStructuredClone(*datap, nbytes, optionalCallbacks, closure). JS_PUBLIC_API(bool) JS_WriteStructuredClone(JSContext *cx, JS::HandleValue v, uint64_t **datap, size_t *nbytesp, const JSStructuredCloneCallbacks *optionalCallbacks, void *closure, JS::HandleValue transferable); JS_PUBLIC_API(bool) -JS_ClearStructuredClone(const uint64_t *data, size_t nbytes); +JS_ClearStructuredClone(uint64_t *data, size_t nbytes, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure); JS_PUBLIC_API(bool) JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes, bool *hasTransferable); @@ -89,10 +159,19 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { uint64_t *data_; size_t nbytes_; uint32_t version_; + const JSStructuredCloneCallbacks *callbacks_; + void *closure_; public: JSAutoStructuredCloneBuffer() - : data_(nullptr), nbytes_(0), version_(JS_STRUCTURED_CLONE_VERSION) {} + : data_(nullptr), nbytes_(0), version_(JS_STRUCTURED_CLONE_VERSION), + callbacks_(nullptr), closure_(nullptr) + {} + + JSAutoStructuredCloneBuffer(const JSStructuredCloneCallbacks *callbacks, void *closure) + : data_(nullptr), nbytes_(0), version_(JS_STRUCTURED_CLONE_VERSION), + callbacks_(callbacks), closure_(closure) + {} JSAutoStructuredCloneBuffer(JSAutoStructuredCloneBuffer &&other); JSAutoStructuredCloneBuffer &operator=(JSAutoStructuredCloneBuffer &&other); @@ -128,8 +207,8 @@ class JS_PUBLIC_API(JSAutoStructuredCloneBuffer) { private: // Copy and assignment are not supported. - JSAutoStructuredCloneBuffer(const JSAutoStructuredCloneBuffer &other); - JSAutoStructuredCloneBuffer &operator=(const JSAutoStructuredCloneBuffer &other); + JSAutoStructuredCloneBuffer(const JSAutoStructuredCloneBuffer &other) MOZ_DELETE; + JSAutoStructuredCloneBuffer &operator=(const JSAutoStructuredCloneBuffer &other) MOZ_DELETE; }; // The range of tag values the application may use for its own custom object types. diff --git a/js/public/Utility.h b/js/public/Utility.h index 5abfd16b2d78..90964a744111 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -35,10 +35,18 @@ namespace mozilla {} namespace js {} /* - * Pattern used to overwrite freed memory. If you are accessing an object with - * this pattern, you probably have a dangling pointer. + * Patterns used by SpiderMonkey to overwrite unused memory. If you are + * accessing an object with one of these pattern, you probably have a dangling + * pointer. */ -#define JS_FREE_PATTERN 0xDA +#define JS_FRESH_NURSERY_PATTERN 0x2F +#define JS_SWEPT_NURSERY_PATTERN 0x2B +#define JS_ALLOCATED_NURSERY_PATTERN 0x2D +#define JS_FRESH_TENURED_PATTERN 0x4F +#define JS_SWEPT_TENURED_PATTERN 0x4B +#define JS_ALLOCATED_TENURED_PATTERN 0x4D +#define JS_SWEPT_CODE_PATTERN 0x3b +#define JS_SWEPT_FRAME_PATTERN 0x5b #define JS_ASSERT(expr) MOZ_ASSERT(expr) #define JS_ASSERT_IF(cond, expr) MOZ_ASSERT_IF(cond, expr) diff --git a/js/src/assembler/assembler/MacroAssemblerCodeRef.h b/js/src/assembler/assembler/MacroAssemblerCodeRef.h index 6ed05bbe0abc..b6f17ebc68e5 100644 --- a/js/src/assembler/assembler/MacroAssemblerCodeRef.h +++ b/js/src/assembler/assembler/MacroAssemblerCodeRef.h @@ -36,6 +36,8 @@ #if ENABLE_ASSEMBLER +#include "jsutil.h" + // ASSERT_VALID_CODE_POINTER checks that ptr is a non-null pointer, and that it is a valid // instruction address on the platform (for example, check any alignment requirements). #if WTF_CPU_ARM_THUMB2 @@ -199,7 +201,7 @@ public: if (!m_executablePool) return; - memset(m_code.executableAddress(), JS_FREE_PATTERN, m_allocSize); + JS_POISON(m_code.executableAddress(), JS_SWEPT_CODE_PATTERN, m_allocSize); m_code = MacroAssemblerCodePtr(); diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index f7767a969302..86a048875540 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -192,6 +192,14 @@ GetBuildConfiguration(JSContext *cx, unsigned argc, jsval *vp) if (!JS_SetProperty(cx, info, "binary-data", value)) return false; +#ifdef EXPOSE_INTL_API + value = BooleanValue(true); +#else + value = BooleanValue(false); +#endif + if (!JS_SetProperty(cx, info, "intl-api", value)) + return false; + args.rval().setObject(*info); return true; } @@ -1274,7 +1282,7 @@ class CloneBufferObject : public JSObject { // Discard an owned clone buffer. void discard() { if (data()) - JS_ClearStructuredClone(data(), nbytes()); + JS_ClearStructuredClone(data(), nbytes(), nullptr, nullptr); setReservedSlot(DATA_SLOT, PrivateValue(nullptr)); } diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 3c7dff34d643..7aa4ed1dcb08 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -288,6 +288,7 @@ struct FreeSpan return nullptr; } checkSpan(); + JS_POISON(reinterpret_cast(thing), JS_ALLOCATED_TENURED_PATTERN, thingSize); return reinterpret_cast(thing); } diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index edb89e28cadb..bab045d4abfd 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -97,17 +97,36 @@ static void MarkChildren(JSTracer *trc, jit::JitCode *code); /*** Object Marking ***/ -#ifdef DEBUG +#if defined(DEBUG) template static inline bool IsThingPoisoned(T *thing) { - const uint8_t pb = JS_FREE_PATTERN; - const uint32_t pw = pb | (pb << 8) | (pb << 16) | (pb << 24); - JS_STATIC_ASSERT(sizeof(T) >= sizeof(FreeSpan) + sizeof(uint32_t)); - uint32_t *p = - reinterpret_cast(reinterpret_cast(thing) + 1); - return *p == pw; + static_assert(sizeof(T) >= sizeof(FreeSpan) + sizeof(uint32_t), + "Ensure it is well defined to look past any free span that " + "may be embedded in the thing's header when freed."); + const uint8_t poisonBytes[] = { + JS_FRESH_NURSERY_PATTERN, + JS_SWEPT_NURSERY_PATTERN, + JS_ALLOCATED_NURSERY_PATTERN, + JS_FRESH_TENURED_PATTERN, + JS_SWEPT_TENURED_PATTERN, + JS_ALLOCATED_TENURED_PATTERN, + JS_SWEPT_CODE_PATTERN, + JS_SWEPT_FRAME_PATTERN + }; + const int numPoisonBytes = sizeof(poisonBytes) / sizeof(poisonBytes[0]); + uint32_t *p = reinterpret_cast(reinterpret_cast(thing) + 1); + // Note: all free patterns are odd to make the common, not-poisoned case a single test. + if ((*p & 1) == 0) + return false; + for (int i = 0; i < numPoisonBytes; ++i) { + const uint8_t pb = poisonBytes[i]; + const uint32_t pw = pb | (pb << 8) | (pb << 16) | (pb << 24); + if (*p == pw) + return true; + } + return false; } #endif diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 26f369bc00f3..b5941d0013ca 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -62,9 +62,7 @@ js::Nursery::init() currentStart_ = start(); rt->gcNurseryEnd_ = chunk(LastNurseryChunk).end(); numActiveChunks_ = 1; -#ifdef JS_GC_ZEAL - JS_POISON(heap, FreshNursery, NurserySize); -#endif + JS_POISON(heap, JS_FRESH_NURSERY_PATTERN, NurserySize); setCurrentChunk(0); updateDecommittedRegion(); @@ -170,9 +168,7 @@ js::Nursery::allocate(size_t size) void *thing = (void *)position(); position_ = position() + size; -#ifdef JS_GC_ZEAL - JS_POISON(thing, AllocatedThing, size); -#endif + JS_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size); return thing; } @@ -701,6 +697,8 @@ js::Nursery::collect(JSRuntime *rt, JS::gcreason::Reason reason, TypeObjectList if (isEmpty()) return; + rt->gcStats.count(gcstats::STAT_MINOR_GC); + TIME_START(total); AutoStopVerifyingBarriers av(rt, false); @@ -877,7 +875,7 @@ js::Nursery::sweep(JSRuntime *rt) { #ifdef JS_GC_ZEAL /* Poison the nursery contents so touching a freed object will crash. */ - JS_POISON((void *)start(), SweptNursery, NurserySize - sizeof(JSRuntime *)); + JS_POISON((void *)start(), JS_SWEPT_NURSERY_PATTERN, NurserySize); for (int i = 0; i < NumNurseryChunks; ++i) chunk(i).trailer.runtime = runtime(); @@ -890,6 +888,11 @@ js::Nursery::sweep(JSRuntime *rt) } else #endif { +#ifdef JS_CRASH_DIAGNOSTICS + JS_POISON((void *)start(), JS_SWEPT_NURSERY_PATTERN, allocationEnd() - start()); + for (int i = 0; i < numActiveChunks_; ++i) + chunk(i).trailer.runtime = runtime(); +#endif setCurrentChunk(0); } @@ -900,7 +903,9 @@ js::Nursery::sweep(JSRuntime *rt) void js::Nursery::growAllocableSpace() { +#ifdef JS_GC_ZEAL MOZ_ASSERT_IF(runtime()->gcZeal_ == ZealGenerationalGCValue, numActiveChunks_ == NumNurseryChunks); +#endif numActiveChunks_ = Min(numActiveChunks_ * 2, NumNurseryChunks); } diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index ed090d4cd1a5..3f33ab7d0f0a 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -293,9 +293,6 @@ class Nursery * In debug and zeal builds, these bytes indicate the state of an unused * segment of nursery-allocated memory. */ - static const uint8_t FreshNursery = 0x2a; - static const uint8_t SweptNursery = 0x2b; - static const uint8_t AllocatedThing = 0x2c; void enterZealMode() { if (isEnabled()) numActiveChunks_ = NumNurseryChunks; diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 36226c3366fa..edc0f16958ff 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -365,6 +365,7 @@ Statistics::formatData(StatisticsSerializer &ss, uint64_t timestamp) ss.appendNumber("Zones Collected", "%d", "", collectedCount); ss.appendNumber("Total Zones", "%d", "", zoneCount); ss.appendNumber("Total Compartments", "%d", "", compartmentCount); + ss.appendNumber("Minor GCs", "%d", "", counts[STAT_MINOR_GC]); ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100)); ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100)); ss.appendDecimal("SCC Sweep Total", "ms", t(sccTotal)); diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 695c5af57448..d97b4d36899c 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -68,6 +68,7 @@ enum Phase { enum Stat { STAT_NEW_CHUNK, STAT_DESTROY_CHUNK, + STAT_MINOR_GC, STAT_LIMIT }; diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 40539855a72e..8863538eb26c 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -6706,6 +6706,7 @@ ICGetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) } Register code = regs.takeAny(); masm.loadPtr(Address(BaselineStubReg, ICGetProp_CallScripted::offsetOfGetter()), callee); + masm.branchIfFunctionHasNoScript(callee, &failureLeaveStubFrame); masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code); masm.loadBaselineOrIonRaw(code, code, SequentialExecution, &failureLeaveStubFrame); @@ -7600,6 +7601,7 @@ ICSetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) } Register code = regs.takeAny(); masm.loadPtr(Address(BaselineStubReg, ICSetProp_CallScripted::offsetOfSetter()), callee); + masm.branchIfFunctionHasNoScript(callee, &failureLeaveStubFrame); masm.loadPtr(Address(callee, JSFunction::offsetOfNativeOrScript()), code); masm.loadBaselineOrIonRaw(code, code, SequentialExecution, &failureLeaveStubFrame); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 108dc1657ead..fda263d86900 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -742,7 +742,7 @@ JitCode::finalize(FreeOp *fop) // Don't do this if the Ion code is protected, as the signal handler will // deadlock trying to reacquire the interrupt lock. if (fop->runtime()->jitRuntime() && !fop->runtime()->jitRuntime()->ionCodeProtected()) - memset(code_, JS_FREE_PATTERN, bufferSize_); + memset(code_, JS_SWEPT_CODE_PATTERN, bufferSize_); code_ = nullptr; // Code buffers are stored inside JSC pools. diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 37e280a38043..9aac3427257a 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2141,7 +2141,7 @@ js::ArrayShiftMoveElements(JSObject *obj) * themselves. */ uint32_t initlen = obj->getDenseInitializedLength(); - obj->moveDenseElementsUnbarriered(0, 1, initlen); + obj->moveDenseElementsNoPreBarrier(0, 1, initlen); } /* ES5 15.4.4.9 */ diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index b2cc008f6bac..21af761c5440 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -484,7 +484,7 @@ Arena::finalize(FreeOp *fop, AllocKind thingKind, size_t thingSize) if (!newFreeSpanStart) newFreeSpanStart = thing; t->finalize(fop); - JS_POISON(t, JS_FREE_PATTERN, thingSize); + JS_POISON(t, JS_SWEPT_TENURED_PATTERN, thingSize); } } } @@ -493,6 +493,7 @@ Arena::finalize(FreeOp *fop, AllocKind thingKind, size_t thingSize) JS_ASSERT(newListTail == &newListHead); JS_ASSERT(!newFreeSpanStart || newFreeSpanStart == thingsStart(thingKind)); + JS_POISON(data, JS_SWEPT_TENURED_PATTERN, sizeof(data)); return true; } @@ -781,7 +782,7 @@ Chunk::prepareToBeFreed(JSRuntime *rt) void Chunk::init(JSRuntime *rt) { - JS_POISON(this, JS_FREE_PATTERN, ChunkSize); + JS_POISON(this, JS_FRESH_TENURED_PATTERN, ChunkSize); /* * We clear the bitmap to guard against xpc_IsGrayGCThing being called on diff --git a/js/src/jsiter.cpp b/js/src/jsiter.cpp index 3c050d79667b..e3f227829369 100644 --- a/js/src/jsiter.cpp +++ b/js/src/jsiter.cpp @@ -1472,8 +1472,8 @@ FinalizeGenerator(FreeOp *fop, JSObject *obj) gen->state == JSGEN_OPEN); // If gen->state is JSGEN_CLOSED, gen->fp may be nullptr. if (gen->fp) - JS_POISON(gen->fp, JS_FREE_PATTERN, sizeof(InterpreterFrame)); - JS_POISON(gen, JS_FREE_PATTERN, sizeof(JSGenerator)); + JS_POISON(gen->fp, JS_SWEPT_FRAME_PATTERN, sizeof(InterpreterFrame)); + JS_POISON(gen, JS_SWEPT_FRAME_PATTERN, sizeof(JSGenerator)); fop->free_(gen); } diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 9df3fcec93a3..67a9dcdfc92e 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -725,13 +725,14 @@ class JSObject : public js::ObjectImpl } } - void moveDenseElementsUnbarriered(uint32_t dstStart, uint32_t srcStart, uint32_t count) { + void moveDenseElementsNoPreBarrier(uint32_t dstStart, uint32_t srcStart, uint32_t count) { JS_ASSERT(!shadowZone()->needsBarrier()); JS_ASSERT(dstStart + count <= getDenseCapacity()); JS_ASSERT(srcStart + count <= getDenseCapacity()); memmove(elements + dstStart, elements + srcStart, count * sizeof(js::Value)); + DenseRangeWriteBarrierPost(runtimeFromMainThread(), this, dstStart, count); } bool shouldConvertDoubleElements() { diff --git a/js/src/jsutil.h b/js/src/jsutil.h index 98ae97d00a62..521cfd5a6263 100644 --- a/js/src/jsutil.h +++ b/js/src/jsutil.h @@ -278,7 +278,7 @@ ClearAllBitArrayElements(size_t *array, size_t length) #ifdef DEBUG # define JS_CRASH_DIAGNOSTICS 1 #endif -#ifdef JS_CRASH_DIAGNOSTICS +#if defined(JS_CRASH_DIAGNOSTICS) || defined(JS_GC_ZEAL) # define JS_POISON(p, val, size) memset((p), (val), (size)) #else # define JS_POISON(p, val, size) ((void) 0) diff --git a/js/src/tests/Intl/extensions/options-value-emulates-undefined.js b/js/src/tests/Intl/extensions/options-value-emulates-undefined.js index e7af6a4860de..a154e0c37fc7 100644 --- a/js/src/tests/Intl/extensions/options-value-emulates-undefined.js +++ b/js/src/tests/Intl/extensions/options-value-emulates-undefined.js @@ -10,6 +10,12 @@ var summary = print(BUGNUMBER + ": " + summary); +if (typeof Intl !== 'object' && typeof quit == 'function') { + print("Test skipped"); + reportCompare(true, true); + quit(0); +} + /************** * BEGIN TEST * **************/ diff --git a/js/src/tests/lib/jittests.py b/js/src/tests/lib/jittests.py index fb509eeb7136..079ab8c0a32a 100755 --- a/js/src/tests/lib/jittests.py +++ b/js/src/tests/lib/jittests.py @@ -340,7 +340,18 @@ def run_test_remote(test, device, prefix, options): # the same buffer to both. return TestOutput(test, cmd, out, out, returncode, None, False) -def check_output(out, err, rc, test): +def check_output(out, err, rc, timed_out, test): + if timed_out: + # The shell sometimes hangs on shutdown on Windows 7 and Windows + # Server 2008. See bug 970063 comment 7 for a description of the + # problem. Until bug 956899 is fixed, ignore timeouts on these + # platforms (versions 6.0 and 6.1). + if sys.platform == 'win32': + ver = sys.getwindowsversion() + if ver.major == 6 and ver.minor <= 1: + return True + return False + if test.expect_error: # The shell exits with code 3 on uncaught exceptions. # Sometimes 0 is returned on Windows for unknown reasons. @@ -582,7 +593,7 @@ def process_test_results(results, num_tests, options): if res.test.valgrind: sys.stdout.write(res.err) - ok = check_output(res.out, res.err, res.rc, res.test) + ok = check_output(res.out, res.err, res.rc, res.timed_out, res.test) doing = 'after %s' % res.test.relpath_tests if not ok: failures.append(res) diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index df66d5319a0a..1eed49931668 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -70,8 +70,8 @@ enum StructuredDataType { SCTAG_STRING_OBJECT, SCTAG_NUMBER_OBJECT, SCTAG_BACK_REFERENCE_OBJECT, - SCTAG_DO_NOT_USE_1, - SCTAG_DO_NOT_USE_2, + SCTAG_DO_NOT_USE_1, // Required for backwards compatibility + SCTAG_DO_NOT_USE_2, // Required for backwards compatibility SCTAG_TYPED_ARRAY_OBJECT, SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100, SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeDescr::TYPE_INT8, @@ -91,11 +91,24 @@ enum StructuredDataType { * require bumping JS_STRUCTURED_CLONE_VERSION. */ SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200, - SCTAG_TRANSFER_MAP_ENTRY, + SCTAG_TRANSFER_MAP_PENDING_ENTRY, + SCTAG_TRANSFER_MAP_ARRAY_BUFFER, + SCTAG_TRANSFER_MAP_SHARED_BUFFER, + SCTAG_TRANSFER_MAP_END_OF_BUILTIN_TYPES, SCTAG_END_OF_BUILTIN_TYPES }; +/* + * Format of transfer map: + * + * numTransferables (64 bits) + * array of: + * + * pointer (64 bits) + * extraData (64 bits), eg byte length for ArrayBuffers + */ + // Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the // contents have been read out yet or not. enum TransferableMapHeader { @@ -103,29 +116,17 @@ enum TransferableMapHeader { SCTAG_TM_TRANSFERRED }; -enum TransferableObjectType { - // Transferable data has not been filled in yet - SCTAG_TM_UNFILLED = 0, - - // Structured clone buffer does not yet own the data - SCTAG_TM_UNOWNED = 1, - - // All values at least this large are owned by the clone buffer - SCTAG_TM_FIRST_OWNED = 2, - - // Data is a pointer to a SharedArrayRawBuffer. - SCTAG_TM_SHARED_BUFFER = 2, - - // Data is a pointer that can be freed - SCTAG_TM_ALLOC_DATA = 3, -}; +static inline uint64_t +PairToUInt64(uint32_t tag, uint32_t data) +{ + return uint64_t(data) | (uint64_t(tag) << 32); +} namespace js { struct SCOutput { public: explicit SCOutput(JSContext *cx); - ~SCOutput(); JSContext *context() const { return cx; } @@ -146,16 +147,20 @@ struct SCOutput { private: JSContext *cx; - js::Vector buf; + Vector buf; }; -struct SCInput { +class SCInput { public: SCInput(JSContext *cx, uint64_t *data, size_t nbytes); JSContext *context() const { return cx; } + static void getPtr(const uint64_t *buffer, void **ptr); + static void getPair(const uint64_t *buffer, uint32_t *tagp, uint32_t *datap); + bool read(uint64_t *p); + bool readNativeEndian(uint64_t *p); bool readPair(uint32_t *tagp, uint32_t *datap); bool readDouble(double *p); bool readBytes(void *p, size_t nbytes); @@ -165,21 +170,19 @@ struct SCInput { bool get(uint64_t *p); bool getPair(uint32_t *tagp, uint32_t *datap); - bool replace(uint64_t u); - bool replacePair(uint32_t tag, uint32_t data); - uint64_t *tell() const { return point; } - void seek(uint64_t *pos) { - JS_ASSERT(pos <= end); - point = pos; - } + uint64_t *end() const { return bufEnd; } template bool readArray(T *p, size_t nelems); - private: - bool eof(); + bool reportTruncated() { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, + JSMSG_SC_BAD_SERIALIZED_DATA, "truncated"); + return false; + } + private: void staticAssertions() { JS_STATIC_ASSERT(sizeof(jschar) == 2); JS_STATIC_ASSERT(sizeof(uint32_t) == 4); @@ -188,20 +191,20 @@ struct SCInput { JSContext *cx; uint64_t *point; - uint64_t *end; + uint64_t *bufEnd; }; -} +} /* namespace js */ struct JSStructuredCloneReader { public: - explicit JSStructuredCloneReader(js::SCInput &in, const JSStructuredCloneCallbacks *cb, + explicit JSStructuredCloneReader(SCInput &in, const JSStructuredCloneCallbacks *cb, void *cbClosure) : in(in), objs(in.context()), allObjs(in.context()), callbacks(cb), closure(cbClosure) { } - js::SCInput &input() { return in; } - bool read(js::Value *vp); + SCInput &input() { return in; } + bool read(Value *vp); private: JSContext *context() { return in.context(); } @@ -210,19 +213,19 @@ struct JSStructuredCloneReader { bool checkDouble(double d); JSString *readString(uint32_t nchars); - bool readTypedArray(uint32_t arrayType, uint32_t nelems, js::Value *vp, bool v1Read = false); - bool readArrayBuffer(uint32_t nbytes, js::Value *vp); - bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, js::Value *vp); + bool readTypedArray(uint32_t arrayType, uint32_t nelems, Value *vp, bool v1Read = false); + bool readArrayBuffer(uint32_t nbytes, Value *vp); + bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, Value *vp); bool readId(jsid *idp); - bool startRead(js::Value *vp); + bool startRead(Value *vp); - js::SCInput ∈ + SCInput ∈ // Stack of objects with properties remaining to be read. - js::AutoValueVector objs; + AutoValueVector objs; // Stack of all objects read during this deserialization - js::AutoValueVector allObjs; + AutoValueVector allObjs; // The user defined callbacks that will be used for cloning. const JSStructuredCloneCallbacks *callbacks; @@ -230,25 +233,31 @@ struct JSStructuredCloneReader { // Any value passed to JS_ReadStructuredClone. void *closure; - friend bool JS_ReadTypedArray(JSStructuredCloneReader *r, JS::MutableHandleValue vp); + friend bool JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp); }; struct JSStructuredCloneWriter { public: - explicit JSStructuredCloneWriter(js::SCOutput &out, + explicit JSStructuredCloneWriter(JSContext *cx, const JSStructuredCloneCallbacks *cb, void *cbClosure, jsval tVal) - : out(out), objs(out.context()), + : out(cx), objs(out.context()), counts(out.context()), ids(out.context()), memory(out.context()), callbacks(cb), closure(cbClosure), transferable(out.context(), tVal), transferableObjects(out.context()) { } + ~JSStructuredCloneWriter(); + bool init() { return memory.init() && parseTransferable() && writeTransferMap(); } - bool write(const js::Value &v); + bool write(const Value &v); - js::SCOutput &output() { return out; } + SCOutput &output() { return out; } + + bool extractBuffer(uint64_t **datap, size_t *sizep) { + return out.extractBuffer(datap, sizep); + } private: JSContext *context() { return out.context(); } @@ -257,37 +266,37 @@ struct JSStructuredCloneWriter { bool writeString(uint32_t tag, JSString *str); bool writeId(jsid id); - bool writeArrayBuffer(JS::HandleObject obj); - bool writeTypedArray(JS::HandleObject obj); - bool startObject(JS::HandleObject obj, bool *backref); - bool startWrite(const js::Value &v); - bool traverseObject(JS::HandleObject obj); + bool writeArrayBuffer(HandleObject obj); + bool writeTypedArray(HandleObject obj); + bool startObject(HandleObject obj, bool *backref); + bool startWrite(const Value &v); + bool traverseObject(HandleObject obj); bool parseTransferable(); - void reportErrorTransferable(); + bool reportErrorTransferable(); bool transferOwnership(); inline void checkStack(); - js::SCOutput &out; + SCOutput out; // Vector of objects with properties remaining to be written. // // NB: These can span multiple compartments, so the compartment must be // entered before any manipulation is performed. - js::AutoValueVector objs; + AutoValueVector objs; // counts[i] is the number of properties of objs[i] remaining to be written. // counts.length() == objs.length() and sum(counts) == ids.length(). - js::Vector counts; + Vector counts; // Ids of properties remaining to be written. - js::AutoIdVector ids; + AutoIdVector ids; // The "memory" list described in the HTML5 internal structured cloning algorithm. // memory is a superset of objs; items are never removed from Memory // until a serialization operation is finished - typedef js::AutoObjectUnsigned32HashMap CloneMemory; + typedef AutoObjectUnsigned32HashMap CloneMemory; CloneMemory memory; // The user defined callbacks that will be used for cloning. @@ -297,10 +306,10 @@ struct JSStructuredCloneWriter { void *closure; // List of transferable objects - JS::RootedValue transferable; - JS::AutoObjectVector transferableObjects; + RootedValue transferable; + AutoObjectVector transferableObjects; - friend bool JS_WriteTypedArray(JSStructuredCloneWriter *w, JS::HandleValue v); + friend bool JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v); }; JS_FRIEND_API(uint64_t) @@ -314,16 +323,22 @@ JS_STATIC_ASSERT(SCTAG_END_OF_BUILTIN_TYPES <= JS_SCTAG_USER_MIN); JS_STATIC_ASSERT(JS_SCTAG_USER_MIN <= JS_SCTAG_USER_MAX); JS_STATIC_ASSERT(ScalarTypeDescr::TYPE_INT8 == 0); -namespace js { +static void +ReportErrorTransferable(JSContext *cx, const JSStructuredCloneCallbacks *callbacks) +{ + if (callbacks && callbacks->reportError) + callbacks->reportError(cx, JS_SCERR_TRANSFERABLE); + else + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE); +} bool WriteStructuredClone(JSContext *cx, HandleValue v, uint64_t **bufp, size_t *nbytesp, const JSStructuredCloneCallbacks *cb, void *cbClosure, jsval transferable) { - SCOutput out(cx); - JSStructuredCloneWriter w(out, cb, cbClosure, transferable); - return w.init() && w.write(v) && out.extractBuffer(bufp, nbytesp); + JSStructuredCloneWriter w(cx, cb, cbClosure, transferable); + return w.init() && w.write(v) && w.extractBuffer(bufp, nbytesp); } bool @@ -335,56 +350,62 @@ ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, MutableHandleV return r.read(vp.address()); } -// This may acquire new ways of discarding transfer map entries as new -// Transferables are implemented. +// If the given buffer contains Transferables, free them. Note that custom +// Transferables will use the JSStructuredCloneCallbacks::freeTransfer() to +// delete their transferables. static void -DiscardEntry(uint32_t mapEntryDescriptor, const uint64_t *ptr) +Discard(uint64_t *buffer, size_t nbytes, const JSStructuredCloneCallbacks *cb, void *cbClosure) { - if (mapEntryDescriptor == SCTAG_TM_ALLOC_DATA) { - uint64_t u = LittleEndian::readUint64(ptr); - js_free(reinterpret_cast(u)); - } else { - JS_ASSERT(mapEntryDescriptor == SCTAG_TM_SHARED_BUFFER); - uint64_t u = LittleEndian::readUint64(ptr); - SharedArrayRawBuffer *raw = reinterpret_cast(u); - if (raw) - raw->dropReference(); - } -} - -static void -Discard(const uint64_t *begin, const uint64_t *end) -{ - const uint64_t *point = begin; - if (begin >= end) + JS_ASSERT(nbytes % sizeof(uint64_t) == 0); + if (nbytes < sizeof(uint64_t)) return; // Empty buffer - uint64_t u = LittleEndian::readUint64(point++); - uint32_t tag = uint32_t(u >> 32); + uint64_t *point = buffer; + uint32_t tag, data; + SCInput::getPair(point++, &tag, &data); if (tag != SCTAG_TRANSFER_MAP_HEADER) return; - if (TransferableMapHeader(u) == SCTAG_TM_TRANSFERRED) + if (TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) return; + // freeTransfer should not GC + JS::AutoAssertNoGC nogc; + uint64_t numTransferables = LittleEndian::readUint64(point++); while (numTransferables--) { - uint64_t u = LittleEndian::readUint64(point++); - JS_ASSERT(uint32_t(u >> 32) == SCTAG_TRANSFER_MAP_ENTRY); - uint32_t mapEntryDescriptor = uint32_t(u); - if (mapEntryDescriptor >= SCTAG_TM_FIRST_OWNED) { - DiscardEntry(mapEntryDescriptor, point); - point += 2; // Pointer and userdata + uint32_t ownership; + SCInput::getPair(point++, &tag, &ownership); + JS_ASSERT(tag >= SCTAG_TRANSFER_MAP_PENDING_ENTRY); + + void *content; + SCInput::getPtr(point++, &content); + + uint64_t extraData = LittleEndian::readUint64(point++); + + if (ownership < JS::SCTAG_TMO_FIRST_OWNED) + continue; + + if (ownership == JS::SCTAG_TMO_ALLOC_DATA) { + js_free(content); + } else if (ownership == JS::SCTAG_TMO_SHARED_BUFFER) { + SharedArrayRawBuffer *raw = static_cast(content); + if (raw) + raw->dropReference(); + } else if (cb && cb->freeTransfer) { + cb->freeTransfer(tag, JS::TransferableOwnership(ownership), content, extraData, cbClosure); + } else { + MOZ_ASSERT(false, "unknown ownership"); } } } static void -ClearStructuredClone(const uint64_t *data, size_t nbytes) +ClearStructuredClone(uint64_t *data, size_t nbytes, + const JSStructuredCloneCallbacks *cb, void *cbClosure) { - JS_ASSERT(nbytes % 8 == 0); - Discard(data, data + nbytes / 8); - js_free(const_cast(data)); + Discard(data, nbytes, cb, cbClosure); + js_free(data); } bool @@ -402,40 +423,39 @@ StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes, bool *has return true; } -} /* namespace js */ - -static inline uint64_t -PairToUInt64(uint32_t tag, uint32_t data) -{ - return uint64_t(data) | (uint64_t(tag) << 32); -} - -bool -SCInput::eof() -{ - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, - JSMSG_SC_BAD_SERIALIZED_DATA, "truncated"); - return false; -} +namespace js { SCInput::SCInput(JSContext *cx, uint64_t *data, size_t nbytes) - : cx(cx), point(data), end(data + nbytes / 8) + : cx(cx), point(data), bufEnd(data + nbytes / 8) { - JS_ASSERT((uintptr_t(data) & 7) == 0); + // On 32-bit, we sometimes construct an SCInput from an SCOutput buffer, + // which is not guaranteed to be 8-byte aligned + JS_ASSERT((uintptr_t(data) & (sizeof(int) - 1)) == 0); JS_ASSERT((nbytes & 7) == 0); } bool SCInput::read(uint64_t *p) { - if (point == end) { + if (point == bufEnd) { *p = 0; /* initialize to shut GCC up */ - return eof(); + return reportTruncated(); } *p = LittleEndian::readUint64(point++); return true; } +bool +SCInput::readNativeEndian(uint64_t *p) +{ + if (point == bufEnd) { + *p = 0; /* initialize to shut GCC up */ + return reportTruncated(); + } + *p = *(point++); + return true; +} + bool SCInput::readPair(uint32_t *tagp, uint32_t *datap) { @@ -451,8 +471,8 @@ SCInput::readPair(uint32_t *tagp, uint32_t *datap) bool SCInput::get(uint64_t *p) { - if (point == end) - return eof(); + if (point == bufEnd) + return reportTruncated(); *p = LittleEndian::readUint64(point); return true; } @@ -460,7 +480,7 @@ SCInput::get(uint64_t *p) bool SCInput::getPair(uint32_t *tagp, uint32_t *datap) { - uint64_t u; + uint64_t u = 0; if (!get(&u)) return false; @@ -469,19 +489,12 @@ SCInput::getPair(uint32_t *tagp, uint32_t *datap) return true; } -bool -SCInput::replace(uint64_t u) +void +SCInput::getPair(const uint64_t *p, uint32_t *tagp, uint32_t *datap) { - if (point == end) - return eof(); - LittleEndian::writeUint64(point, u); - return true; -} - -bool -SCInput::replacePair(uint32_t tag, uint32_t data) -{ - return replace(PairToUInt64(tag, data)); + uint64_t u = LittleEndian::readUint64(p); + *tagp = uint32_t(u >> 32); + *datap = uint32_t(u); } bool @@ -523,8 +536,8 @@ SCInput::readArray(T *p, size_t nelems) * larger than the remaining data. */ size_t nwords = JS_HOWMANY(nelems, sizeof(uint64_t) / sizeof(T)); - if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(end - point)) - return eof(); + if (nelems + sizeof(uint64_t) / sizeof(T) - 1 < nelems || nwords > size_t(bufEnd - point)) + return reportTruncated(); copyAndSwapFromLittleEndian(p, point, nelems); point += nwords; @@ -544,25 +557,26 @@ SCInput::readChars(jschar *p, size_t nchars) return readArray((uint16_t *) p, nchars); } +void +SCInput::getPtr(const uint64_t *p, void **ptr) +{ + // No endianness conversion is used for pointers, since they are not sent + // across address spaces anyway. + *ptr = reinterpret_cast(*p); +} + bool SCInput::readPtr(void **p) { - // On a 32 bit system the void* variable we have to write to is only - // 32 bits, so we create a 64 temporary and discard the unused bits. - uint64_t tmp; - bool ret = read(&tmp); - *p = reinterpret_cast(tmp); - return ret; + uint64_t u; + if (!readNativeEndian(&u)) + return false; + *p = reinterpret_cast(u); + return true; } SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {} -SCOutput::~SCOutput() -{ - // Free any transferable data left lying around in the buffer - Discard(rawBuffer(), rawBuffer() + count()); -} - bool SCOutput::write(uint64_t u) { @@ -685,8 +699,19 @@ SCOutput::extractBuffer(uint64_t **datap, size_t *sizep) return (*datap = buf.extractRawBuffer()) != nullptr; } +} /* namespace js */ + JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX); +JSStructuredCloneWriter::~JSStructuredCloneWriter() +{ + // Free any transferable data left lying around in the buffer + uint64_t *data; + size_t size; + MOZ_ALWAYS_TRUE(extractBuffer(&data, &size)); + ClearStructuredClone(data, size, callbacks, closure); +} + bool JSStructuredCloneWriter::parseTransferable() { @@ -695,17 +720,13 @@ JSStructuredCloneWriter::parseTransferable() if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable)) return true; - if (!transferable.isObject()) { - reportErrorTransferable(); - return false; - } + if (!transferable.isObject()) + return reportErrorTransferable(); JSContext *cx = context(); RootedObject array(cx, &transferable.toObject()); - if (!JS_IsArrayObject(cx, array)) { - reportErrorTransferable(); - return false; - } + if (!JS_IsArrayObject(cx, array)) + return reportErrorTransferable(); uint32_t length; if (!JS_GetArrayLength(cx, array, &length)) { @@ -715,24 +736,18 @@ JSStructuredCloneWriter::parseTransferable() RootedValue v(context()); for (uint32_t i = 0; i < length; ++i) { - if (!JS_GetElement(cx, array, i, &v)) { + if (!JS_GetElement(cx, array, i, &v)) return false; - } - if (!v.isObject()) { - reportErrorTransferable(); - return false; - } + if (!v.isObject()) + return reportErrorTransferable(); + + RootedObject tObj(context(), CheckedUnwrap(&v.toObject())); - JSObject* tObj = CheckedUnwrap(&v.toObject()); if (!tObj) { JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); return false; } - if (!tObj->is() && !tObj->is()) { - reportErrorTransferable(); - return false; - } // No duplicates allowed if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) { @@ -747,13 +762,11 @@ JSStructuredCloneWriter::parseTransferable() return true; } -void +bool JSStructuredCloneWriter::reportErrorTransferable() { - if (callbacks && callbacks->reportError) - return callbacks->reportError(context(), JS_SCERR_TRANSFERABLE); - else - JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_SC_NOT_TRANSFERABLE); + ReportErrorTransferable(context(), callbacks); + return false; } bool @@ -914,7 +927,7 @@ JSStructuredCloneWriter::startWrite(const Value &v) // behind it. If we can, unwrap the object. obj = CheckedUnwrap(obj); if (!obj) { - JS_ReportError(context(), "Permission denied to access object"); + JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); return false; } @@ -977,13 +990,11 @@ JSStructuredCloneWriter::writeTransferMap() // Emit a placeholder pointer. We will steal the data and neuter the // transferable later, in the case of ArrayBufferObject. - if (!out.writePair(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_UNFILLED)) + if (!out.writePair(SCTAG_TRANSFER_MAP_PENDING_ENTRY, JS::SCTAG_TMO_UNFILLED)) return false; if (!out.writePtr(nullptr)) // Pointer to ArrayBuffer contents or to SharedArrayRawBuffer. return false; - if (!out.write(0)) // |byteLength| for an ArrayBuffer, 0 for SharedArrayBuffer - return false; - if (!out.write(0)) // |userdata|, intended to be passed to callbacks. + if (!out.write(0)) // extraData return false; } @@ -1008,38 +1019,52 @@ JSStructuredCloneWriter::transferOwnership() for (JS::AutoObjectVector::Range tr = transferableObjects.all(); !tr.empty(); tr.popFront()) { RootedObject obj(context(), tr.front()); - MOZ_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_ENTRY); - MOZ_ASSERT(uint32_t(LittleEndian::readUint64(point)) == SCTAG_TM_UNFILLED); + uint32_t tag; + JS::TransferableOwnership ownership; + void *content; + uint64_t extraData; + +#if DEBUG + SCInput::getPair(point, &tag, (uint32_t*) &ownership); + MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_PENDING_ENTRY); + MOZ_ASSERT(ownership == JS::SCTAG_TMO_UNFILLED); +#endif if (obj->is()) { size_t nbytes = obj->as().byteLength(); - void *contents = JS_StealArrayBufferContents(context(), obj); - if (!contents) + content = JS_StealArrayBufferContents(context(), obj); + if (!content) return false; // Destructor will clean up the already-transferred data - - uint64_t entryTag = PairToUInt64(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_ALLOC_DATA); - LittleEndian::writeUint64(point++, entryTag); - LittleEndian::writeUint64(point++, reinterpret_cast(contents)); - LittleEndian::writeUint64(point++, nbytes); - LittleEndian::writeUint64(point++, 0); - } else { + tag = SCTAG_TRANSFER_MAP_ARRAY_BUFFER; + ownership = JS::SCTAG_TMO_ALLOC_DATA; + extraData = nbytes; + } else if (obj->is()) { SharedArrayRawBuffer *rawbuf = obj->as().rawBufferObject(); // Avoids a race condition where the parent thread frees the buffer // before the child has accepted the transferable. rawbuf->addReference(); - uint64_t entryTag = PairToUInt64(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_SHARED_BUFFER); - LittleEndian::writeUint64(point++, entryTag); - LittleEndian::writeUint64(point++, reinterpret_cast(rawbuf)); - LittleEndian::writeUint64(point++, 0); - LittleEndian::writeUint64(point++, 0); + tag = SCTAG_TRANSFER_MAP_SHARED_BUFFER; + ownership = JS::SCTAG_TMO_SHARED_BUFFER; + content = rawbuf; + extraData = 0; + } else { + if (!callbacks || !callbacks->writeTransfer) + return reportErrorTransferable(); + if (!callbacks->writeTransfer(context(), obj, closure, &tag, &ownership, &content, &extraData)) + return false; + JS_ASSERT(tag > SCTAG_TRANSFER_MAP_PENDING_ENTRY); } + + LittleEndian::writeUint64(point++, PairToUInt64(tag, ownership)); + LittleEndian::writeUint64(point++, reinterpret_cast(content)); + LittleEndian::writeUint64(point++, extraData); } JS_ASSERT(point <= out.rawBuffer() + out.count()); JS_ASSERT_IF(point < out.rawBuffer() + out.count(), - uint32_t(LittleEndian::readUint64(point) >> 32) != SCTAG_TRANSFER_MAP_ENTRY); + uint32_t(LittleEndian::readUint64(point) >> 32) < SCTAG_TRANSFER_MAP_HEADER); return true; } @@ -1402,15 +1427,9 @@ JSStructuredCloneReader::startRead(Value *vp) } case SCTAG_TRANSFER_MAP_HEADER: - // A map header cannot be here but just at the beginning of the buffer. - JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, - JSMSG_SC_BAD_SERIALIZED_DATA, - "invalid input"); - return false; - - case SCTAG_TRANSFER_MAP_ENTRY: - // A map cannot be here but just at the beginning of the buffer. - JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr, + case SCTAG_TRANSFER_MAP_PENDING_ENTRY: + // We should be past all the transfer map tags. + JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_BAD_SERIALIZED_DATA, "invalid input"); return false; @@ -1494,11 +1513,12 @@ JSStructuredCloneReader::readId(jsid *idp) bool JSStructuredCloneReader::readTransferMap() { + JSContext *cx = context(); uint64_t *headerPos = in.tell(); uint32_t tag, data; if (!in.getPair(&tag, &data)) - return false; + return in.reportTruncated(); if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED) return true; @@ -1513,47 +1533,56 @@ JSStructuredCloneReader::readTransferMap() if (!in.readPair(&tag, &data)) return false; - JS_ASSERT(tag == SCTAG_TRANSFER_MAP_ENTRY); - JS_ASSERT(data == SCTAG_TM_ALLOC_DATA || data == SCTAG_TM_SHARED_BUFFER); + + JS_ASSERT(tag != SCTAG_TRANSFER_MAP_PENDING_ENTRY); + RootedObject obj(cx); void *content; if (!in.readPtr(&content)) return false; - uint64_t nbytes; - if (!in.read(&nbytes)) + uint64_t extraData; + if (!in.read(&extraData)) return false; - uint64_t userdata; - if (!in.read(&userdata)) - return false; - - RootedObject obj(context()); - - if (data == SCTAG_TM_ALLOC_DATA) - obj = JS_NewArrayBufferWithContents(context(), nbytes, content); - else if (data == SCTAG_TM_SHARED_BUFFER) + if (tag == SCTAG_TRANSFER_MAP_ARRAY_BUFFER) { + size_t nbytes = extraData; + JS_ASSERT(data == JS::SCTAG_TMO_ALLOC_DATA); + obj = JS_NewArrayBufferWithContents(cx, nbytes, content); + } else if (tag == SCTAG_TRANSFER_MAP_SHARED_BUFFER) { + JS_ASSERT(data == JS::SCTAG_TMO_SHARED_BUFFER); obj = SharedArrayBufferObject::New(context(), (SharedArrayRawBuffer *)content); + } else { + if (!callbacks || !callbacks->readTransfer) { + ReportErrorTransferable(cx, callbacks); + return false; + } + if (!callbacks->readTransfer(cx, this, tag, content, extraData, closure, &obj)) + return false; + MOZ_ASSERT(obj); + MOZ_ASSERT(!cx->isExceptionPending()); + } if (!obj) return false; - // Rewind to the SCTAG_TRANSFER_MAP_ENTRY and mark this entry as unowned by - // the input buffer. - uint64_t *next = in.tell(); - in.seek(pos); - MOZ_ALWAYS_TRUE(in.replacePair(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_UNOWNED)); - in.seek(next); + // Mark the SCTAG_TRANSFER_MAP_* entry as no longer owned by the input + // buffer. + *pos = PairToUInt64(tag, JS::SCTAG_TMO_UNOWNED); + MOZ_ASSERT(headerPos < pos && pos < in.end()); if (!allObjs.append(ObjectValue(*obj))) return false; } - // Mark the whole transfer map as consumed - uint64_t *endPos = in.tell(); - in.seek(headerPos); - MOZ_ALWAYS_TRUE(in.replacePair(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED)); - in.seek(endPos); + // Mark the whole transfer map as consumed. + MOZ_ASSERT(headerPos <= in.tell()); +#ifdef DEBUG + SCInput::getPair(headerPos, &tag, &data); + MOZ_ASSERT(tag == SCTAG_TRANSFER_MAP_HEADER); + MOZ_ASSERT(TransferableMapHeader(data) != SCTAG_TM_TRANSFERRED); +#endif + *headerPos = PairToUInt64(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED); return true; } @@ -1587,11 +1616,12 @@ JSStructuredCloneReader::read(Value *vp) return true; } + using namespace js; JS_PUBLIC_API(bool) JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes, - uint32_t version, JS::MutableHandleValue vp, + uint32_t version, MutableHandleValue vp, const JSStructuredCloneCallbacks *optionalCallbacks, void *closure) { @@ -1610,9 +1640,9 @@ JS_ReadStructuredClone(JSContext *cx, uint64_t *buf, size_t nbytes, } JS_PUBLIC_API(bool) -JS_WriteStructuredClone(JSContext *cx, JS::HandleValue value, uint64_t **bufp, size_t *nbytesp, +JS_WriteStructuredClone(JSContext *cx, HandleValue value, uint64_t **bufp, size_t *nbytesp, const JSStructuredCloneCallbacks *optionalCallbacks, - void *closure, JS::HandleValue transferable) + void *closure, HandleValue transferable) { AssertHeapIsIdle(cx); CHECK_REQUEST(cx); @@ -1626,9 +1656,11 @@ JS_WriteStructuredClone(JSContext *cx, JS::HandleValue value, uint64_t **bufp, s } JS_PUBLIC_API(bool) -JS_ClearStructuredClone(const uint64_t *data, size_t nbytes) +JS_ClearStructuredClone(uint64_t *data, size_t nbytes, + const JSStructuredCloneCallbacks *optionalCallbacks, + void *closure) { - ClearStructuredClone(data, nbytes); + ClearStructuredClone(data, nbytes, optionalCallbacks, closure); return true; } @@ -1645,7 +1677,7 @@ JS_StructuredCloneHasTransferables(const uint64_t *data, size_t nbytes, } JS_PUBLIC_API(bool) -JS_StructuredClone(JSContext *cx, JS::HandleValue value, JS::MutableHandleValue vp, +JS_StructuredClone(JSContext *cx, HandleValue value, MutableHandleValue vp, const JSStructuredCloneCallbacks *optionalCallbacks, void *closure) { @@ -1704,7 +1736,7 @@ void JSAutoStructuredCloneBuffer::clear() { if (data_) { - ClearStructuredClone(data_, nbytes_); + ClearStructuredClone(data_, nbytes_, callbacks_, closure_); data_ = nullptr; nbytes_ = 0; version_ = 0; @@ -1756,7 +1788,7 @@ JSAutoStructuredCloneBuffer::steal(uint64_t **datap, size_t *nbytesp, uint32_t * } bool -JSAutoStructuredCloneBuffer::read(JSContext *cx, JS::MutableHandleValue vp, +JSAutoStructuredCloneBuffer::read(JSContext *cx, MutableHandleValue vp, const JSStructuredCloneCallbacks *optionalCallbacks, void *closure) { @@ -1767,17 +1799,17 @@ JSAutoStructuredCloneBuffer::read(JSContext *cx, JS::MutableHandleValue vp, } bool -JSAutoStructuredCloneBuffer::write(JSContext *cx, JS::HandleValue value, +JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value, const JSStructuredCloneCallbacks *optionalCallbacks, void *closure) { - JS::HandleValue transferable = JS::UndefinedHandleValue; + HandleValue transferable = UndefinedHandleValue; return write(cx, value, transferable, optionalCallbacks, closure); } bool -JSAutoStructuredCloneBuffer::write(JSContext *cx, JS::HandleValue value, - JS::HandleValue transferable, +JSAutoStructuredCloneBuffer::write(JSContext *cx, HandleValue value, + HandleValue transferable, const JSStructuredCloneCallbacks *optionalCallbacks, void *closure) { @@ -1812,7 +1844,7 @@ JS_ReadBytes(JSStructuredCloneReader *r, void *p, size_t len) } JS_PUBLIC_API(bool) -JS_ReadTypedArray(JSStructuredCloneReader *r, JS::MutableHandleValue vp) +JS_ReadTypedArray(JSStructuredCloneReader *r, MutableHandleValue vp) { uint32_t tag, nelems; if (!r->input().readPair(&tag, &nelems)) @@ -1844,7 +1876,7 @@ JS_WriteBytes(JSStructuredCloneWriter *w, const void *p, size_t len) } JS_PUBLIC_API(bool) -JS_WriteTypedArray(JSStructuredCloneWriter *w, JS::HandleValue v) +JS_WriteTypedArray(JSStructuredCloneWriter *w, HandleValue v) { JS_ASSERT(v.isObject()); assertSameCompartment(w->context(), v); @@ -1855,7 +1887,7 @@ JS_WriteTypedArray(JSStructuredCloneWriter *w, JS::HandleValue v) if (obj->is()) obj = CheckedUnwrap(obj); if (!obj) { - JS_ReportError(w->context(), "Permission denied to access object"); + JS_ReportErrorNumber(w->context(), js_GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); return false; } return w->writeTypedArray(obj); diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp index 773e526684ba..fe829b0fd2d0 100644 --- a/js/xpconnect/src/Sandbox.cpp +++ b/js/xpconnect/src/Sandbox.cpp @@ -435,6 +435,9 @@ CloneNonReflectorsWrite(JSContext *cx, JSStructuredCloneWriter *writer, static const JSStructuredCloneCallbacks gForwarderStructuredCloneCallbacks = { CloneNonReflectorsRead, CloneNonReflectorsWrite, + nullptr, + nullptr, + nullptr, nullptr }; diff --git a/layout/base/UnitTransforms.h b/layout/base/UnitTransforms.h index cb8ca9d2b800..8f5c116e894b 100644 --- a/layout/base/UnitTransforms.h +++ b/layout/base/UnitTransforms.h @@ -49,6 +49,14 @@ template gfx::IntSizeTyped ViewAs(const nsIntSize& aSize) { return gfx::IntSizeTyped(aSize.width, aSize.height); } +template +gfx::IntPointTyped ViewAs(const nsIntPoint& aPoint) { + return gfx::IntPointTyped(aPoint.x, aPoint.y); +} +template +gfx::IntRectTyped ViewAs(const nsIntRect& aRect) { + return gfx::IntRectTyped(aRect.x, aRect.y, aRect.width, aRect.height); +} // Convenience functions for casting typed entities to untyped entities. // Using these functions does not require a justification, but once we convert diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 5f550a588d21..98f1e1e8f105 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -642,30 +642,37 @@ CalculateRootCompositionSize(FrameMetrics& aMetrics, } nsIPresShell* rootPresShell = nullptr; if (rootPresContext) { + // See the comments in the code that calculates the root + // composition bounds in RecordFrameMetrics. + // TODO: Reuse that code here. nsIPresShell* rootPresShell = rootPresContext->PresShell(); if (nsIFrame* rootFrame = rootPresShell->GetRootFrame()) { if (nsView* view = rootFrame->GetView()) { - nsIWidget* widget = view->GetWidget(); - #ifdef MOZ_WIDGET_ANDROID - // Android hack - temporary workaround for bug 983208 until we figure - // out what a proper fix is. - if (!widget) { - widget = rootFrame->GetNearestWidget(); - } - #endif + LayoutDeviceToParentLayerScale parentResolution( + rootPresShell->GetCumulativeResolution().width + / rootPresShell->GetResolution().width); + int32_t rootAUPerDevPixel = rootPresContext->AppUnitsPerDevPixel(); + nsRect viewBounds = view->GetBounds(); + LayerSize viewSize = ViewAs( + (LayoutDeviceRect::FromAppUnits(viewBounds, rootAUPerDevPixel) + * parentResolution).Size(), PixelCastJustification::ParentLayerToLayerForRootComposition); + nsIWidget* widget = +#ifdef MOZ_WIDGET_ANDROID + rootFrame->GetNearestWidget(); +#else + view->GetWidget(); +#endif if (widget) { - nsIntRect bounds; - widget->GetBounds(bounds); - rootCompositionSize = LayerSize(ViewAs(bounds.Size())); + nsIntRect widgetBounds; + widget->GetBounds(widgetBounds); + rootCompositionSize = LayerSize(ViewAs(widgetBounds.Size())); +#ifdef MOZ_WIDGET_ANDROID + if (viewSize.height < rootCompositionSize.height) { + rootCompositionSize.height = viewSize.height; + } +#endif } else { - LayoutDeviceToParentLayerScale parentResolution( - rootPresShell->GetCumulativeResolution().width - / rootPresShell->GetResolution().width); - int32_t rootAUPerDevPixel = rootPresContext->AppUnitsPerDevPixel(); - nsRect viewBounds = view->GetBounds(); - rootCompositionSize = ViewAs( - (LayoutDeviceRect::FromAppUnits(viewBounds, rootAUPerDevPixel) - * parentResolution).Size(), PixelCastJustification::ParentLayerToLayerForRootComposition); + rootCompositionSize = viewSize; } } } @@ -793,7 +800,7 @@ static void RecordFrameMetrics(nsIFrame* aForFrame, nsRect compositionBounds(frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aReferenceFrame), frameForCompositionBoundsCalculation->GetSize()); metrics.mCompositionBounds = RoundedToInt(LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel) - * metrics.GetParentResolution()); + * metrics.GetParentResolution()); // For the root scroll frame of the root content document, the above calculation // will yield the size of the viewport frame as the composition bounds, which @@ -808,23 +815,35 @@ static void RecordFrameMetrics(nsIFrame* aForFrame, if (isRootContentDocRootScrollFrame) { if (nsIFrame* rootFrame = presShell->GetRootFrame()) { if (nsView* view = rootFrame->GetView()) { - nsIWidget* widget = view->GetWidget(); + nsRect viewBoundsAppUnits = view->GetBounds() + rootFrame->GetOffsetToCrossDoc(aReferenceFrame); + ParentLayerIntRect viewBounds = RoundedToInt(LayoutDeviceRect::FromAppUnits(viewBoundsAppUnits, auPerDevPixel) + * metrics.GetParentResolution()); + // On Android, we need to do things a bit differently to get things + // right (see bug 983208, bug 988882). We use the bounds of the nearest + // widget, but clamp the height to the view bounds height. This clamping + // is done to get correct results for a page where the page is sized to + // the screen and thus the dynamic toolbar never disappears. In such a + // case, we want the composition bounds to exclude the toolbar height, + // but the widget bounds includes it. We don't currently have a good way + // of knowing about the toolbar height, but clamping to the view bounds + // height gives the correct answer in the cases we care about. + nsIWidget* widget = #ifdef MOZ_WIDGET_ANDROID - // Android hack - temporary workaround for bug 983208 until we figure - // out what a proper fix is. - if (!widget) { - widget = rootFrame->GetNearestWidget(); - } + rootFrame->GetNearestWidget(); +#else + view->GetWidget(); #endif if (widget) { - nsIntRect bounds; - widget->GetBounds(bounds); - metrics.mCompositionBounds = ParentLayerIntRect::FromUnknownRect(mozilla::gfx::IntRect( - bounds.x, bounds.y, bounds.width, bounds.height)); + nsIntRect widgetBounds; + widget->GetBounds(widgetBounds); + metrics.mCompositionBounds = ViewAs(widgetBounds); +#ifdef MOZ_WIDGET_ANDROID + if (viewBounds.height < metrics.mCompositionBounds.height) { + metrics.mCompositionBounds.height = viewBounds.height; + } +#endif } else { - nsRect viewBounds = view->GetBounds() + rootFrame->GetOffsetToCrossDoc(aReferenceFrame); - metrics.mCompositionBounds = RoundedToInt(LayoutDeviceRect::FromAppUnits(viewBounds, auPerDevPixel) - * metrics.GetParentResolution()); + metrics.mCompositionBounds = viewBounds; } } } diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index fdd57616de65..023242faac32 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3676,6 +3676,51 @@ nsLayoutUtils::ComputeHeightDependentValue( return 0; } +/* static */ void +nsLayoutUtils::MarkDescendantsDirty(nsIFrame *aSubtreeRoot) +{ + nsAutoTArray subtrees; + subtrees.AppendElement(aSubtreeRoot); + + // dirty descendants, iterating over subtrees that may include + // additional subtrees associated with placeholders + do { + nsIFrame *subtreeRoot = subtrees.ElementAt(subtrees.Length() - 1); + subtrees.RemoveElementAt(subtrees.Length() - 1); + + // Mark all descendants dirty (using an nsTArray stack rather than + // recursion). + // Note that nsHTMLReflowState::InitResizeFlags has some similar + // code; see comments there for how and why it differs. + nsAutoTArray stack; + stack.AppendElement(subtreeRoot); + + do { + nsIFrame *f = stack.ElementAt(stack.Length() - 1); + stack.RemoveElementAt(stack.Length() - 1); + + f->MarkIntrinsicWidthsDirty(); + + if (f->GetType() == nsGkAtoms::placeholderFrame) { + nsIFrame *oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f); + if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) { + // We have another distinct subtree we need to mark. + subtrees.AppendElement(oof); + } + } + + nsIFrame::ChildListIterator lists(f); + for (; !lists.IsDone(); lists.Next()) { + nsFrameList::Enumerator childFrames(lists.CurrentList()); + for (; !childFrames.AtEnd(); childFrames.Next()) { + nsIFrame* kid = childFrames.get(); + stack.AppendElement(kid); + } + } + } while (stack.Length() != 0); + } while (subtrees.Length() != 0); +} + #define MULDIV(a,b,c) (nscoord(int64_t(a) * int64_t(b) / int64_t(c))) /* static */ nsSize @@ -6125,36 +6170,34 @@ nsLayoutUtils::CalculateCompositionSizeForFrame(nsIFrame* aFrame) nsPresContext* presContext = aFrame->PresContext(); nsIPresShell* presShell = presContext->PresShell(); - // For the root scroll frame of the root content document, the above calculation - // will yield the size of the viewport frame as the composition bounds, which - // doesn't actually correspond to what is visible when - // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible area of - // the prescontext that the viewport frame is reflowed into. In that case if our - // document has a widget then the widget's bounds will correspond to what is - // visible. If we don't have a widget the root view's bounds correspond to what - // would be visible because they don't get modified by setCSSViewport. + // See the comments in the code that calculates the root + // composition bounds in RecordFrameMetrics. + // TODO: Reuse that code here. bool isRootContentDocRootScrollFrame = presContext->IsRootContentDocument() && aFrame == presShell->GetRootScrollFrame(); if (isRootContentDocRootScrollFrame) { if (nsIFrame* rootFrame = presShell->GetRootFrame()) { if (nsView* view = rootFrame->GetView()) { - nsIWidget* widget = view->GetWidget(); + nsSize viewSize = view->GetBounds().Size(); + nsIWidget* widget = #ifdef MOZ_WIDGET_ANDROID - // Android hack - temporary workaround for bug 983208 until we figure - // out what a proper fix is. - if (!widget) { - widget = rootFrame->GetNearestWidget(); - } + rootFrame->GetNearestWidget(); +#else + view->GetWidget(); #endif if (widget) { - nsIntRect bounds; - widget->GetBounds(bounds); + nsIntRect widgetBounds; + widget->GetBounds(widgetBounds); int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel(); - size = nsSize(bounds.width * auPerDevPixel, - bounds.height * auPerDevPixel); + size = nsSize(widgetBounds.width * auPerDevPixel, + widgetBounds.height * auPerDevPixel); +#ifdef MOZ_WIDGET_ANDROID + if (viewSize.height < size.height) { + size.height = viewSize.height; + } +#endif } else { - nsRect viewBounds = view->GetBounds(); - size = nsSize(viewBounds.width, viewBounds.height); + size = viewSize; } } } diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 77b6e9b5dea8..3f4a19582152 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1210,6 +1210,8 @@ public: nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) == 0); } + static void MarkDescendantsDirty(nsIFrame *aSubtreeRoot); + /* * Calculate the used values for 'width' and 'height' for a replaced element. * diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 582ff68a0a43..8664c0678010 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -5268,9 +5268,9 @@ nsresult PresShell::SetResolution(float aXResolution, float aYResolution) gfxSize PresShell::GetCumulativeResolution() { gfxSize resolution = GetResolution(); - nsCOMPtr parent = GetParentPresShell(); - if (parent) { - resolution = resolution * parent->GetCumulativeResolution(); + nsPresContext* parentCtx = GetPresContext()->GetParentPresContext(); + if (parentCtx) { + resolution = resolution * parentCtx->PresShell()->GetCumulativeResolution(); } return resolution; } @@ -6162,13 +6162,13 @@ PresShell::GetRootWindow() // If we don't have DOM window, we're zombie, we should find the root window // with our parent shell. - nsCOMPtr parent = GetParentPresShell(); + nsCOMPtr parent = GetParentPresShellForEventHandling(); NS_ENSURE_TRUE(parent, nullptr); return parent->GetRootWindow(); } already_AddRefed -PresShell::GetParentPresShell() +PresShell::GetParentPresShellForEventHandling() { NS_ENSURE_TRUE(mPresContext, nullptr); @@ -6200,7 +6200,7 @@ PresShell::RetargetEventToParent(WidgetGUIEvent* aEvent, // That way at least the UI key bindings can work. nsCOMPtr kungFuDeathGrip(this); - nsCOMPtr parentPresShell = GetParentPresShell(); + nsCOMPtr parentPresShell = GetParentPresShellForEventHandling(); NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE); // Fake the event as though it's from the parent pres shell's root frame. diff --git a/layout/base/nsPresShell.h b/layout/base/nsPresShell.h index 523dea76e24d..275018bbc45a 100644 --- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -629,7 +629,7 @@ protected: nsIDocument* GetTouchEventTargetDocument(); #endif bool InZombieDocument(nsIContent *aContent); - already_AddRefed GetParentPresShell(); + already_AddRefed GetParentPresShellForEventHandling(); nsIContent* GetCurrentEventContent(); nsIFrame* GetCurrentEventFrame(); nsresult RetargetEventToParent(mozilla::WidgetGUIEvent* aEvent, diff --git a/layout/generic/nsFirstLetterFrame.cpp b/layout/generic/nsFirstLetterFrame.cpp index 59a4afb3022c..ab82391d95d3 100644 --- a/layout/generic/nsFirstLetterFrame.cpp +++ b/layout/generic/nsFirstLetterFrame.cpp @@ -85,6 +85,7 @@ nsFirstLetterFrame::SetInitialChildList(ChildListID aListID, for (nsFrameList::Enumerator e(aChildList); !e.AtEnd(); e.Next()) { NS_ASSERTION(e.get()->GetParent() == this, "Unexpected parent"); restyleManager->ReparentStyleContext(e.get()); + nsLayoutUtils::MarkDescendantsDirty(e.get()); } mFrames.SetFrames(aChildList); @@ -316,6 +317,7 @@ nsFirstLetterFrame::CreateContinuationForFloatingParent(nsPresContext* aPresCont nsRefPtr newSC; newSC = presShell->StyleSet()->ResolveStyleForNonElement(parentSC); continuation->SetStyleContext(newSC); + nsLayoutUtils::MarkDescendantsDirty(continuation); } //XXX Bidi may not be involved but we have to use the list name @@ -369,6 +371,7 @@ nsFirstLetterFrame::DrainOverflowFrames(nsPresContext* aPresContext) mStyleContext; sc = aPresContext->StyleSet()->ResolveStyleForNonElement(parentSC); kid->SetStyleContext(sc); + nsLayoutUtils::MarkDescendantsDirty(kid); } } } diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 61b35f990a12..5d8557e527fc 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -274,9 +274,18 @@ public: nsSize(aCrossSize, aMainSize); } + // Are my axes reversed with respect to what the author asked for? + // (We may reverse the axes in the FlexboxAxisTracker constructor and set + // this flag, to avoid reflowing our children in bottom-to-top order.) + bool AreAxesInternallyReversed() const + { + return mAreAxesInternallyReversed; + } + private: AxisOrientationType mMainAxis; AxisOrientationType mCrossAxis; + bool mAreAxesInternallyReversed; }; /** @@ -331,10 +340,11 @@ public: // Returns the distance between this FlexItem's baseline and the cross-start // edge of its margin-box. Used in baseline alignment. - // (This function needs to be told what the cross axis is so that it can - // look up the appropriate component from mMargin.) - nscoord GetBaselineOffsetFromOuterCrossStart( - AxisOrientationType aCrossAxis) const; + // (This function needs to be told what cross axis is & which edge we're + // measuring the baseline from, so that it can look up the appropriate + // components from mMargin.) + nscoord GetBaselineOffsetFromOuterCrossEdge(AxisOrientationType aCrossAxis, + AxisEdgeType aEdge) const; float GetShareOfFlexWeightSoFar() const { return mShareOfFlexWeightSoFar; } @@ -592,7 +602,7 @@ public: mTotalInnerHypotheticalMainSize(0), mTotalOuterHypotheticalMainSize(0), mLineCrossSize(0), - mBaselineOffsetFromCrossStart(nscoord_MIN) + mBaselineOffset(nscoord_MIN) {} // Returns the sum of our FlexItems' outer hypothetical main sizes. @@ -630,16 +640,20 @@ public: return mNumItems; } - // Adds the given FlexItem to our list of items, and adds its hypothetical + // Adds the given FlexItem to our list of items (at the front or back + // depending on aShouldInsertAtFront), and adds its hypothetical // outer & inner main sizes to our totals. Use this method instead of // directly modifying the item list, so that our bookkeeping remains correct. - // XXXdholbert Bug 983427 will extend this to let the caller choose whether - // the new FlexItem goes at the front or the back of the list. void AddItem(FlexItem* aItem, + bool aShouldInsertAtFront, nscoord aItemInnerHypotheticalMainSize, nscoord aItemOuterHypotheticalMainSize) { - mItems.insertBack(aItem); + if (aShouldInsertAtFront) { + mItems.insertFront(aItem); + } else { + mItems.insertBack(aItem); + } mNumItems++; mTotalInnerHypotheticalMainSize += aItemInnerHypotheticalMainSize; mTotalOuterHypotheticalMainSize += aItemOuterHypotheticalMainSize; @@ -659,11 +673,17 @@ public: mLineCrossSize = aLineCrossSize; } - // Returns the distance from the cross-start edge of this FlexLine - // to its baseline (derived from its baseline-aligned FlexItems). - // If there are no baseline-aligned FlexItems, returns nscoord_MIN. - nscoord GetBaselineOffsetFromCrossStart() const { - return mBaselineOffsetFromCrossStart; + /** + * Returns the offset within this line where any baseline-aligned FlexItems + * should place their baseline. Usually, this represents a distance from the + * line's cross-start edge, but if we're internally reversing the axes (see + * AreAxesInternallyReversed()), this instead represents the distance from + * its cross-end edge. + * + * If there are no baseline-aligned FlexItems, returns nscoord_MIN. + */ + nscoord GetBaselineOffset() const { + return mBaselineOffset; } // Runs the "resolve the flexible lengths" algorithm, distributing @@ -695,7 +715,7 @@ private: nscoord mTotalInnerHypotheticalMainSize; nscoord mTotalOuterHypotheticalMainSize; nscoord mLineCrossSize; - nscoord mBaselineOffsetFromCrossStart; + nscoord mBaselineOffset; }; // Information about a strut left behind by a FlexItem that's been collapsed @@ -1205,8 +1225,8 @@ FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize) } nscoord -FlexItem::GetBaselineOffsetFromOuterCrossStart( - AxisOrientationType aCrossAxis) const +FlexItem::GetBaselineOffsetFromOuterCrossEdge(AxisOrientationType aCrossAxis, + AxisEdgeType aEdge) const { // NOTE: Currently, 'mAscent' (taken from reflow) is an inherently vertical // measurement -- it's the distance from the border-top edge of this FlexItem @@ -1217,18 +1237,23 @@ FlexItem::GetBaselineOffsetFromOuterCrossStart( "Only expecting to be doing baseline computations when the " "cross axis is vertical"); + Side sideToMeasureFrom = kAxisOrientationToSidesMap[aCrossAxis][aEdge]; + nscoord marginTopToBaseline = mAscent + mMargin.top; - if (aCrossAxis == eAxis_TB) { - // Top-to-bottom (normal case): the distance from the cross-start margin-box - // edge (i.e. the margin-top edge) to the baseline is ascent + margin-top. + if (sideToMeasureFrom == eSideTop) { + // Measuring from top (normal case): the distance from the margin-box top + // edge to the baseline is just ascent + margin-top. return marginTopToBaseline; } - // Bottom-to-top: The distance from the cross-start margin-box edge (i.e. the - // margin-bottom edge) to the baseline is just the margin-box cross size - // (i.e. outer cross size), minus the distance from margin-top to baseline - // (already computed above). + MOZ_ASSERT(sideToMeasureFrom == eSideBottom, + "We already checked that we're dealing with a vertical axis, and " + "we're not using the top side, so that only leaves the bottom..."); + + // Measuring from bottom: The distance from the margin-box bottom edge to the + // baseline is just the margin-box cross size (i.e. outer cross size), minus + // the already-computed distance from margin-top to baseline. return GetOuterCrossSize(aCrossAxis) - marginTopToBaseline; } @@ -1392,7 +1417,8 @@ public: FlexItem& aItem); void EnterAlignPackingSpace(const FlexLine& aLine, - const FlexItem& aItem); + const FlexItem& aItem, + const FlexboxAxisTracker& aAxisTracker); // Resets our position to the cross-start edge of this line. inline void ResetPosition() { mPosition = 0; } @@ -1817,6 +1843,16 @@ MainAxisPositionTracker:: } } + // If our main axis is (internally) reversed, swap the justify-content + // "flex-start" and "flex-end" behaviors: + if (aAxisTracker.AreAxesInternallyReversed()) { + if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_START) { + mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_END; + } else if (mJustifyContent == NS_STYLE_JUSTIFY_CONTENT_FLEX_END) { + mJustifyContent = NS_STYLE_JUSTIFY_CONTENT_FLEX_START; + } + } + // Figure out how much space we'll set aside for auto margins or // packing spaces, and advance past any leading packing-space. if (mNumAutoMarginsInMainAxis == 0 && @@ -1975,6 +2011,16 @@ CrossAxisPositionTracker:: } } + // If our cross axis is (internally) reversed, swap the align-content + // "flex-start" and "flex-end" behaviors: + if (aAxisTracker.AreAxesInternallyReversed()) { + if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_START) { + mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_END; + } else if (mAlignContent == NS_STYLE_ALIGN_CONTENT_FLEX_END) { + mAlignContent = NS_STYLE_ALIGN_CONTENT_FLEX_START; + } + } + // Figure out how much space we'll set aside for packing spaces, and advance // past any leading packing-space. if (mPackingSpaceRemaining != 0) { @@ -2116,7 +2162,8 @@ FlexLine::ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker) // crossEndToBaseline. nscoord crossStartToBaseline = - item->GetBaselineOffsetFromOuterCrossStart(aAxisTracker.GetCrossAxis()); + item->GetBaselineOffsetFromOuterCrossEdge(aAxisTracker.GetCrossAxis(), + eAxisEdge_Start); nscoord crossEndToBaseline = curOuterCrossSize - crossStartToBaseline; // Now, update our "largest" values for these (across all the flex items @@ -2131,10 +2178,12 @@ FlexLine::ComputeCrossSizeAndBaseline(const FlexboxAxisTracker& aAxisTracker) } } - // The line's baseline is the distance from the cross-start edge to the - // furthest baseline. (The item(s) with that baseline will be exactly - // aligned with the line's cross-start edge.) - mBaselineOffsetFromCrossStart = crossStartToFurthestBaseline; + // The line's baseline offset is the distance from the line's edge (start or + // end, depending on whether we've flipped the axes) to the furthest + // item-baseline. The item(s) with that baseline will be exactly aligned with + // the line's edge. + mBaselineOffset = aAxisTracker.AreAxesInternallyReversed() ? + crossEndToFurthestBaseline : crossStartToFurthestBaseline; // The line's cross-size is the larger of: // (a) [largest cross-start-to-baseline + largest baseline-to-cross-end] of @@ -2221,7 +2270,8 @@ SingleLineCrossAxisPositionTracker:: void SingleLineCrossAxisPositionTracker:: EnterAlignPackingSpace(const FlexLine& aLine, - const FlexItem& aItem) + const FlexItem& aItem, + const FlexboxAxisTracker& aAxisTracker) { // We don't do align-self alignment on items that have auto margins // in the cross axis. @@ -2229,12 +2279,26 @@ SingleLineCrossAxisPositionTracker:: return; } - switch (aItem.GetAlignSelf()) { + uint8_t alignSelf = aItem.GetAlignSelf(); + // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any + // auto-sized items (which we've already done). + if (alignSelf == NS_STYLE_ALIGN_ITEMS_STRETCH) { + alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + } + + // If our cross axis is (internally) reversed, swap the align-self + // "flex-start" and "flex-end" behaviors: + if (aAxisTracker.AreAxesInternallyReversed()) { + if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_START) { + alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_END; + } else if (alignSelf == NS_STYLE_ALIGN_ITEMS_FLEX_END) { + alignSelf = NS_STYLE_ALIGN_ITEMS_FLEX_START; + } + } + + switch (alignSelf) { case NS_STYLE_ALIGN_ITEMS_FLEX_START: - case NS_STYLE_ALIGN_ITEMS_STRETCH: // No space to skip over -- we're done. - // NOTE: 'stretch' behaves like 'flex-start' once we've stretched any - // auto-sized items (which we've already done). break; case NS_STYLE_ALIGN_ITEMS_FLEX_END: mPosition += aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis); @@ -2245,15 +2309,33 @@ SingleLineCrossAxisPositionTracker:: (aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis)) / 2; break; case NS_STYLE_ALIGN_ITEMS_BASELINE: { - nscoord lineBaselineOffset = - aLine.GetBaselineOffsetFromCrossStart(); + // Normally, baseline-aligned items are collectively aligned with the + // line's cross-start edge; however, if our cross axis is (internally) + // reversed, we instead align them with the cross-end edge. nscoord itemBaselineOffset = - aItem.GetBaselineOffsetFromOuterCrossStart(mAxis); - MOZ_ASSERT(lineBaselineOffset >= itemBaselineOffset, - "failed at finding largest baseline offset"); + aItem.GetBaselineOffsetFromOuterCrossEdge(mAxis, + aAxisTracker.AreAxesInternallyReversed() ? + eAxisEdge_End : eAxisEdge_Start); - // Advance so that aItem's baseline is aligned with the line's baseline. - mPosition += (lineBaselineOffset - itemBaselineOffset); + nscoord lineBaselineOffset = aLine.GetBaselineOffset(); + + NS_ASSERTION(lineBaselineOffset >= itemBaselineOffset, + "failed at finding largest baseline offset"); + + // How much do we need to adjust our position (from the line edge), + // to get the item's baseline to hit the line's baseline offset: + nscoord baselineDiff = lineBaselineOffset - itemBaselineOffset; + + if (aAxisTracker.AreAxesInternallyReversed()) { + // Advance to align item w/ line's flex-end edge (as in FLEX_END case): + mPosition += aLine.GetLineCrossSize() - aItem.GetOuterCrossSize(mAxis); + // ...and step *back* by the baseline adjustment: + mPosition -= baselineDiff; + } else { + // mPosition is already at line's flex-start edge. + // From there, we step *forward* by the baseline adjustment: + mPosition += baselineDiff; + } break; } default: @@ -2262,7 +2344,9 @@ SingleLineCrossAxisPositionTracker:: } } -FlexboxAxisTracker::FlexboxAxisTracker(nsFlexContainerFrame* aFlexContainerFrame) +FlexboxAxisTracker::FlexboxAxisTracker( + nsFlexContainerFrame* aFlexContainerFrame) + : mAreAxesInternallyReversed(false) { const nsStylePosition* pos = aFlexContainerFrame->StylePosition(); uint32_t flexDirection = pos->mFlexDirection; @@ -2324,19 +2408,43 @@ FlexboxAxisTracker::FlexboxAxisTracker(nsFlexContainerFrame* aFlexContainerFrame mCrossAxis = GetReverseAxis(mCrossAxis); } + // Master switch to enable/disable bug 983427's code for reversing our axes + // and reversing some logic, to avoid reflowing children in bottom-to-top + // order. (This switch can be removed eventually, but for now, it allows + // this special-case code path to be compared against the normal code path.) + // + // XXXdholbert Initially, I'm defaulting this switch to *off*, meaning the + // new code isn't enabled yet. A subsequent patch will turn it on, once + // intermediate patches have made us react to AreAxesInternallyReversed() + // correctly in the appropriate places. + static bool sPreventBottomToTopChildOrdering = false; + + if (sPreventBottomToTopChildOrdering) { + // If either axis is bottom-to-top, we flip both axes (and set a flag + // so that we can flip some logic to make the reversal transparent). + if (eAxis_BT == mMainAxis || eAxis_BT == mCrossAxis) { + mMainAxis = GetReverseAxis(mMainAxis); + mCrossAxis = GetReverseAxis(mCrossAxis); + mAreAxesInternallyReversed = true; + } + } + MOZ_ASSERT(IsAxisHorizontal(mMainAxis) != IsAxisHorizontal(mCrossAxis), "main & cross axes should be in different dimensions"); } -// Allocates a new FlexLine, adds it at the back of the given LinkedList, -// and returs a pointer to it. -// XXXdholbert Bug 983427 will extend this to let the caller choose whether -// the new FlexLine goes at the front or the back of the list. +// Allocates a new FlexLine, adds it to the given LinkedList (at the front or +// back depending on aShouldInsertAtFront), and returns a pointer to it. static FlexLine* -AddNewFlexLineToList(LinkedList& aLines) +AddNewFlexLineToList(LinkedList& aLines, + bool aShouldInsertAtFront) { FlexLine* newLine = new FlexLine(); - aLines.insertBack(newLine); + if (aShouldInsertAtFront) { + aLines.insertFront(newLine); + } else { + aLines.insertBack(newLine); + } return newLine; } @@ -2355,9 +2463,17 @@ nsFlexContainerFrame::GenerateFlexLines( const bool isSingleLine = NS_STYLE_FLEX_WRAP_NOWRAP == aReflowState.mStylePosition->mFlexWrap; + // If we're transparently reversing axes, then we'll need to link up our + // FlexItems and FlexLines in the reverse order, so that the rest of flex + // layout (with flipped axes) will still produce the correct result. + // Here, we declare a convenience bool that we'll pass when adding a new + // FlexLine or FlexItem, to make us insert it at the beginning of its list + // (so the list ends up reversed). + const bool shouldInsertAtFront = aAxisTracker.AreAxesInternallyReversed(); + // We have at least one FlexLine. Even an empty flex container has a single // (empty) flex line. - FlexLine* curLine = AddNewFlexLineToList(aLines); + FlexLine* curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); nscoord wrapThreshold; if (isSingleLine) { @@ -2401,7 +2517,7 @@ nsFlexContainerFrame::GenerateFlexLines( // Honor "page-break-before", if we're multi-line and this line isn't empty: if (!isSingleLine && !curLine->IsEmpty() && childFrame->StyleDisplay()->mBreakBefore) { - curLine = AddNewFlexLineToList(aLines); + curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); } nsAutoPtr item; @@ -2431,19 +2547,19 @@ nsFlexContainerFrame::GenerateFlexLines( !curLine->IsEmpty() && // No need to wrap at start of a line. wrapThreshold < (curLine->GetTotalOuterHypotheticalMainSize() + itemOuterHypotheticalMainSize)) { - curLine = AddNewFlexLineToList(aLines); + curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); } // Add item to current flex line (and update the line's bookkeeping about // how large its items collectively are). - curLine->AddItem(item.forget(), + curLine->AddItem(item.forget(), shouldInsertAtFront, itemInnerHypotheticalMainSize, itemOuterHypotheticalMainSize); // Honor "page-break-after", if we're multi-line and have more children: if (!isSingleLine && childFrame->GetNextSibling() && childFrame->StyleDisplay()->mBreakAfter) { - curLine = AddNewFlexLineToList(aLines); + curLine = AddNewFlexLineToList(aLines, shouldInsertAtFront); } itemIdxInContainer++; } @@ -2636,6 +2752,23 @@ ResolveReflowedChildAscent(nsIFrame* aFrame, } } +/** + * Given the flex container's "logical ascent" (i.e. distance from the + * flex container's content-box cross-start edge to its baseline), returns + * its actual physical ascent value (the distance from the *border-box* top + * edge to its baseline). + */ +static nscoord +ComputePhysicalAscentFromLogicalAscent(nscoord aLogicalAscent, + nscoord aContentBoxCrossSize, + const nsHTMLReflowState& aReflowState, + const FlexboxAxisTracker& aAxisTracker) +{ + return aReflowState.ComputedPhysicalBorderPadding().top + + PhysicalPosFromLogicalPos(aLogicalAscent, aContentBoxCrossSize, + aAxisTracker.GetCrossAxis()); +} + nsresult nsFlexContainerFrame::SizeItemInCrossAxis( nsPresContext* aPresContext, @@ -2741,7 +2874,7 @@ FlexLine::PositionItemsInCrossAxis(nscoord aLineStartPosition, nscoord itemCrossBorderBoxSize = item->GetCrossSize() + item->GetBorderPaddingSizeInAxis(aAxisTracker.GetCrossAxis()); - lineCrossAxisPosnTracker.EnterAlignPackingSpace(*this, *item); + lineCrossAxisPosnTracker.EnterAlignPackingSpace(*this, *item, aAxisTracker); lineCrossAxisPosnTracker.EnterMargin(item->GetMargin()); lineCrossAxisPosnTracker.EnterChildFrame(itemCrossBorderBoxSize); @@ -2949,29 +3082,23 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, } } - // Set the flex container's baseline, from the baseline-alignment position - // of the first line's baseline-aligned items. + // If the container should derive its baseline from the first FlexLine, + // do that here (while crossAxisPosnTracker is conveniently pointing + // at the cross-start edge of that line, which the line's baseline offset is + // measured from): nscoord flexContainerAscent; - nscoord firstLineBaselineOffset = - lines.getFirst()->GetBaselineOffsetFromCrossStart(); - if (firstLineBaselineOffset == nscoord_MIN) { - // No baseline-aligned flex items in first line --> just use a sentinel - // value for now, and we'll update it during final reflow. - flexContainerAscent = nscoord_MIN; - } else { - // Add the position of the first line to that line's baseline-alignment - // offset, to get the baseline offset with respect to the *container's* - // cross-start edge. - nscoord firstLineBaselineOffsetWRTContainer = - firstLineBaselineOffset + crossAxisPosnTracker.GetPosition(); - - // The container's ascent is that ^ offset, converted out of logical coords - // (into distance from top of content-box), plus the top border/padding - // (since ascent is measured with respect to the top of the border-box). - flexContainerAscent = aReflowState.ComputedPhysicalBorderPadding().top + - PhysicalPosFromLogicalPos(firstLineBaselineOffsetWRTContainer, - contentBoxCrossSize, - aAxisTracker.GetCrossAxis()); + if (!aAxisTracker.AreAxesInternallyReversed()) { + nscoord firstLineBaselineOffset = lines.getFirst()->GetBaselineOffset(); + if (firstLineBaselineOffset == nscoord_MIN) { + // No baseline-aligned items in line. Use sentinel value to prompt us to + // get baseline from the first FlexItem after we've reflowed it. + flexContainerAscent = nscoord_MIN; + } else { + flexContainerAscent = + ComputePhysicalAscentFromLogicalAscent( + crossAxisPosnTracker.GetPosition() + firstLineBaselineOffset, + contentBoxCrossSize, aReflowState, aAxisTracker); + } } for (FlexLine* line = lines.getFirst(); line; line = line->getNext()) { @@ -2990,6 +3117,24 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, crossAxisPosnTracker.TraversePackingSpace(); } + // If the container should derive its baseline from the last FlexLine, + // do that here (while crossAxisPosnTracker is conveniently pointing + // at the cross-end edge of that line, which the line's baseline offset is + // measured from): + if (aAxisTracker.AreAxesInternallyReversed()) { + nscoord lastLineBaselineOffset = lines.getLast()->GetBaselineOffset(); + if (lastLineBaselineOffset == nscoord_MIN) { + // No baseline-aligned items in line. Use sentinel value to prompt us to + // get baseline from the last FlexItem after we've reflowed it. + flexContainerAscent = nscoord_MIN; + } else { + flexContainerAscent = + ComputePhysicalAscentFromLogicalAscent( + crossAxisPosnTracker.GetPosition() - lastLineBaselineOffset, + contentBoxCrossSize, aReflowState, aAxisTracker); + } + } + // Before giving each child a final reflow, calculate the origin of the // flex container's content box (with respect to its border-box), so that // we can compute our flex item's final positions. diff --git a/layout/generic/nsInlineFrame.cpp b/layout/generic/nsInlineFrame.cpp index d344871c9326..526041e63646 100644 --- a/layout/generic/nsInlineFrame.cpp +++ b/layout/generic/nsInlineFrame.cpp @@ -286,6 +286,7 @@ ReparentChildListStyle(nsPresContext* aPresContext, for (nsFrameList::Enumerator e(aFrames); !e.AtEnd(); e.Next()) { NS_ASSERTION(e.get()->GetParent() == aParentFrame, "Bogus parentage"); restyleManager->ReparentStyleContext(e.get()); + nsLayoutUtils::MarkDescendantsDirty(e.get()); } } @@ -424,6 +425,7 @@ nsInlineFrame::DrainSelfOverflowListInternal(DrainFlags aFlags, f->SetParent(this); if (inFirstLine) { restyleManager->ReparentStyleContext(f); + nsLayoutUtils::MarkDescendantsDirty(f); } } } @@ -529,6 +531,7 @@ nsInlineFrame::ReflowFrames(nsPresContext* aPresContext, child->SetParent(this); if (inFirstLine) { restyleManager->ReparentStyleContext(child); + nsLayoutUtils::MarkDescendantsDirty(child); } // We also need to do the same for |frame|'s next-in-flows that are in // the sibling list. Otherwise, if we reflow |frame| and it's complete @@ -563,6 +566,7 @@ nsInlineFrame::ReflowFrames(nsPresContext* aPresContext, nextInFlow->SetParent(this); if (inFirstLine) { restyleManager->ReparentStyleContext(nextInFlow); + nsLayoutUtils::MarkDescendantsDirty(nextInFlow); } } else { @@ -1005,6 +1009,7 @@ nsFirstLineFrame::PullOneFrame(nsPresContext* aPresContext, InlineReflowState& i // style-context that we just pulled. NS_ASSERTION(frame->GetParent() == this, "Incorrect parent?"); aPresContext->RestyleManager()->ReparentStyleContext(frame); + nsLayoutUtils::MarkDescendantsDirty(frame); } return frame; } diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index db056162c54d..e4b85009af8f 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -4441,7 +4441,6 @@ nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) { nsFrame::DidSetStyleContext(aOldStyleContext); - ClearTextRuns(); } class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry diff --git a/layout/reftests/first-letter/font-text-styles-floater-ref.html b/layout/reftests/first-letter/font-text-styles-floater-ref.html new file mode 100644 index 000000000000..337aabeb992d --- /dev/null +++ b/layout/reftests/first-letter/font-text-styles-floater-ref.html @@ -0,0 +1,28 @@ + + + +Testing first-letter handling of font/text styles + + + + + + +
+

Gonzo

+
+ + diff --git a/layout/reftests/first-letter/font-text-styles-floater.html b/layout/reftests/first-letter/font-text-styles-floater.html new file mode 100644 index 000000000000..b68a5579c8da --- /dev/null +++ b/layout/reftests/first-letter/font-text-styles-floater.html @@ -0,0 +1,27 @@ + + + +Testing first-letter handling of font/text styles + + + + + + +
+

gonzo

+
+ + diff --git a/layout/reftests/first-letter/font-text-styles-ref.html b/layout/reftests/first-letter/font-text-styles-ref.html new file mode 100644 index 000000000000..a9e400d1b632 --- /dev/null +++ b/layout/reftests/first-letter/font-text-styles-ref.html @@ -0,0 +1,33 @@ + + + +Testing first-letter handling of font/text styles + + + + + + +
+

Yellow corndog

+

hIPPIE BANANA

+

Gonzo macadamia

+

fELICITOUS Raspberry

+
+ + diff --git a/layout/reftests/first-letter/font-text-styles.html b/layout/reftests/first-letter/font-text-styles.html new file mode 100644 index 000000000000..e117a6bbf603 --- /dev/null +++ b/layout/reftests/first-letter/font-text-styles.html @@ -0,0 +1,35 @@ + + + +Testing first-letter handling of font/text styles + + + + + + +
+

yellow corndog

+

HIPPIE BANANA

+

gonzo macadamia

+

FELICITOUS raspberry

+
+ + diff --git a/layout/reftests/first-letter/reftest.list b/layout/reftests/first-letter/reftest.list index b510dfd736fe..020f020314a5 100644 --- a/layout/reftests/first-letter/reftest.list +++ b/layout/reftests/first-letter/reftest.list @@ -63,3 +63,5 @@ fails-if(winWidget||cocoaWidget) == 617869-1.html 617869-1-ref.html == 723509-1.html 723509-1-ref.html == 922550-1.html 922550-1-ref.html == 958249.html 958249-ref.html +== font-text-styles.html font-text-styles-ref.html +fails-if(gtk2Widget) random-if(winWidget&&!d2d) == font-text-styles-floater.html font-text-styles-floater-ref.html # bug 992846 diff --git a/layout/reftests/first-line/font-styles-nooverflow.html b/layout/reftests/first-line/font-styles-nooverflow.html new file mode 100644 index 000000000000..5cf63f1b5d35 --- /dev/null +++ b/layout/reftests/first-line/font-styles-nooverflow.html @@ -0,0 +1,29 @@ + + + +Simple first-line test + + + + + +
sans-serif
serif bold serif italic
just italicitalic too
+ diff --git a/layout/reftests/first-line/font-styles-ref.html b/layout/reftests/first-line/font-styles-ref.html new file mode 100644 index 000000000000..d68b28a1d054 --- /dev/null +++ b/layout/reftests/first-line/font-styles-ref.html @@ -0,0 +1,29 @@ + + + +Simple first-line test + + + + + +
sans-serif
serif bold serif italic
just italicitalic too
+ \ No newline at end of file diff --git a/layout/reftests/first-line/font-styles.html b/layout/reftests/first-line/font-styles.html new file mode 100644 index 000000000000..a330f54e7222 --- /dev/null +++ b/layout/reftests/first-line/font-styles.html @@ -0,0 +1,29 @@ + + + +Simple first-line test + + + + + +
sans-serif
serif bold serif italic
just italicitalic too
+ \ No newline at end of file diff --git a/layout/reftests/first-line/reftest.list b/layout/reftests/first-line/reftest.list index bca4ee8dd108..9e0cca7081d6 100644 --- a/layout/reftests/first-line/reftest.list +++ b/layout/reftests/first-line/reftest.list @@ -33,3 +33,5 @@ load stress-10.html # crash test == 469227-3.html 469227-3-ref.html == restyle-inside-first-line.html restyle-inside-first-line-ref.html +== font-styles.html font-styles-ref.html +== font-styles-nooverflow.html font-styles-ref.html diff --git a/layout/reftests/text-transform/capitalize-7a.html b/layout/reftests/text-transform/capitalize-7a.html new file mode 100644 index 000000000000..06360354cfa1 --- /dev/null +++ b/layout/reftests/text-transform/capitalize-7a.html @@ -0,0 +1,11 @@ + + + + +

blah blah

+ + diff --git a/layout/reftests/text-transform/reftest.list b/layout/reftests/text-transform/reftest.list index 8a55f5b19bb6..8205abc4ba07 100644 --- a/layout/reftests/text-transform/reftest.list +++ b/layout/reftests/text-transform/reftest.list @@ -5,6 +5,7 @@ == capitalize-5.html capitalize-5-ref.html == capitalize-6.html capitalize-6-ref.html == capitalize-7.html capitalize-7-ref.html +== capitalize-7a.html capitalize-7-ref.html == lowercase-1.html lowercase-ref.html == lowercase-sigma-1.html lowercase-sigma-1-ref.html == small-caps-1.html small-caps-1-ref.html diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp index 06c76093c0c3..a878b41ea1e5 100644 --- a/layout/svg/SVGTextFrame.cpp +++ b/layout/svg/SVGTextFrame.cpp @@ -3312,7 +3312,7 @@ SVGTextFrame::ScheduleReflowSVGNonDisplayText() MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); PresContext()->PresShell()->FrameNeedsReflow( - f, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN); + f, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); } NS_IMPL_ISUPPORTS1(SVGTextFrame::MutationObserver, nsIMutationObserver) diff --git a/netwerk/ipc/RemoteOpenFileChild.cpp b/netwerk/ipc/RemoteOpenFileChild.cpp index 748406baa5a2..c15c9550a2f3 100644 --- a/netwerk/ipc/RemoteOpenFileChild.cpp +++ b/netwerk/ipc/RemoteOpenFileChild.cpp @@ -4,8 +4,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "mozilla/net/RemoteOpenFileChild.h" +#include "RemoteOpenFileChild.h" +#include "mozilla/unused.h" #include "mozilla/ipc/FileDescriptor.h" #include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/ipc/URIUtils.h" @@ -13,6 +14,7 @@ #include "nsThreadUtils.h" #include "nsJARProtocolHandler.h" #include "nsIRemoteOpenFileListener.h" +#include "nsProxyRelease.h" // needed to alloc/free NSPR file descriptors #include "private/pprio.h" @@ -83,8 +85,40 @@ RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other) RemoteOpenFileChild::~RemoteOpenFileChild() { - if (mListener) { - NotifyListener(NS_ERROR_UNEXPECTED); + if (NS_IsMainThread()) { + if (mListener) { + NotifyListener(NS_ERROR_UNEXPECTED); + } + } else { + nsCOMPtr mainThread = do_GetMainThread(); + if (mainThread) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProxyRelease(mainThread, mURI, true))); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProxyRelease(mainThread, mAppURI, true))); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProxyRelease(mainThread, mListener, + true))); + + TabChild* tabChild; + mTabChild.forget(&tabChild); + + if (tabChild) { + nsCOMPtr runnable = + NS_NewNonOwningRunnableMethod(tabChild, &TabChild::Release); + MOZ_ASSERT(runnable); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mainThread->Dispatch(runnable, + NS_DISPATCH_NORMAL))); + } + } else { + using mozilla::unused; + + NS_WARNING("RemoteOpenFileChild released after thread shutdown, leaking " + "its members!"); + + unused << mURI.forget(); + unused << mAppURI.forget(); + unused << mListener.forget(); + unused << mTabChild.forget(); + } } if (mNSPRFileDesc) { diff --git a/testing/marionette/client/marionette/tests/unit/test_window_management.py b/testing/marionette/client/marionette/tests/unit/test_window_management.py index 6ab6773297d4..9b529209d9aa 100644 --- a/testing/marionette/client/marionette/tests/unit/test_window_management.py +++ b/testing/marionette/client/marionette/tests/unit/test_window_management.py @@ -11,10 +11,10 @@ class TestSwitchWindow(MarionetteTestCase): self.marionette.set_script_timeout(5000) self.marionette.execute_async_script(""" var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] - .getService(Components.interfaces.nsIWindowWatcher); + .getService(Components.interfaces.nsIWindowWatcher); var win = ww.openWindow(null, "chrome://browser/content/browser.xul", "testWin", null, null); -win.addEventListener("load", function() { - win.removeEventListener("load", arguments.callee, true); +win.addEventListener("load", function() { + win.removeEventListener("load", arguments.callee, true); marionetteScriptFinished(); }, null); """) @@ -24,7 +24,7 @@ win.addEventListener("load", function() { self.marionette.set_context("chrome") self.marionette.execute_script(""" var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] - .getService(Components.interfaces.nsIWindowWatcher); + .getService(Components.interfaces.nsIWindowWatcher); var win = ww.getWindowByName("testWin", null); if (win != null) win.close(); @@ -39,7 +39,7 @@ if (win != null) self.assertEqual(self.marionette.current_window_handle, orig_win) now_available = self.marionette.window_handles #assert we can find the new window - self.assertEqual(len(now_available), len(orig_available) + 1) + self.assertEqual(len(now_available), len(orig_available) + 1) #assert that our window is there self.assertTrue(orig_win in now_available) new_win = None @@ -59,16 +59,16 @@ if (win != null) test_html = self.marionette.absolute_url("test_windows.html") self.marionette.navigate(test_html) current = self.marionette.current_window_handle - + self.marionette.find_element('link text',"Open new window").click() window_handles = self.marionette.window_handles window_handles.remove(current) - + self.marionette.switch_to_window(window_handles[0]) self.assertEqual(self.marionette.title, "We Arrive Here") - + handle = self.marionette.current_window_handle - + self.assertEqual(self.marionette.current_window_handle, handle) self.assertEqual(2, len(self.marionette.window_handles)) diff --git a/testing/marionette/client/marionette/tests/unit/unit-tests.ini b/testing/marionette/client/marionette/tests/unit/unit-tests.ini index 19a33a1f8aba..a6a8b25c1061 100644 --- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini +++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini @@ -28,6 +28,9 @@ b2g = false [test_getattr.py] [test_getattr_chrome.py] b2g = false +[test_elementsize.py] +[test_position.py] +[test_rendered_element.py] [test_elementState.py] [test_elementState_chrome.py] b2g = false @@ -35,6 +38,10 @@ b2g = false [test_text_chrome.py] disabled = "Bug 896046" +[test_clearing.py] +[test_typing.py] +disabled = "Bug 123456" + [test_log.py] [test_emulator.py] browser = false @@ -77,12 +84,15 @@ browser = false [test_specialpowers.py] [test_switch_frame.py] [test_switch_frame_chrome.py] - - [test_switch_remote_frame.py] -[test_visibility.py] +[test_pagesource.py] + +[test_visibility.py] +[test_window_switching.py] +disabled = "Bug 990298" [test_window_management.py] +disabled = "Bug 990299" b2g = false [test_appcache.py] @@ -103,4 +113,6 @@ disabled = "Bug 925688" browser = false [test_errors.py] -[include:oop/manifest.ini] +[test_execute_isolate.py] + +[include:oop/manifest.ini] \ No newline at end of file diff --git a/testing/marionette/client/marionette/www/rectangles.html b/testing/marionette/client/marionette/www/rectangles.html index 56c6191cdcb7..59871c6de6a8 100644 --- a/testing/marionette/client/marionette/www/rectangles.html +++ b/testing/marionette/client/marionette/www/rectangles.html @@ -18,8 +18,8 @@ } #r2 { background-color: red; - left: 10.9px; - top: 10.1px; + left: 11px; + top: 10px; width: 48.666666667px; height: 49.333333333px; } diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp index d9eb50ac6dba..6c7d32ca3fac 100644 --- a/widget/windows/WinUtils.cpp +++ b/widget/windows/WinUtils.cpp @@ -770,12 +770,15 @@ AsyncFaviconDataReady::OnComplete(nsIURI *aFaviconURI, dataSurface->Unmap(); } else { - size.width = GetSystemMetrics(SM_CXSMICON); - size.height = GetSystemMetrics(SM_CYSMICON); - if (!size.width || !size.height) { - size.width = 16; - size.height = 16; - } + // By using the input image surface's size, we may end up encoding + // to a different size than a 16x16 (or bigger for higher DPI) ICO, but + // Windows will resize appropriately for us. If we want to encode ourselves + // one day because we like our resizing better, we'd have to manually + // resize the image here and use GetSystemMetrics w/ SM_CXSMICON and + // SM_CYSMICON. We don't support resizing images asynchronously at the + // moment anyway so getting the DPI aware icon size won't help. + size.width = surface->GetSize().width; + size.height = surface->GetSize().height; dataSurface = surface->GetDataSurface(); } @@ -823,11 +826,6 @@ NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() { NS_PRECONDITION(!NS_IsMainThread(), "Should not be called on the main thread."); - // Get the recommended icon width and height, or if failure to obtain - // these settings, fall back to 16x16 ICOs. These values can be different - // if the user has a different DPI setting other than 100%. - // Windows would scale the 16x16 icon themselves, but it's better - // we let our ICO encoder do it. nsCOMPtr iconStream; nsRefPtr encoder = do_CreateInstance("@mozilla.org/image/encoder;2?"