import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1213818 - Align document.title for SVG documents with HTML spec; r=bz (fb60e8c048)
- Bug 1234170 - WebSocket should check if the channel has been opened before send the 'close' notification to the WebSocketEventService, r=jduell (4bfd6f3f3f)
- Bug 1245261 - Use an atomic to safely access gcTriggerBytes; r=jonco (f9c80d47e1)
- Bug 1243001 part 1. Remove the dead WrappedWorkerRunnable class from Promise code. r=peterv (9f8c758723)
- Bug 1243001 part 2. Make Promise an empty [NoInterfaceObject] interface when SPIDERMONKEY_PROMISE is defined. r=peterv (6be034ee59)
- Bug 1243001 part 3. Turn off the IDL bits of PromiseDebugging when SPIDERMONKEY_PROMISE is defined. r=peterv (114241ddd6)
- Bug 1243001 part 4. Switch to using MaybeResolve/MaybeReject instead of ResolveInternal/RejectInternal for PromiseWorkerProxy. r=peterv (ca8faf02f8)
- Bug 1243114 - Convert PromiseCapability::mPromise to a rooted JSObject* instead of a rooted JS::Value. r=bz (e4c907e5c2)
- Bug 1243001 part 5. Get rid of most of the dom::Promise methods when SPIDERMONKEY_PROMISE is defined, and reimplement the rest in terms of SpiderMonkey Promise. r=peterv (693a61e5a2)
- Bug 1242054. Get rid of AbortablePromise, so we can move Promise into SpiderMonkey more easily. r=khuey (6a0200e625)
- Bug 1243001 part 6. Implement Promise::AppendNativeHandler in the SPIDERMONKEY_PROMISE world. r=peterv (7c3a6390f9)
- Bug 1243001 part 7. Stop wrappercaching dom::Promise when SPIDERMONKEY_PROMISE is defined. r=peterv (be1bd9b33f)
- Bug 1243001 part 8. Tell SpiderMonkey to put its promise jobs into the CycleCollectedJSRuntime job queue. r=peterv (192e6a551c)
- Bug 1156880 - Null check the prescontext in nsDOMWindowUtils::AdvanceTimeAndRefresh; r=mstange (11f1a39f22)
- Bug 1191597 part 1 - Add head.js and dummy page for browser chrome test. r=smaug (5257870dd3)
- Bug 1191597 part 2 - Convert fullscreen-esc-context-menu to a browser chrome test. r=smaug (e1c0fe84a4)
This commit is contained in:
2023-11-08 17:17:49 +08:00
parent 0ccb78c213
commit 3e34b8d21b
48 changed files with 1118 additions and 620 deletions
+1 -1
View File
@@ -232,7 +232,7 @@ DOMRequest::Then(JSContext* aCx, AnyCallback* aResolveCallback,
}
// Just use the global of the Promise itself as the callee global.
JS::Rooted<JSObject*> global(aCx, mPromise->GetWrapper());
JS::Rooted<JSObject*> global(aCx, mPromise->PromiseObj());
global = js::GetGlobalForObjectCrossCompartment(global);
mPromise->Then(aCx, global, aResolveCallback, aRejectCallback, aRetval, aRv);
}
+5 -3
View File
@@ -1912,9 +1912,11 @@ WebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
MOZ_ASSERT(mImpl);
AssertIsOnTargetThread();
mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
mImpl->mInnerWindowID,
aWasClean, aCode, aReason);
if (mImpl->mChannel) {
mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(),
mImpl->mInnerWindowID,
aWasClean, aCode, aReason);
}
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
+10 -7
View File
@@ -2285,7 +2285,7 @@ nsDOMWindowUtils::GetSupportsHardwareH264Decoding(JS::MutableHandle<JS::Value> a
RefPtr<Promise> promise =
MP4Decoder::IsVideoAccelerated(mgr->GetCompositorBackendType(), parentObject);
NS_ENSURE_STATE(promise);
aPromise.setObject(*promise->GetWrapper());
aPromise.setObject(*promise->PromiseObj());
#else
ErrorResult rv;
RefPtr<Promise> promise = Promise::Create(parentObject, rv);
@@ -2293,7 +2293,7 @@ nsDOMWindowUtils::GetSupportsHardwareH264Decoding(JS::MutableHandle<JS::Value> a
return rv.StealNSResult();
}
promise.MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support."));
aPromise.setObject(*promise->GetWrapper());
aPromise.setObject(*promise->PromiseObj());
#endif
return NS_OK;
}
@@ -2416,12 +2416,15 @@ nsDOMWindowUtils::AdvanceTimeAndRefresh(int64_t aMilliseconds)
}
}
nsRefreshDriver* driver = GetPresContext()->RefreshDriver();
driver->AdvanceTimeAndRefresh(aMilliseconds);
nsPresContext* presContext = GetPresContext();
if (presContext) {
nsRefreshDriver* driver = presContext->RefreshDriver();
driver->AdvanceTimeAndRefresh(aMilliseconds);
RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
if (transaction && transaction->IPCOpen()) {
transaction->SendSetTestSampleTime(driver->MostRecentRefresh());
RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
if (transaction && transaction->IPCOpen()) {
transaction->SendSetTestSampleTime(driver->MostRecentRefresh());
}
}
return NS_OK;
+68 -48
View File
@@ -7010,8 +7010,8 @@ nsIDocument::GetHtmlChildElement(nsIAtom* aTag)
return nullptr;
}
nsIContent*
nsDocument::GetTitleContent(uint32_t aNamespace)
Element*
nsDocument::GetTitleElement()
{
// mMayHaveTitleElement will have been set to true if any HTML or SVG
// <title> element has been bound to this document. So if it's false,
@@ -7021,19 +7021,26 @@ nsDocument::GetTitleContent(uint32_t aNamespace)
if (!mMayHaveTitleElement)
return nullptr;
Element* root = GetRootElement();
if (root && root->IsSVGElement(nsGkAtoms::svg)) {
// In SVG, the document's title must be a child
for (nsIContent* child = root->GetFirstChild();
child; child = child->GetNextSibling()) {
if (child->IsSVGElement(nsGkAtoms::title)) {
return child->AsElement();
}
}
return nullptr;
}
// We check the HTML namespace even for non-HTML documents, except SVG. This
// matches the spec and the behavior of all tested browsers.
RefPtr<nsContentList> list =
NS_GetContentList(this, aNamespace, NS_LITERAL_STRING("title"));
NS_GetContentList(this, kNameSpaceID_XHTML, NS_LITERAL_STRING("title"));
return list->Item(0, false);
}
nsIContent* first = list->Item(0, false);
void
nsDocument::GetTitleFromElement(uint32_t aNamespace, nsAString& aTitle)
{
nsIContent* title = GetTitleContent(aNamespace);
if (!title)
return;
nsContentUtils::GetNodeTextContent(title, false, aTitle);
return first ? first->AsElement() : nullptr;
}
NS_IMETHODIMP
@@ -7050,26 +7057,24 @@ nsDocument::GetTitle(nsString& aTitle)
{
aTitle.Truncate();
nsIContent *rootElement = GetRootElement();
if (!rootElement)
Element* rootElement = GetRootElement();
if (!rootElement) {
return;
}
nsAutoString tmp;
switch (rootElement->GetNameSpaceID()) {
#ifdef MOZ_XUL
case kNameSpaceID_XUL:
rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
break;
if (rootElement->IsXULElement()) {
rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, tmp);
} else
#endif
case kNameSpaceID_SVG:
if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
GetTitleFromElement(kNameSpaceID_SVG, tmp);
break;
} // else fall through
default:
GetTitleFromElement(kNameSpaceID_XHTML, tmp);
break;
{
Element* title = GetTitleElement();
if (!title) {
return;
}
nsContentUtils::GetNodeTextContent(title, false, tmp);
}
tmp.CompressWhitespace();
@@ -7079,41 +7084,56 @@ nsDocument::GetTitle(nsString& aTitle)
NS_IMETHODIMP
nsDocument::SetTitle(const nsAString& aTitle)
{
Element *rootElement = GetRootElement();
if (!rootElement)
Element* rootElement = GetRootElement();
if (!rootElement) {
return NS_OK;
switch (rootElement->GetNameSpaceID()) {
case kNameSpaceID_SVG:
return NS_OK; // SVG doesn't support setting a title
#ifdef MOZ_XUL
case kNameSpaceID_XUL:
return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title,
aTitle, true);
#endif
}
#ifdef MOZ_XUL
if (rootElement->IsXULElement()) {
return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::title,
aTitle, true);
}
#endif
// Batch updates so that mutation events don't change "the title
// element" under us
mozAutoDocUpdate updateBatch(this, UPDATE_CONTENT_MODEL, true);
nsIContent* title = GetTitleContent(kNameSpaceID_XHTML);
if (!title) {
Element *head = GetHeadElement();
if (!head)
return NS_OK;
nsCOMPtr<Element> title = GetTitleElement();
if (rootElement->IsSVGElement(nsGkAtoms::svg)) {
if (!title) {
RefPtr<mozilla::dom::NodeInfo> titleInfo =
mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr,
kNameSpaceID_SVG,
nsIDOMNode::ELEMENT_NODE);
NS_NewSVGElement(getter_AddRefs(title), titleInfo.forget(),
NOT_FROM_PARSER);
if (!title) {
return NS_OK;
}
rootElement->InsertChildAt(title, 0, true);
}
} else if (rootElement->IsHTMLElement()) {
if (!title) {
Element* head = GetHeadElement();
if (!head) {
return NS_OK;
}
{
RefPtr<mozilla::dom::NodeInfo> titleInfo;
titleInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::title, nullptr,
kNameSpaceID_XHTML,
nsIDOMNode::ELEMENT_NODE);
kNameSpaceID_XHTML,
nsIDOMNode::ELEMENT_NODE);
title = NS_NewHTMLTitleElement(titleInfo.forget());
if (!title)
if (!title) {
return NS_OK;
}
}
head->AppendChildTo(title, true);
head->AppendChildTo(title, true);
}
} else {
return NS_OK;
}
return nsContentUtils::SetNodeTextContent(title, aTitle, false);
+9 -7
View File
@@ -1447,13 +1447,15 @@ protected:
nsIContent* GetFirstBaseNodeWithHref();
nsresult SetFirstBaseNodeWithHref(nsIContent *node);
// Get the first <title> element with the given IsNodeOfType type, or
// return null if there isn't one
nsIContent* GetTitleContent(uint32_t aNodeType);
// Find the first "title" element in the given IsNodeOfType type and
// append the concatenation of its text node children to aTitle. Do
// nothing if there is no such element.
void GetTitleFromElement(uint32_t aNodeType, nsAString& aTitle);
/**
* Returns the title element of the document as defined by the HTML
* specification, or null if there isn't one. For documents whose root
* element is an <svg:svg>, this is the first <svg:title> element that's a
* child of the root. For other documents, it's the first HTML title element
* in the document.
*/
Element* GetTitleElement();
public:
// Get our title
virtual void GetTitle(nsString& aTitle) override;
+28
View File
@@ -2795,6 +2795,7 @@ ConvertExceptionToPromise(JSContext* cx,
JSObject* promiseScope,
JS::MutableHandle<JS::Value> rval)
{
#ifndef SPIDERMONKEY_PROMISE
GlobalObject global(cx, promiseScope);
if (global.Failed()) {
return false;
@@ -2829,6 +2830,33 @@ ConvertExceptionToPromise(JSContext* cx,
}
return GetOrCreateDOMReflector(cx, promise, rval);
#else // SPIDERMONKEY_PROMISE
{
JSAutoCompartment ac(cx, promiseScope);
JS::Rooted<JS::Value> exn(cx);
if (!JS_GetPendingException(cx, &exn)) {
// This is very important: if there is no pending exception here but we're
// ending up in this code, that means the callee threw an uncatchable
// exception. Just propagate that out as-is.
return false;
}
JS_ClearPendingException(cx);
JSObject* promise = JS::CallOriginalPromiseReject(cx, exn);
if (!promise) {
// We just give up. Put the exception back.
JS_SetPendingException(cx, exn);
return false;
}
rval.setObject(*promise);
}
// Now make sure we rewrap promise back into the compartment we want
return JS_WrapValue(cx, rval);
#endif // SPIDERMONKEY_PROMISE
}
/* static */
+15 -1
View File
@@ -1131,7 +1131,8 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx,
// Helper for smart pointers (nsRefPtr/nsCOMPtr).
template <template <typename> class SmartPtr, typename T,
typename U=typename EnableIf<IsRefcounted<T>::value, T>::Type>
typename U=typename EnableIf<IsRefcounted<T>::value, T>::Type,
typename V=typename EnableIf<IsSmartPtr<SmartPtr<T>>::value, T>::Type>
inline bool
WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope,
const SmartPtr<T>& value,
@@ -1142,6 +1143,19 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope,
givenProto);
}
// Helper for object references (as opposed to pointers).
template <typename T,
typename U=typename EnableIf<!IsSmartPtr<T>::value, T>::Type>
inline bool
WrapNewBindingNonWrapperCachedObject(JSContext* cx, JS::Handle<JSObject*> scope,
T& value,
JS::MutableHandle<JS::Value> rval,
JS::Handle<JSObject*> givenProto = nullptr)
{
return WrapNewBindingNonWrapperCachedObject(cx, scope, &value, rval,
givenProto);
}
// Only set allowNativeWrapper to false if you really know you need it, if in
// doubt use true. Setting it to false disables security wrappers.
bool
+4 -4
View File
@@ -80,10 +80,6 @@ DOMInterfaces = {
'nativeType': 'mozilla::dom::Activity',
},
'MozAbortablePromise': {
'nativeType': 'mozilla::dom::AbortablePromise',
},
'AbstractWorker': {
'concrete': False
},
@@ -977,6 +973,10 @@ DOMInterfaces = {
'concrete': False,
},
'PromiseNativeHandler': {
'wrapperCache': False,
},
'PropertyNodeList': {
'headerFile': 'HTMLPropertiesCollection.h',
},
+31 -13
View File
@@ -2982,6 +2982,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
if self.descriptor.name == "Promise":
speciesSetup = CGGeneric(fill(
"""
#ifndef SPIDERMONKEY_PROMISE
JS::Rooted<JSObject*> promiseConstructor(aCx, *interfaceCache);
JS::Rooted<jsid> species(aCx,
SYMBOL_TO_JSID(JS::GetWellKnownSymbol(aCx, JS::SymbolCode::species)));
@@ -2989,6 +2990,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
JSPROP_SHARED, Promise::PromiseSpecies, nullptr)) {
$*{failureCode}
}
#endif // SPIDERMONKEY_PROMISE
""",
failureCode=failureCode))
else:
@@ -5037,11 +5039,6 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
return JSToNativeConversionInfo(template, declType=declType,
dealWithOptional=isOptional)
if descriptor.interface.identifier.name == "AbortablePromise":
raise TypeError("Need to figure out what argument conversion "
"should look like for AbortablePromise: %s" %
sourceDescription)
# This is an interface that we implement as a concrete class
# or an XPCOM interface.
@@ -5185,7 +5182,26 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if (promiseGlobal.Failed()) {
$*{exceptionCode}
}
JS::Rooted<JS::Value> valueToResolve(cx, $${val});
if (!JS_WrapValue(cx, &valueToResolve)) {
$*{exceptionCode}
}
ErrorResult promiseRv;
#ifdef SPIDERMONKEY_PROMISE
nsCOMPtr<nsIGlobalObject> global =
do_QueryInterface(promiseGlobal.GetAsSupports());
if (!global) {
promiseRv.Throw(NS_ERROR_UNEXPECTED);
promiseRv.MaybeSetPendingException(cx);
$*{exceptionCode}
}
$${declName} = Promise::Resolve(global, cx, valueToResolve,
promiseRv);
if (promiseRv.MaybeSetPendingException(cx)) {
$*{exceptionCode}
}
#else
JS::Handle<JSObject*> promiseCtor =
PromiseBinding::GetConstructorObjectHandle(cx, globalObj);
if (!promiseCtor) {
@@ -5193,10 +5209,6 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
}
JS::Rooted<JS::Value> resolveThisv(cx, JS::ObjectValue(*promiseCtor));
JS::Rooted<JS::Value> resolveResult(cx);
JS::Rooted<JS::Value> valueToResolve(cx, $${val});
if (!JS_WrapValue(cx, &valueToResolve)) {
$*{exceptionCode}
}
Promise::Resolve(promiseGlobal, resolveThisv, valueToResolve,
&resolveResult, promiseRv);
if (promiseRv.MaybeSetPendingException(cx)) {
@@ -5208,6 +5220,7 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
promiseRv.MaybeSetPendingException(cx);
$*{exceptionCode}
}
#endif // SPIDERMONKEY_PROMISE
}
""",
getPromiseGlobal=getPromiseGlobal,
@@ -6328,7 +6341,13 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
wrapMethod = "GetOrCreateDOMReflector"
wrapArgs = "cx, %s, ${jsvalHandle}" % result
else:
if not returnsNewObject:
# Hack: the "Promise" interface is OK to return from
# non-newobject things even when it's not wrappercached; that
# happens when using SpiderMonkey promises, and the WrapObject()
# method will just return the existing reflector, which is just
# not stored in a wrappercache.
if (not returnsNewObject and
descriptor.interface.identifier.name != "Promise"):
raise MethodNotNewObjectError(descriptor.interface.identifier.name)
wrapMethod = "WrapNewBindingNonWrapperCachedObject"
wrapArgs = "cx, ${obj}, %s, ${jsvalHandle}" % result
@@ -7149,8 +7168,7 @@ class CGPerSignatureCall(CGThing):
# Hack for making Promise.prototype.then work well over Xrays.
if (not static and
(descriptor.name == "Promise" or
descriptor.name == "MozAbortablePromise") and
descriptor.name == "Promise" and
idlNode.isMethod() and
idlNode.identifier.name == "then"):
cgThings.append(CGGeneric(dedent(
@@ -7162,7 +7180,7 @@ class CGPerSignatureCall(CGThing):
needsUnwrap = False
argsPost = []
if isConstructor:
if descriptor.name == "Promise" or descriptor.name == "MozAbortablePromise":
if descriptor.name == "Promise":
# Hack for Promise for now: pass in our desired proto so the
# implementation can create the reflector with the right proto.
argsPost.append("desiredProto")
+7
View File
@@ -532,6 +532,13 @@ class Descriptor(DescriptorProvider):
self.wrapperCache = (not self.interface.isCallback() and
not self.interface.isIteratorInterface() and
desc.get('wrapperCache', True))
# Nasty temporary hack for supporting both DOM and SpiderMonkey promises
# without too much pain
if self.interface.identifier.name == "Promise":
assert self.wrapperCache
# But really, we're only wrappercached if we have an interface
# object (that is, when we're not using SpiderMonkey promises).
self.wrapperCache = self.interface.hasInterfaceObject()
def make_name(name):
return name + "_workers" if self.workers else name
+13
View File
@@ -7,6 +7,9 @@
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/Exceptions.h"
#ifdef SPIDERMONKEY_PROMISE
#include "mozilla/dom/Promise.h"
#endif // SPIDERMONKEY_PROMISE
#include "nsAString.h"
#include "nsContentUtils.h"
#include "nsStringBuffer.h"
@@ -64,5 +67,15 @@ ToJSValue(JSContext* aCx,
return true;
}
#ifdef SPIDERMONKEY_PROMISE
bool
ToJSValue(JSContext* aCx, Promise& aArgument,
JS::MutableHandle<JS::Value> aValue)
{
aValue.setObject(*aArgument.PromiseObj());
return true;
}
#endif // SPIDERMONKEY_PROMISE
} // namespace dom
} // namespace mozilla
+10
View File
@@ -19,6 +19,8 @@
namespace mozilla {
namespace dom {
class Promise;
// If ToJSValue returns false, it must set an exception on the
// JSContext.
@@ -304,6 +306,14 @@ ToJSValue(JSContext* aCx,
return ToJSValue(aCx, *aArgument, aValue);
}
#ifdef SPIDERMONKEY_PROMISE
// Accept Promise objects, which need special handling.
MOZ_WARN_UNUSED_RESULT bool
ToJSValue(JSContext* aCx,
Promise& aArgument,
JS::MutableHandle<JS::Value> aValue);
#endif // SPIDERMONKEY_PROMISE
// Accept arrays of other things we accept
template <typename T>
MOZ_WARN_UNUSED_RESULT bool
+3
View File
@@ -1,8 +1,10 @@
[DEFAULT]
support-files =
bug592641_img.jpg
dummy_page.html
file_bug649778.html
file_bug649778.html^headers^
head.js
[browser_bug592641.js]
[browser_bug649778.js]
@@ -14,3 +16,4 @@ support-files =
file_bug1108547-2.html
file_bug1108547-3.html
[browser_DOMDocElementInserted.js]
[browser_fullscreen-contextmenu-esc.js]
@@ -0,0 +1,105 @@
"use strict";
function frameScript() {
addMessageListener("Test:RequestFullscreen", () => {
content.document.body.mozRequestFullScreen();
});
content.document.addEventListener("mozfullscreenchange", () => {
sendAsyncMessage("Test:FullscreenChanged", content.document.mozFullScreen);
});
addMessageListener("Test:QueryFullscreenState", () => {
sendAsyncMessage("Test:FullscreenState", content.document.mozFullScreen);
});
function waitUntilActive() {
let doc = content.document;
if (doc.docShell.isActive && doc.hasFocus()) {
sendAsyncMessage("Test:Activated");
} else {
setTimeout(waitUntilActive, 10);
}
}
waitUntilActive();
}
var gMessageManager;
function listenOneMessage(aMsg, aListener) {
function listener({ data }) {
gMessageManager.removeMessageListener(aMsg, listener);
aListener(data);
}
gMessageManager.addMessageListener(aMsg, listener);
}
function promiseOneMessage(aMsg) {
return new Promise(resolve => listenOneMessage(aMsg, resolve));
}
function captureUnexpectedFullscreenChange() {
ok(false, "Caught an unexpected fullscreen change");
}
const kPage = "http://example.org/browser/dom/html/test/dummy_page.html";
add_task(function* () {
yield pushPrefs(
["full-screen-api.transition-duration.enter", "0 0"],
["full-screen-api.transition-duration.leave", "0 0"]);
let tab = gBrowser.addTab(kPage);
registerCleanupFunction(() => gBrowser.removeTab(tab));
let browser = tab.linkedBrowser;
gBrowser.selectedTab = tab;
yield waitForDocLoadComplete();
gMessageManager = browser.messageManager;
gMessageManager.loadFrameScript(
"data:,(" + frameScript.toString() + ")();", false);
// Wait for the document being activated, so that
// fullscreen request won't be denied.
yield promiseOneMessage("Test:Activated");
let contextMenu = document.getElementById("contentAreaContextMenu");
ok(contextMenu, "Got context menu");
let state;
info("Enter DOM fullscreen");
gMessageManager.sendAsyncMessage("Test:RequestFullscreen");
state = yield promiseOneMessage("Test:FullscreenChanged");
ok(state, "The content should have entered fullscreen");
ok(document.mozFullScreen, "The chrome should also be in fullscreen");
gMessageManager.addMessageListener(
"Test:FullscreenChanged", captureUnexpectedFullscreenChange);
info("Open context menu");
is(contextMenu.state, "closed", "Should not have opened context menu");
let popupShownPromise = promiseWaitForEvent(window, "popupshown");
EventUtils.synthesizeMouse(browser, screen.width / 2, screen.height / 2,
{type: "contextmenu", button: 2}, window);
yield popupShownPromise;
is(contextMenu.state, "open", "Should have opened context menu");
info("Send the first escape");
let popupHidePromise = promiseWaitForEvent(window, "popuphidden");
EventUtils.synthesizeKey("VK_ESCAPE", {});
yield popupHidePromise;
is(contextMenu.state, "closed", "Should have closed context menu");
// Wait a small time to confirm that the first ESC key
// does not exit fullscreen.
yield new Promise(resolve => setTimeout(resolve, 1000));
gMessageManager.sendAsyncMessage("Test:QueryFullscreenState");
state = yield promiseOneMessage("Test:FullscreenState");
ok(state, "The content should still be in fullscreen");
ok(document.mozFullScreen, "The chrome should still be in fullscreen");
info("Send the second escape");
gMessageManager.removeMessageListener(
"Test:FullscreenChanged", captureUnexpectedFullscreenChange);
let fullscreenExitPromise = promiseOneMessage("Test:FullscreenChanged");
EventUtils.synthesizeKey("VK_ESCAPE", {});
state = yield fullscreenExitPromise;
ok(!state, "The content should have exited fullscreen");
ok(!document.mozFullScreen, "The chrome should have exited fullscreen");
});
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Dummy test page</title>
<meta charset="utf-8"/>
</head>
<body>
<p>Dummy test page</p>
</body>
</html>
@@ -1,100 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=910532
Verify that an ESC key press canceling the context menu
won't exit DOM fullscreen.
-->
<head>
<meta charset="UTF-8">
<title>Text for bug 910532</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="file_fullscreen-utils.js"></script>
<style>
body:-moz-full-screen {
background-color: red;
}
</style>
</head>
<body>
<script type="application/javascript">
const Ci = SpecialPowers.Ci;
SimpleTest.requestFlakyTimeout("We need to wait a small time to confirm " +
"that the first ESC key does not exit fullscreen.");
function ok(condition, msg) {
opener.ok(condition, "[esc-context-menu] " + msg);
}
function is(a, b, msg) {
opener.is(a, b, "[esc-context-menu] " + msg);
}
var contextMenu;
var escapeSent = 0;
function sendEscape() {
escapeSent++;
synthesizeKey("VK_ESCAPE", {});
}
function begin() {
// Copy from browser/base/content/test/general/test_contextmenu.html
var chromeWin = SpecialPowers.wrap(window)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
contextMenu = chromeWin.document.getElementById("contentAreaContextMenu");
ok(contextMenu, "Got context menu XUL");
addFullscreenChangeContinuation("enter", fullscreenEntered);
SpecialPowers.pushPrefEnv({"set":[["full-screen-api.approval-required", false]]}, function() {
document.body.mozRequestFullScreen();
});
}
function finish() {
opener.nextTest();
}
function fullscreenEntered(event) {
ok(document.mozFullScreen, "Should have entered fullscreen mode");
is(document.mozFullScreenElement, document.body, "FSE should be doc");
contextMenu.addEventListener("popupshown", contextMenuOpened, false);
is(contextMenu.state, "closed", "Should not have opened context menu");
synthesizeMouseAtCenter(document.body, {type: 'contextmenu', button: 2});
}
function contextMenuOpened(event) {
contextMenu.removeEventListener("popupshown", contextMenuOpened);
is(contextMenu.state, "open", "Should have opened context menu");
addFullscreenChangeContinuation("exit", fullscreenExited);
contextMenu.addEventListener("popuphidden", contextMenuClosed, false);
sendEscape();
}
function contextMenuClosed(event) {
is(contextMenu.state, "closed", "Should have closed context menu");
setTimeout(function () {
ok(document.mozFullScreen, "Should still be in fullscreen mode");
sendEscape();
}, 100);
}
function fullscreenExited(event) {
is(escapeSent, 2, "Only the second escape should exit fullscreen");
ok(!document.mozFullScreen, "Should have left fullscreen mode");
finish();
}
</script>
</body>
</html>
+54
View File
@@ -0,0 +1,54 @@
function pushPrefs(...aPrefs) {
return new Promise(resolve => {
SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
});
}
function promiseWaitForEvent(object, eventName, capturing = false, chrome = false) {
return new Promise((resolve) => {
function listener(event) {
info("Saw " + eventName);
object.removeEventListener(eventName, listener, capturing, chrome);
resolve(event);
}
info("Waiting for " + eventName);
object.addEventListener(eventName, listener, capturing, chrome);
});
}
/**
* Waits for the next load to complete in any browser or the given browser.
* If a <tabbrowser> is given it waits for a load in any of its browsers.
*
* @return promise
*/
function waitForDocLoadComplete(aBrowser=gBrowser) {
return new Promise(resolve => {
let listener = {
onStateChange: function (webProgress, req, flags, status) {
let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
Ci.nsIWebProgressListener.STATE_STOP;
info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
// When a load needs to be retargetted to a new process it is cancelled
// with NS_BINDING_ABORTED so ignore that case
if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
aBrowser.removeProgressListener(this);
waitForDocLoadComplete.listeners.delete(this);
let chan = req.QueryInterface(Ci.nsIChannel);
info("Browser loaded " + chan.originalURI.spec);
resolve();
}
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference])
};
aBrowser.addProgressListener(listener);
waitForDocLoadComplete.listeners.add(listener);
info("Waiting for browser load");
});
}
// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
// they're not GC'ed before we saw the page load.
waitForDocLoadComplete.listeners = new Set();
registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
-1
View File
@@ -51,7 +51,6 @@ support-files =
file_fullscreen-backdrop.html
file_fullscreen-denied-inner.html
file_fullscreen-denied.html
file_fullscreen-esc-context-menu.html
file_fullscreen-esc-exit-inner.html
file_fullscreen-esc-exit.html
file_fullscreen-hidden.html
-1
View File
@@ -30,7 +30,6 @@ SimpleTest.requestFlakyTimeout("untriaged");
var gTestWindows = [
"file_fullscreen-multiple.html",
"file_fullscreen-rollback.html",
"file_fullscreen-esc-context-menu.html",
"file_fullscreen-esc-exit.html",
"file_fullscreen-denied.html",
"file_fullscreen-api.html",
-118
View File
@@ -1,118 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/AbortablePromise.h"
#include "mozilla/dom/AbortablePromiseBinding.h"
#include "mozilla/dom/PromiseNativeAbortCallback.h"
#include "mozilla/ErrorResult.h"
#include "nsThreadUtils.h"
#include "PromiseCallback.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseNativeAbortCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseNativeAbortCallback)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PromiseNativeAbortCallback)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_0(PromiseNativeAbortCallback)
NS_IMPL_ADDREF_INHERITED(AbortablePromise, Promise)
NS_IMPL_RELEASE_INHERITED(AbortablePromise, Promise)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AbortablePromise)
NS_INTERFACE_MAP_END_INHERITING(Promise)
NS_IMPL_CYCLE_COLLECTION_INHERITED(AbortablePromise, Promise, mAbortCallback)
AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal,
PromiseNativeAbortCallback& aAbortCallback)
: Promise(aGlobal)
, mAbortCallback(&aAbortCallback)
{
}
AbortablePromise::AbortablePromise(nsIGlobalObject* aGlobal)
: Promise(aGlobal)
{
}
AbortablePromise::~AbortablePromise()
{
}
/* static */ already_AddRefed<AbortablePromise>
AbortablePromise::Create(nsIGlobalObject* aGlobal,
PromiseNativeAbortCallback& aAbortCallback,
ErrorResult& aRv)
{
RefPtr<AbortablePromise> p = new AbortablePromise(aGlobal, aAbortCallback);
p->CreateWrapper(nullptr, aRv);
if (aRv.Failed()) {
return nullptr;
}
return p.forget();
}
JSObject*
AbortablePromise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return MozAbortablePromiseBinding::Wrap(aCx, this, aGivenProto);
}
/* static */ already_AddRefed<AbortablePromise>
AbortablePromise::Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
AbortCallback& aAbortCallback, ErrorResult& aRv,
JS::Handle<JSObject*> aDesiredProto)
{
nsCOMPtr<nsIGlobalObject> global;
global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
RefPtr<AbortablePromise> promise = new AbortablePromise(global);
promise->CreateWrapper(aDesiredProto, aRv);
if (aRv.Failed()) {
return nullptr;
}
promise->CallInitFunction(aGlobal, aInit, aRv);
if (aRv.Failed()) {
return nullptr;
}
promise->mAbortCallback = &aAbortCallback;
return promise.forget();
}
void
AbortablePromise::Abort()
{
if (IsPending()) {
return;
}
MaybeReject(NS_ERROR_ABORT);
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &AbortablePromise::DoAbort);
Promise::DispatchToMicroTask(runnable);
}
void
AbortablePromise::DoAbort()
{
if (mAbortCallback.HasWebIDLCallback()) {
mAbortCallback.GetWebIDLCallback()->Call();
return;
}
mAbortCallback.GetXPCOMCallback()->Call();
}
} // namespace dom
} // namespace mozilla
-67
View File
@@ -1,67 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_AbortablePromise_h__
#define mozilla_dom_AbortablePromise_h__
#include "js/TypeDecls.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/CallbackObject.h"
namespace mozilla {
namespace dom {
class AbortCallback;
class PromiseNativeAbortCallback;
class AbortablePromise
: public Promise
{
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AbortablePromise, Promise)
public:
// It is the same as Promise::Create except that this takes an extra
// aAbortCallback parameter to set the abort callback handler.
static already_AddRefed<AbortablePromise>
Create(nsIGlobalObject* aGlobal, PromiseNativeAbortCallback& aAbortCallback,
ErrorResult& aRv);
protected:
// Constructor used to create native AbortablePromise with C++.
AbortablePromise(nsIGlobalObject* aGlobal,
PromiseNativeAbortCallback& aAbortCallback);
// Constructor used to create AbortablePromise for JavaScript. It should be
// called by the static AbortablePromise::Constructor.
explicit AbortablePromise(nsIGlobalObject* aGlobal);
virtual ~AbortablePromise();
public:
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
static already_AddRefed<AbortablePromise>
Constructor(const GlobalObject& aGlobal, PromiseInit& aInit,
AbortCallback& aAbortCallback, ErrorResult& aRv,
JS::Handle<JSObject*> aDesiredProto);
void Abort();
private:
void DoAbort();
// The callback functions to abort the promise.
CallbackObjectHolder<AbortCallback,
PromiseNativeAbortCallback> mAbortCallback;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_AbortablePromise_h__
+384 -40
View File
@@ -50,6 +50,7 @@ Atomic<uintptr_t> gIDGenerator(0);
using namespace workers;
#ifndef SPIDERMONKEY_PROMISE
// This class processes the promise's callbacks with promise's result.
class PromiseReactionJob final : public nsRunnable
{
@@ -317,7 +318,7 @@ struct MOZ_STACK_CLASS Promise::PromiseCapability
// purposes it's simpler to store them as JS::Value.
// [[Promise]].
JS::Rooted<JS::Value> mPromise;
JS::Rooted<JSObject*> mPromise;
// [[Resolve]]. Value in the context compartment.
JS::Rooted<JS::Value> mResolve;
// [[Reject]]. Value in the context compartment.
@@ -340,7 +341,7 @@ Promise::PromiseCapability::RejectWithException(JSContext* aCx,
// or at least the parts of it that happen if we have an abrupt completion.
MOZ_ASSERT(!aRv.Failed());
MOZ_ASSERT(mNativePromise || !mPromise.isUndefined(),
MOZ_ASSERT(mNativePromise || mPromise,
"NewPromiseCapability didn't succeed");
JS::Rooted<JS::Value> exn(aCx);
@@ -370,47 +371,62 @@ Promise::PromiseCapability::RejectWithException(JSContext* aCx,
JS::Value
Promise::PromiseCapability::PromiseValue() const
{
MOZ_ASSERT(mNativePromise || !mPromise.isUndefined(),
MOZ_ASSERT(mNativePromise || mPromise,
"NewPromiseCapability didn't succeed");
if (mNativePromise) {
return JS::ObjectValue(*mNativePromise->GetWrapper());
}
return mPromise;
return JS::ObjectValue(*mPromise);
}
#endif // SPIDERMONKEY_PROMISE
// Promise
NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
#ifndef SPIDERMONKEY_PROMISE
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
tmp->MaybeReportRejectedOnce();
#else
tmp->mResult = JS::UndefinedValue();
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
#endif // SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
#ifndef SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
#else // SPIDERMONKEY_PROMISE
tmp->mPromiseObj = nullptr;
#endif // SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
#ifndef SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResolveCallbacks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRejectCallbacks)
#endif // SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Promise)
#ifndef SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mResult)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mAllocationStack)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRejectionStack)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mFullfillmentStack)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
#else // SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPromiseObj);
#endif // SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_TRACE_END
#ifndef SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(Promise)
if (tmp->IsBlack()) {
JS::ExposeValueToActiveJS(tmp->mResult);
@@ -434,18 +450,22 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(Promise)
return tmp->IsBlack();
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
#endif // SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTING_ADDREF(Promise)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Promise)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Promise)
#ifndef SPIDERMONKEY_PROMISE
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
#endif // SPIDERMONKEY_PROMISE
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRY(Promise)
NS_INTERFACE_MAP_END
Promise::Promise(nsIGlobalObject* aGlobal)
: mGlobal(aGlobal)
#ifndef SPIDERMONKEY_PROMISE
, mResult(JS::UndefinedValue())
, mAllocationStack(nullptr)
, mRejectionStack(nullptr)
@@ -459,22 +479,364 @@ Promise::Promise(nsIGlobalObject* aGlobal)
, mIsLastInChain(true)
, mWasNotifiedAsUncaught(false)
, mID(0)
#else // SPIDERMONKEY_PROMISE
, mPromiseObj(nullptr)
#endif // SPIDERMONKEY_PROMISE
{
MOZ_ASSERT(mGlobal);
mozilla::HoldJSObjects(this);
#ifndef SPIDERMONKEY_PROMISE
mCreationTimestamp = TimeStamp::Now();
#endif // SPIDERMONKEY_PROMISE
}
Promise::~Promise()
{
#ifndef SPIDERMONKEY_PROMISE
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
MaybeReportRejectedOnce();
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
#endif // SPIDERMONKEY_PROMISE
mozilla::DropJSObjects(this);
}
#ifdef SPIDERMONKEY_PROMISE
bool
Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aWrapper)
{
#ifdef DEBUG
binding_detail::AssertReflectorHasGivenProto(aCx, mPromiseObj, aGivenProto);
#endif // DEBUG
JS::ExposeObjectToActiveJS(mPromiseObj);
aWrapper.set(mPromiseObj);
return true;
}
// static
already_AddRefed<Promise>
Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
{
RefPtr<Promise> p = new Promise(aGlobal);
p->CreateWrapper(nullptr, aRv);
if (aRv.Failed()) {
return nullptr;
}
return p.forget();
}
// static
already_AddRefed<Promise>
Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
{
JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject());
JS::Rooted<JSObject*> p(aCx,
JS::CallOriginalPromiseResolve(aCx, aValue));
if (!p) {
aRv.NoteJSContextException();
return nullptr;
}
return CreateFromExisting(aGlobal, p);
}
// static
already_AddRefed<Promise>
Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
{
JSAutoCompartment ac(aCx, aGlobal->GetGlobalJSObject());
JS::Rooted<JSObject*> p(aCx,
JS::CallOriginalPromiseReject(aCx, aValue));
if (!p) {
aRv.NoteJSContextException();
return nullptr;
}
return CreateFromExisting(aGlobal, p);
}
// static
already_AddRefed<Promise>
Promise::All(const GlobalObject& aGlobal,
const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global;
global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
JSContext* cx = aGlobal.Context();
JS::AutoObjectVector promises(cx);
if (!promises.reserve(aPromiseList.Length())) {
aRv.NoteJSContextException();
return nullptr;
}
for (auto& promise : aPromiseList) {
JS::Rooted<JSObject*> promiseObj(cx, promise->PromiseObj());
// Just in case, make sure these are all in the context compartment.
if (!JS_WrapObject(cx, &promiseObj)) {
aRv.NoteJSContextException();
return nullptr;
}
promises.infallibleAppend(promiseObj);
}
JS::Rooted<JSObject*> result(cx, JS::GetWaitForAllPromise(cx, promises));
if (!result) {
aRv.NoteJSContextException();
return nullptr;
}
return CreateFromExisting(global, result);
}
void
Promise::Then(JSContext* aCx,
// aCalleeGlobal may not be in the compartment of aCx, when called over
// Xrays.
JS::Handle<JSObject*> aCalleeGlobal,
AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv)
{
// Let's hope this does the right thing with Xrays... Ensure everything is
// just in the caller compartment; that ought to do the trick. In theory we
// should consider aCalleeGlobal, but in practice our only caller is
// DOMRequest::Then, which is not working with a Promise subclass, so things
// should be OK.
JS::Rooted<JSObject*> promise(aCx, PromiseObj());
if (!JS_WrapObject(aCx, &promise)) {
aRv.NoteJSContextException();
return;
}
JS::Rooted<JSObject*> resolveCallback(aCx);
if (aResolveCallback) {
resolveCallback = aResolveCallback->Callback();
if (!JS_WrapObject(aCx, &resolveCallback)) {
aRv.NoteJSContextException();
return;
}
}
JS::Rooted<JSObject*> rejectCallback(aCx);
if (aRejectCallback) {
rejectCallback = aRejectCallback->Callback();
if (!JS_WrapObject(aCx, &rejectCallback)) {
aRv.NoteJSContextException();
return;
}
}
JS::Rooted<JSObject*> retval(aCx);
retval = JS::CallOriginalPromiseThen(aCx, promise, resolveCallback,
rejectCallback);
if (!retval) {
aRv.NoteJSContextException();
return;
}
aRetval.setObject(*retval);
}
// We need a dummy function to pass to JS::NewPromiseObject.
static bool
DoNothingPromiseExecutor(JSContext*, unsigned aArgc, JS::Value* aVp)
{
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
args.rval().setUndefined();
return true;
}
void
Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv)
{
AutoJSAPI jsapi;
if (!jsapi.Init(mGlobal)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
JSContext* cx = jsapi.cx();
JSFunction* doNothingFunc =
JS_NewFunction(cx, DoNothingPromiseExecutor, /* nargs = */ 2,
/* flags = */ 0, nullptr);
if (!doNothingFunc) {
JS_ClearPendingException(cx);
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
JS::Rooted<JSObject*> doNothingObj(cx, JS_GetFunctionObject(doNothingFunc));
mPromiseObj = JS::NewPromiseObject(cx, doNothingObj, aDesiredProto);
if (!mPromiseObj) {
JS_ClearPendingException(cx);
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
void
Promise::MaybeResolve(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
JS::Rooted<JSObject*> p(aCx, PromiseObj());
if (!JS::ResolvePromise(aCx, p, aValue)) {
// Now what? There's nothing sane to do here.
JS_ClearPendingException(aCx);
}
}
void
Promise::MaybeReject(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
JS::Rooted<JSObject*> p(aCx, PromiseObj());
if (!JS::RejectPromise(aCx, p, aValue)) {
// Now what? There's nothing sane to do here.
JS_ClearPendingException(aCx);
}
}
#define SLOT_NATIVEHANDLER 0
#define SLOT_NATIVEHANDLER_TASK 1
enum class NativeHandlerTask : int32_t {
Resolve,
Reject
};
static bool
NativeHandlerCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
{
JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
JS::Rooted<JS::Value> v(aCx,
js::GetFunctionNativeReserved(&args.callee(),
SLOT_NATIVEHANDLER));
MOZ_ASSERT(v.isObject());
PromiseNativeHandler* handler;
if (NS_FAILED(UNWRAP_OBJECT(PromiseNativeHandler, &v.toObject(),
handler))) {
return Throw(aCx, NS_ERROR_UNEXPECTED);
}
v = js::GetFunctionNativeReserved(&args.callee(), SLOT_NATIVEHANDLER_TASK);
NativeHandlerTask task = static_cast<NativeHandlerTask>(v.toInt32());
if (task == NativeHandlerTask::Resolve) {
handler->ResolvedCallback(aCx, args.get(0));
} else {
MOZ_ASSERT(task == NativeHandlerTask::Reject);
handler->RejectedCallback(aCx, args.get(0));
}
return true;
}
static JSObject*
CreateNativeHandlerFunction(JSContext* aCx, JS::Handle<JSObject*> aHolder,
NativeHandlerTask aTask)
{
JSFunction* func = js::NewFunctionWithReserved(aCx, NativeHandlerCallback,
/* nargs = */ 1,
/* flags = */ 0, nullptr);
if (!func) {
return nullptr;
}
JS::Rooted<JSObject*> obj(aCx, JS_GetFunctionObject(func));
JS::ExposeObjectToActiveJS(aHolder);
js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER,
JS::ObjectValue(*aHolder));
js::SetFunctionNativeReserved(obj, SLOT_NATIVEHANDLER_TASK,
JS::Int32Value(static_cast<int32_t>(aTask)));
return obj;
}
void
Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
{
AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
// Our API doesn't allow us to return a useful error. Not like this should
// happen anyway.
return;
}
jsapi.TakeOwnershipOfErrorReporting();
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> handlerWrapper(cx);
// Note: PromiseNativeHandler is NOT wrappercached. So we can't use
// ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
if (NS_WARN_IF(!aRunnable->WrapObject(cx, nullptr, &handlerWrapper))) {
// Again, no way to report errors.
jsapi.ClearException();
return;
}
JS::Rooted<JSObject*> resolveFunc(cx);
resolveFunc =
CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Resolve);
if (NS_WARN_IF(!resolveFunc)) {
jsapi.ClearException();
return;
}
JS::Rooted<JSObject*> rejectFunc(cx);
rejectFunc =
CreateNativeHandlerFunction(cx, handlerWrapper, NativeHandlerTask::Reject);
if (NS_WARN_IF(!rejectFunc)) {
jsapi.ClearException();
return;
}
JS::Rooted<JSObject*> promiseObj(cx, PromiseObj());
if (NS_WARN_IF(!JS::AddPromiseReactions(cx, promiseObj, resolveFunc,
rejectFunc))) {
jsapi.ClearException();
return;
}
}
void
Promise::HandleException(JSContext* aCx)
{
JS::Rooted<JS::Value> exn(aCx);
if (JS_GetPendingException(aCx, &exn)) {
JS_ClearPendingException(aCx);
// This is only called from MaybeSomething, so it's OK to MaybeReject here,
// unlike in the version that's used when !SPIDERMONKEY_PROMISE.
MaybeReject(aCx, exn);
}
}
// static
already_AddRefed<Promise>
Promise::CreateFromExisting(nsIGlobalObject* aGlobal,
JS::Handle<JSObject*> aPromiseObj)
{
MOZ_ASSERT(js::GetObjectCompartment(aGlobal->GetGlobalJSObject()) ==
js::GetObjectCompartment(aPromiseObj));
RefPtr<Promise> p = new Promise(aGlobal);
p->mPromiseObj = aPromiseObj;
return p.forget();
}
#else // SPIDERMONKEY_PROMISE
JSObject*
Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
@@ -537,6 +899,8 @@ Promise::MaybeReject(JSContext* aCx,
MaybeRejectInternal(aCx, aValue);
}
#endif // SPIDERMONKEY_PROMISE
void
Promise::MaybeReject(const RefPtr<MediaStreamError>& aArg) {
MaybeSomething(aArg, &Promise::MaybeReject);
@@ -577,6 +941,8 @@ Promise::PerformMicroTaskCheckpoint()
return true;
}
#ifndef SPIDERMONKEY_PROMISE
/* static */ bool
Promise::JSCallback(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
{
@@ -959,7 +1325,7 @@ Promise::NewPromiseCapability(JSContext* aCx, nsIGlobalObject* aGlobal,
aCapability.mReject = v;
// Step 10.
aCapability.mPromise.setObject(*promiseObj);
aCapability.mPromise = promiseObj;
// Step 11 doesn't need anything, since the PromiseCapability was passed in.
}
@@ -1274,11 +1640,11 @@ Promise::Then(JSContext* aCx, JS::Handle<JSObject*> aCalleeGlobal,
RefPtr<AnyCallback> rejectFunc =
new AnyCallback(aCx, rejectObj, GetIncumbentGlobal());
if (!capability.mPromise.isObject()) {
if (!capability.mPromise) {
aRv.ThrowTypeError<MSG_ILLEGAL_PROMISE_CONSTRUCTOR>();
return;
}
JS::Rooted<JSObject*> newPromiseObj(aCx, &capability.mPromise.toObject());
JS::Rooted<JSObject*> newPromiseObj(aCx, capability.mPromise);
// We want to store the reflector itself.
newPromiseObj = js::CheckedUnwrap(newPromiseObj);
if (!newPromiseObj) {
@@ -1979,6 +2345,8 @@ Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
AppendCallbacks(resolveCb, rejectCb);
}
#endif // SPIDERMONKEY_PROMISE
JSObject*
Promise::GlobalJSObject() const
{
@@ -1991,6 +2359,7 @@ Promise::Compartment() const
return js::GetObjectCompartment(GlobalJSObject());
}
#ifndef SPIDERMONKEY_PROMISE
void
Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
PromiseCallback* aRejectCallback)
@@ -2027,37 +2396,7 @@ Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
TriggerPromiseReactions();
}
}
class WrappedWorkerRunnable final : public WorkerSameThreadRunnable
{
public:
WrappedWorkerRunnable(workers::WorkerPrivate* aWorkerPrivate, nsIRunnable* aRunnable)
: WorkerSameThreadRunnable(aWorkerPrivate)
, mRunnable(aRunnable)
{
MOZ_ASSERT(aRunnable);
MOZ_COUNT_CTOR(WrappedWorkerRunnable);
}
bool
WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
{
NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable);
mRunnable->Run();
return true;
}
private:
virtual
~WrappedWorkerRunnable()
{
MOZ_COUNT_DTOR(WrappedWorkerRunnable);
NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable);
}
nsCOMPtr<nsIRunnable> mRunnable;
NS_DECL_OWNINGTHREAD
};
#endif // SPIDERMONKEY_PROMISE
/* static */ void
Promise::DispatchToMicroTask(nsIRunnable* aRunnable)
@@ -2071,6 +2410,7 @@ Promise::DispatchToMicroTask(nsIRunnable* aRunnable)
microtaskQueue.push(aRunnable);
}
#ifndef SPIDERMONKEY_PROMISE
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
void
Promise::MaybeReportRejected()
@@ -2363,6 +2703,8 @@ Promise::GetDependentPromises(nsTArray<RefPtr<Promise>>& aPromises)
}
}
#endif // SPIDERMONKEY_PROMISE
// A WorkerRunnable to resolve/reject the Promise on the worker thread.
// Calling thread MUST hold PromiseWorkerProxy's mutex before creating this.
class PromiseWorkerProxyRunnable : public workers::WorkerRunnable
@@ -2564,14 +2906,14 @@ void
PromiseWorkerProxy::ResolvedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
RunCallback(aCx, aValue, &Promise::ResolveInternal);
RunCallback(aCx, aValue, &Promise::MaybeResolve);
}
void
PromiseWorkerProxy::RejectedCallback(JSContext* aCx,
JS::Handle<JS::Value> aValue)
{
RunCallback(aCx, aValue, &Promise::RejectInternal);
RunCallback(aCx, aValue, &Promise::MaybeReject);
}
bool
@@ -2647,6 +2989,7 @@ void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
MaybeSomething(aArg, &Promise::MaybeReject);
}
#ifndef SPIDERMONKEY_PROMISE
uint64_t
Promise::GetID() {
if (mID != 0) {
@@ -2654,6 +2997,7 @@ Promise::GetID() {
}
return mID = ++gIDGenerator;
}
#endif // SPIDERMONKEY_PROMISE
} // namespace dom
} // namespace mozilla
+92 -7
View File
@@ -71,7 +71,12 @@ public:
{ 0x8a, 0xf9, 0x31, 0x5e, 0x8f, 0xce, 0x75, 0x65 } }
class Promise : public nsISupports,
#ifndef SPIDERMONKEY_PROMISE
// Only wrappercached when we're not using SpiderMonkey
// promises, because those don't have a useful object moved
// hook, which wrappercache needs.
public nsWrapperCache,
#endif // SPIDERMONKEY_PROMISE
public SupportsWeakPtr<Promise>
{
friend class NativePromiseCallback;
@@ -92,17 +97,27 @@ class Promise : public nsISupports,
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_PROMISE_IID)
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
#ifdef SPIDERMONKEY_PROMISE
// We're not skippable, since we're not owned from JS to start with.
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise)
#else // SPIDERMONKEY_PROMISE
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(Promise)
#endif // SPIDERMONKEY_PROMISE
MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Promise)
// Promise creation tries to create a JS reflector for the Promise, so is
// fallible. Furthermore, we don't want to do JS-wrapping on a 0-refcount
// object, so we addref before doing that and return the addrefed pointer
// here.
#ifdef SPIDERMONKEY_PROMISE
static already_AddRefed<Promise>
Create(nsIGlobalObject* aGlobal, ErrorResult& aRv);
#else
static already_AddRefed<Promise>
Create(nsIGlobalObject* aGlobal, ErrorResult& aRv,
// Passing null for aDesiredProto will use Promise.prototype.
JS::Handle<JSObject*> aDesiredProto = nullptr);
#endif // SPIDERMONKEY_PROMISE
typedef void (Promise::*MaybeFunc)(JSContext* aCx,
JS::Handle<JS::Value> aValue);
@@ -156,6 +171,52 @@ public:
return mGlobal;
}
#ifdef SPIDERMONKEY_PROMISE
bool
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aWrapper);
// Do the equivalent of Promise.resolve in the current compartment of aCx.
// Errorrs are reported on the ErrorResult; if aRv comes back !Failed(), this
// function MUST return a non-null value.
static already_AddRefed<Promise>
Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv);
// Do the equivalent of Promise.reject in the current compartment of aCx.
// Errorrs are reported on the ErrorResult; if aRv comes back !Failed(), this
// function MUST return a non-null value.
static already_AddRefed<Promise>
Reject(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv);
static already_AddRefed<Promise>
All(const GlobalObject& aGlobal,
const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv);
void
Then(JSContext* aCx,
// aCalleeGlobal may not be in the compartment of aCx, when called over
// Xrays.
JS::Handle<JSObject*> aCalleeGlobal,
AnyCallback* aResolveCallback, AnyCallback* aRejectCallback,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aRv);
JSObject* PromiseObj() const
{
if (mPromiseObj) {
JS::ExposeObjectToActiveJS(mPromiseObj);
}
return mPromiseObj;
}
#else // SPIDERMONKEY_PROMISE
JSObject* PromiseObj()
{
return GetWrapper();
}
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
@@ -212,6 +273,7 @@ public:
static bool
PromiseSpecies(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
#endif // SPIDERMONKEY_PROMISE
void AppendNativeHandler(PromiseNativeHandler* aRunnable);
@@ -219,23 +281,36 @@ public:
JSCompartment* Compartment() const;
#ifndef SPIDERMONKEY_PROMISE
// Return a unique-to-the-process identifier for this Promise.
uint64_t GetID();
#endif // SPIDERMONKEY_PROMISE
// Queue an async microtask to current main or worker thread.
static void
DispatchToMicroTask(nsIRunnable* aRunnable);
#ifndef SPIDERMONKEY_PROMISE
enum JSCallbackSlots {
SLOT_PROMISE = 0,
SLOT_DATA
};
#endif // SPIDERMONKEY_PROMISE
#ifdef SPIDERMONKEY_PROMISE
// Create a dom::Promise from a given SpiderMonkey Promise object.
// aPromiseObj MUST be in the compartment of aGlobal's global JS object.
static already_AddRefed<Promise>
CreateFromExisting(nsIGlobalObject* aGlobal,
JS::Handle<JSObject*> aPromiseObj);
#endif // SPIDERMONKEY_PROMISE
protected:
struct PromiseCapability;
// Do NOT call this unless you're Promise::Create. I wish we could enforce
// that from inside this class too, somehow.
// Do NOT call this unless you're Promise::Create or
// Promise::CreateFromExisting. I wish we could enforce that from inside this
// class too, somehow.
explicit Promise(nsIGlobalObject* aGlobal);
virtual ~Promise();
@@ -244,6 +319,7 @@ protected:
// use the default prototype for the sort of Promise we have.
void CreateWrapper(JS::Handle<JSObject*> aDesiredProto, ErrorResult& aRv);
#ifndef SPIDERMONKEY_PROMISE
// Create the JS resolving functions of resolve() and reject(). And provide
// references to the two functions by calling PromiseInit passed from Promise
// constructor.
@@ -282,16 +358,18 @@ protected:
{
return mWasNotifiedAsUncaught;
}
#endif // SPIDERMONKEY_PROMISE
private:
friend class PromiseDebugging;
enum PromiseState {
Pending,
Resolved,
Rejected
};
#ifndef SPIDERMONKEY_PROMISE
friend class PromiseDebugging;
void SetState(PromiseState aState)
{
MOZ_ASSERT(mState == Pending);
@@ -338,11 +416,12 @@ private:
JS::Handle<JS::Value> aValue);
void RejectInternal(JSContext* aCx,
JS::Handle<JS::Value> aValue);
#endif // SPIDERMONKEY_PROMISE
template <typename T>
void MaybeSomething(T& aArgument, MaybeFunc aFunc) {
ThreadsafeAutoJSContext cx;
JSObject* wrapper = GetWrapper();
JSObject* wrapper = PromiseObj();
MOZ_ASSERT(wrapper); // We preserved it!
JSAutoCompartment ac(cx, wrapper);
@@ -355,6 +434,7 @@ private:
(this->*aFunc)(cx, val);
}
#ifndef SPIDERMONKEY_PROMISE
// Static methods for the PromiseInit functions.
static bool
JSCallback(JSContext *aCx, unsigned aArgc, JS::Value *aVp);
@@ -373,8 +453,6 @@ private:
static JSObject*
CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask);
void HandleException(JSContext* aCx);
#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
void RemoveFeature();
#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
@@ -382,9 +460,13 @@ private:
// Capture the current stack and store it in aTarget. If false is
// returned, an exception is presumably pending on aCx.
bool CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget);
#endif // SPIDERMONKEY_PROMISE
void HandleException(JSContext* aCx);
RefPtr<nsIGlobalObject> mGlobal;
#ifndef SPIDERMONKEY_PROMISE
nsTArray<RefPtr<PromiseCallback> > mResolveCallbacks;
nsTArray<RefPtr<PromiseCallback> > mRejectCallbacks;
@@ -435,6 +517,9 @@ private:
// Once `GetID()` has been called, a unique-to-the-process identifier for this
// promise. Until then, `0`.
uint64_t mID;
#else // SPIDERMONKEY_PROMISE
JS::Heap<JSObject*> mPromiseObj;
#endif // SPIDERMONKEY_PROMISE
};
NS_DEFINE_STATIC_IID_ACCESSOR(Promise, NS_PROMISE_IID)
+4
View File
@@ -15,6 +15,8 @@
namespace mozilla {
namespace dom {
#ifndef SPIDERMONKEY_PROMISE
NS_IMPL_CYCLE_COLLECTING_ADDREF(PromiseCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(PromiseCallback)
@@ -566,5 +568,7 @@ PromiseCallback::Factory(Promise* aNextPromise, JS::Handle<JSObject*> aGlobal,
return nullptr;
}
#endif // SPIDERMONKEY_PROMISE
} // namespace dom
} // namespace mozilla
+3
View File
@@ -13,6 +13,7 @@
namespace mozilla {
namespace dom {
#ifndef SPIDERMONKEY_PROMISE
// This is the base class for any PromiseCallback.
// It's a logical step in the promise chain of callbacks.
class PromiseCallback : public nsISupports
@@ -194,6 +195,8 @@ private:
Promise::PromiseState mState;
};
#endif // SPIDERMONKEY_PROMISE
} // namespace dom
} // namespace mozilla
+16
View File
@@ -66,6 +66,7 @@ private:
/* static */ MOZ_THREAD_LOCAL(bool)
FlushRejections::sDispatched;
#ifndef SPIDERMONKEY_PROMISE
static Promise*
UnwrapPromise(JS::Handle<JSObject*> aPromise, ErrorResult& aRv)
{
@@ -103,6 +104,8 @@ PromiseDebugging::GetState(GlobalObject&, JS::Handle<JSObject*> aPromise,
}
}
#endif // SPIDERMONKEY_PROMISE
/*static */ nsString
PromiseDebugging::sIDPrefix;
@@ -130,10 +133,15 @@ PromiseDebugging::Shutdown()
/* static */ void
PromiseDebugging::FlushUncaughtRejections()
{
// XXXbz figure out the plan
#ifndef SPIDERMONKEY_PROMISE
MOZ_ASSERT(!NS_IsMainThread());
FlushRejections::FlushSync();
#endif // SPIDERMONKEY_PROMISE
}
#ifndef SPIDERMONKEY_PROMISE
/* static */ void
PromiseDebugging::GetAllocationStack(GlobalObject&, JS::Handle<JSObject*> aPromise,
JS::MutableHandle<JSObject*> aStack,
@@ -210,6 +218,8 @@ PromiseDebugging::GetTimeToSettle(GlobalObject&, JS::Handle<JSObject*> aPromise,
promise->mCreationTimestamp).ToMilliseconds();
}
#endif // SPIDERMONKEY_PROMISE
/* static */ void
PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&,
UncaughtRejectionObserver& aObserver)
@@ -235,6 +245,8 @@ PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
return false;
}
#ifndef SPIDERMONKEY_PROMISE
/* static */ void
PromiseDebugging::AddUncaughtRejection(Promise& aPromise)
{
@@ -263,10 +275,13 @@ PromiseDebugging::GetPromiseID(GlobalObject&,
aID = sIDPrefix;
aID.AppendInt(promiseID);
}
#endif // SPIDERMONKEY_PROMISE
/* static */ void
PromiseDebugging::FlushUncaughtRejectionsInternal()
{
// XXXbz talk to till about replacement for this stuff.
#ifndef SPIDERMONKEY_PROMISE
CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
// The Promise that have been left uncaught (rejected and last in
@@ -327,6 +342,7 @@ PromiseDebugging::FlushUncaughtRejectionsInternal()
obs->OnConsumed(*promise, err); // Ignore errors
}
}
#endif // SPIDERMONKEY_PROMISE
}
} // namespace dom
+6
View File
@@ -32,6 +32,7 @@ public:
static void Init();
static void Shutdown();
#ifndef SPIDERMONKEY_PROMISE
static void GetState(GlobalObject&, JS::Handle<JSObject*> aPromise,
PromiseDebuggingStateHolder& aState,
ErrorResult& aRv);
@@ -58,20 +59,25 @@ public:
static void GetPromiseID(GlobalObject&, JS::Handle<JSObject*>, nsString&,
ErrorResult&);
#endif // SPIDERMONKEY_PROMISE
// Mechanism for watching uncaught instances of Promise.
// XXXbz figure out the plan
static void AddUncaughtRejectionObserver(GlobalObject&,
UncaughtRejectionObserver& aObserver);
static bool RemoveUncaughtRejectionObserver(GlobalObject&,
UncaughtRejectionObserver& aObserver);
#ifndef SPIDERMONKEY_PROMISE
// Mark a Promise as having been left uncaught at script completion.
static void AddUncaughtRejection(Promise&);
// Mark a Promise previously added with `AddUncaughtRejection` as
// eventually consumed.
static void AddConsumedRejection(Promise&);
#endif // SPIDERMONKEY_PROMISE
// Propagate the informations from AddUncaughtRejection
// and AddConsumedRejection to observers.
// XXXbz figure out the plan.
static void FlushUncaughtRejections();
protected:
-36
View File
@@ -1,36 +0,0 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_dom_PromiseNativeAbortCallback_h
#define mozilla_dom_PromiseNativeAbortCallback_h
#include "nsISupports.h"
namespace mozilla {
namespace dom {
/*
* PromiseNativeAbortCallback allows C++ to react to an AbortablePromise being
* aborted.
*/
class PromiseNativeAbortCallback : public nsISupports
{
protected:
virtual ~PromiseNativeAbortCallback()
{ }
public:
// Implemented in AbortablePromise.cpp.
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(PromiseNativeAbortCallback)
virtual void Call() = 0;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_PromiseNativeAbortCallback_h
+26
View File
@@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/PromiseBinding.h"
namespace mozilla {
namespace dom {
#ifdef SPIDERMONKEY_PROMISE
bool
PromiseNativeHandler::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aWrapper)
{
return PromiseNativeHandlerBinding::Wrap(aCx, this, aGivenProto, aWrapper);
}
#endif // SPIDERMONKEY_PROMISE
} // namespace dom
} // namespace mozilla
+6
View File
@@ -30,6 +30,12 @@ public:
virtual void
RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) = 0;
#ifdef SPIDERMONKEY_PROMISE
bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto,
JS::MutableHandle<JSObject*> aWrapper);
#endif // SPIDERMONKEY_PROMISE
};
} // namespace dom
+2 -4
View File
@@ -5,19 +5,17 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom += [
'AbortablePromise.h',
'Promise.h',
'PromiseDebugging.h',
'PromiseNativeAbortCallback.h',
'PromiseNativeHandler.h',
'PromiseWorkerProxy.h',
]
UNIFIED_SOURCES += [
'AbortablePromise.cpp',
'Promise.cpp',
'PromiseCallback.cpp',
'PromiseDebugging.cpp'
'PromiseDebugging.cpp',
'PromiseNativeHandler.cpp',
]
LOCAL_INCLUDES += [
-1
View File
@@ -3,7 +3,6 @@
# to go in here, not chrome.ini, apparently.
support-files = file_promise_xrays.html
[test_abortable_promise.html]
[test_bug883683.html]
[test_promise.html]
[test_promise_utils.html]
@@ -1,116 +0,0 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<html>
<head>
<title>Promise.resolve(anything) Test</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript"><!--
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("untriaged");
SpecialPowers.pushPrefEnv({"set": [["dom.abortablepromise.enabled", true]]},
runTest);
var gTests = [testPending, testResolved, testRejected];
function runTest() {
if (gTests.length == 0) {
SimpleTest.finish();
return;
}
new Promise(gTests.shift()).then(runTest, SimpleTest.finish);
}
// Aborting pending promise should first reject the promise and then call the
// abortable callback.
// The test succeeds once both the rejection handler and the abort handler have
// been called.
function testPending(succeed, fail) {
var rejected = false;
var aborted = false;
var p = new MozAbortablePromise(function(resolve, reject) {
// Wait for a while so that the promise can be aborted before resolved.
SimpleTest.executeSoon(function() {
resolve(0);
});
}, function abortable() {
aborted = true;
ok(true, "Promise aborted.");
if (rejected) {
succeed();
}
});
p.then(function() {
ok(false, "Failed to abort pending promise.");
fail();
}, function(what) {
rejected = true;
var isAbortException = (what && what.name) == "NS_ERROR_ABORT";
ok(isAbortException, "Should have NS_ERROR_ABORT exception");
if (!isAbortException) {
fail();
}
if (aborted) {
succeed();
}
});
// Abort the promise on creation.
p.abort();
}
// Do nothing when aborting resolved promise.
function testResolved(succeed, fail) {
var p = new MozAbortablePromise(function(resolve, reject) {
resolve();
}, function abortable() {
ok(false, "Should not abort a resolved promise.");
fail();
});
p.then(function() {
ok(true, "Promise resolved.");
// Wait for a while to ensure abort handle won't be called.
setTimeout(succeed, 1000);
}, function(what) {
ok(false, "Failed to resolve promise: " + what);
fail();
});
p.abort();
}
// Do nothing when aborting rejected promise.
function testRejected(succeed, fail) {
var p = new MozAbortablePromise(function(resolve, reject) {
reject(0);
}, function abortable() {
ok(false, "Should not abort a rejected promise.");
fail();
});
p.then(function() {
ok(false, "Failed to reject promise.");
fail();
}, function(what) {
is(what, 0, "promise rejected: " + what);
// Wait for a while to ensure abort handle won't be called.
setTimeout(succeed, 1000);
});
p.abort();
}
// -->
</script>
</pre>
</body>
</html>
@@ -118,8 +118,6 @@ var legacyMozPrefixedInterfaces =
// IMPORTANT: Do not change the list below without review from a DOM peer!
var interfaceNamesInGlobalScope =
[
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "MozAbortablePromise", disabled: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "AlarmsManager", b2g: true},
// IMPORTANT: Do not change this list without review from a DOM peer!
-19
View File
@@ -1,19 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
callback AbortCallback = void ();
[Constructor(PromiseInit init, AbortCallback abortCallback),
Pref="dom.abortablepromise.enabled"]
interface MozAbortablePromise : _Promise {
/*
* Aborts the promise.
* If the promise has not been resolved or rejected, it should be rejected
* with an Exception of type abort and then AbortCallback is called
* asynchronously. Otherwise, nothing should be done.
*/
void abort();
};
+1
View File
@@ -9,6 +9,7 @@
interface DummyInterface {
void lifecycleCallbacks(optional LifecycleCallbacks arg);
void promiseJobCallback(PromiseJobCallback arg);
};
interface DummyInterfaceWorkers {
+20 -1
View File
@@ -12,10 +12,15 @@
// function"; for now, we just use "object".
callback PromiseInit = void (object resolve, object reject);
callback PromiseJobCallback = void();
[TreatNonCallableAsNull]
callback AnyCallback = any (any value);
// REMOVE THE RELEVANT ENTRY FROM test_interfaces.html WHEN THIS IS IMPLEMENTED IN JS.
// When using SpiderMonkey promises, we don't want to define all this stuff;
// just define a tiny interface to make codegen of Promise arguments and return
// values work.
#ifndef SPIDERMONKEY_PROMISE
[Constructor(PromiseInit init),
Exposed=(Window,Worker,System)]
// Need to escape "Promise" so it's treated as an identifier.
@@ -56,3 +61,17 @@ interface _Promise {
[NewObject, Throws]
static any race(optional any iterable);
};
#else // SPIDERMONKEY_PROMISE
[NoInterfaceObject,
Exposed=(Window,Worker,System)]
// Need to escape "Promise" so it's treated as an identifier.
interface _Promise {
};
// Hack to allow us to have JS owning and properly tracing/CCing/etc a
// PromiseNativeHandler.
[NoInterfaceObject,
Exposed=(Window,Worker,System)]
interface PromiseNativeHandler {
};
#endif // SPIDERMONKEY_PROMISE
+3
View File
@@ -52,6 +52,7 @@ callback interface UncaughtRejectionObserver {
[ChromeOnly, Exposed=(Window,System)]
interface PromiseDebugging {
#ifndef SPIDERMONKEY_PROMISE
/**
* The various functions on this interface all expect to take promises but
* don't want the WebIDL behavior of assimilating random passed-in objects
@@ -130,6 +131,8 @@ interface PromiseDebugging {
[Throws]
static DOMHighResTimeStamp getTimeToSettle(object p);
#endif // SPIDERMONKEY_PROMISE
/**
* Watching uncaught rejections on the current thread.
*
+2 -3
View File
@@ -11,12 +11,13 @@ GENERATED_WEBIDL_FILES = [
PREPROCESSED_WEBIDL_FILES = [
'HTMLMediaElement.webidl',
'Navigator.webidl',
'Promise.webidl',
'PromiseDebugging.webidl',
'ServiceWorkerRegistration.webidl',
'Window.webidl',
]
WEBIDL_FILES = [
'AbortablePromise.webidl',
'AbstractWorker.webidl',
'ActivityRequestHandler.webidl',
'AlarmsManager.webidl',
@@ -389,8 +390,6 @@ WEBIDL_FILES = [
'PresentationSession.webidl',
'ProcessingInstruction.webidl',
'ProfileTimelineMarker.webidl',
'Promise.webidl',
'PromiseDebugging.webidl',
'PropertyIndexedKeyframes.webidl',
'RadioNodeList.webidl',
'Range.webidl',
+1 -1
View File
@@ -36,7 +36,7 @@ class ZoneHeapThreshold
double gcHeapGrowthFactor_;
// GC trigger threshold for allocations on the GC heap.
size_t gcTriggerBytes_;
mozilla::Atomic<size_t, mozilla::Relaxed> gcTriggerBytes_;
public:
ZoneHeapThreshold()
+1 -2
View File
@@ -8661,8 +8661,7 @@ AssertIsPromise(JSContext* cx, HandleObject promise)
{
MOZ_ASSERT(promise);
assertSameCompartment(cx, promise);
MOZ_ASSERT(strcmp(promise->getClass()->name, "Promise") == 0 ||
strcmp(promise->getClass()->name, "MozAbortablePromise") == 0);
MOZ_ASSERT(strcmp(promise->getClass()->name, "Promise") == 0);
}
JS_PUBLIC_API(void)
+4 -3
View File
@@ -293,9 +293,8 @@ SandboxFetch(JSContext* cx, JS::HandleObject scope, const CallArgs& args)
if (rv.MaybeSetPendingException(cx)) {
return false;
}
if (!GetOrCreateDOMReflector(cx, response, args.rval())) {
return false;
}
args.rval().setObject(*response->PromiseObj());
return true;
}
@@ -1161,10 +1160,12 @@ xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prin
if (!options.globalProperties.Define(cx, sandbox))
return NS_ERROR_XPC_UNEXPECTED;
#ifndef SPIDERMONKEY_PROMISE
// Promise is supposed to be part of ES, and therefore should appear on
// every global.
if (!dom::PromiseBinding::GetConstructorObject(cx, sandbox))
return NS_ERROR_XPC_UNEXPECTED;
#endif // SPIDERMONKEY_PROMISE
}
// We handle the case where the context isn't in a compartment for the
+30
View File
@@ -13,6 +13,7 @@
#include "nsIAtom.h"
#include "nsWrapperCache.h"
#include "nsJSUtils.h"
#include "nsQueryObject.h"
#include "WrapperFactory.h"
#include "nsWrapperCacheInlines.h"
@@ -25,6 +26,7 @@
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/PrimitiveConversions.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
using namespace xpc;
@@ -792,6 +794,21 @@ XPCConvert::NativeInterface2JSObject(MutableHandleValue d,
return CreateHolderIfNeeded(flat, d, dest);
}
#ifdef SPIDERMONKEY_PROMISE
if (iid->Equals(NS_GET_IID(nsISupports))) {
// Check for a Promise being returned via nsISupports. In that
// situation, we want to dig out its underlying JS object and return
// that.
RefPtr<Promise> promise = do_QueryObject(aHelper.Object());
if (promise) {
flat = promise->PromiseObj();
if (!JS_WrapObject(cx, &flat))
return false;
return CreateHolderIfNeeded(flat, d, dest);
}
}
#endif // SPIDERMONKEY_PROMISE
// Don't double wrap CPOWs. This is a temporary measure for compatibility
// with objects that don't provide necessary QIs (such as objects under
// the new DOM bindings). We expect the other side of the CPOW to have
@@ -925,6 +942,19 @@ XPCConvert::JSObject2NativeInterface(void** dest, HandleObject src,
if (GetISupportsFromJSObject(inner ? inner : src, &iface)) {
return iface && NS_SUCCEEDED(iface->QueryInterface(*iid, dest));
}
#ifdef SPIDERMONKEY_PROMISE
// Deal with Promises being passed as nsISupports. In that situation we
// want to create a dom::Promise and use that.
if (iid->Equals(NS_GET_IID(nsISupports))) {
RootedObject innerObj(cx, inner);
if (IsPromiseObject(innerObj)) {
nsIGlobalObject* glob = NativeGlobal(innerObj);
RefPtr<Promise> p = Promise::CreateFromExisting(glob, innerObj);
return p && NS_SUCCEEDED(p->QueryInterface(*iid, dest));
}
}
#endif // SPIDERMONKEY_PROMISE
}
RefPtr<nsXPCWrappedJS> wrapper;
-3
View File
@@ -113,9 +113,6 @@ pref("browser.cache.compression_level", 3);
pref("browser.download.saveZoneInformation", 2);
#endif
// Whether or not MozAbortablePromise is enabled.
pref("dom.abortablepromise.enabled", false);
// Whether or not testing features are enabled.
pref("dom.quotaManager.testing", false);
@@ -1,11 +0,0 @@
[document.title-09.html]
type: testharness
[No title element in SVG document]
expected: FAIL
[Title element in SVG document]
expected: FAIL
[Title element not child of SVG root]
expected: FAIL
@@ -0,0 +1,97 @@
<!DOCTYPE html>
<link rel="help" href="https://html.spec.whatwg.org/multipage/#document.title">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
function newSVGDocument() {
return document.implementation.createDocument(SVG_NAMESPACE, "svg", null);
}
function assertIsSVGTitle(element, expectedText) {
assert_equals(element.namespaceURI, SVG_NAMESPACE);
assert_equals(element.localName, "title");
assert_equals(element.textContent, expectedText);
}
test(function() {
var doc = newSVGDocument();
assert_equals(doc.title, "");
var child = doc.createElementNS(SVG_NAMESPACE, "x-child");
doc.documentElement.appendChild(child);
doc.title = "foo";
assertIsSVGTitle(doc.documentElement.firstChild, "foo");
assert_equals(doc.title, "foo");
}, "No title element in SVG document");
test(function() {
var doc = newSVGDocument();
var title = doc.createElementNS(SVG_NAMESPACE, "title");
title.textContent = "foo";
doc.documentElement.appendChild(title)
assert_equals(doc.title, "foo");
doc.title += "bar";
assert_equals(title.textContent, "foobar");
assert_equals(title.childNodes.length, 1);
assert_true(title.childNodes[0] instanceof Text);
assert_equals(doc.title, "foobar");
doc.title = "";
assert_equals(title.textContent, "");
assert_equals(doc.title, "");
assert_equals(title.childNodes.length, 0);
}, "Title element in SVG document");
test(function() {
var doc = newSVGDocument();
var title = doc.createElementNS(SVG_NAMESPACE, "title");
title.textContent = "foo";
var child = doc.createElementNS(SVG_NAMESPACE, "x-child");
child.appendChild(title);
doc.documentElement.appendChild(child);
assert_equals(doc.title, "");
// Now test that on setting, we create a new element and don't change the
// existing one
doc.title = "bar";
assert_equals(title.textContent, "foo");
assertIsSVGTitle(doc.documentElement.firstChild, "bar");
assert_equals(doc.title, "bar");
}, "Title element not child of SVG root");
test(function() {
var doc = newSVGDocument();
var title = doc.createElementNS(HTML_NAMESPACE, "title");
title.textContent = "foo";
doc.documentElement.appendChild(title);
assert_equals(doc.title, "");
}, "Title element not in SVG namespace");
test(function() {
// "SVG" != "svg"
var doc = document.implementation.createDocument(SVG_NAMESPACE, "SVG", null);
// Per spec, this does nothing
doc.title = "foo";
assert_equals(doc.documentElement.childNodes.length, 0);
assert_equals(doc.title, "");
// An SVG title is ignored by .title
doc.documentElement.appendChild(doc.createElementNS(SVG_NAMESPACE, "title"));
doc.documentElement.lastChild.textContent = "foo";
assert_equals(doc.title, "");
// But an HTML title is respected
doc.documentElement.appendChild(doc.createElementNS(HTML_NAMESPACE, "title"));
doc.documentElement.lastChild.textContent = "bar";
assert_equals(doc.title, "bar");
// Even if it's not a child of the root
var div = doc.createElementNS(HTML_NAMESPACE, "div");
div.appendChild(doc.documentElement.lastChild);
doc.documentElement.appendChild(div);
assert_equals(doc.title, "bar");
}, 'Root element not named "svg"');
</script>
+44
View File
@@ -68,6 +68,7 @@
#include "mozilla/dom/DOMJSClass.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseBinding.h"
#include "mozilla/dom/ScriptSettings.h"
#include "jsprf.h"
#include "js/Debug.h"
@@ -468,6 +469,10 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSRuntime* aParentRuntime,
};
SetDOMCallbacks(mJSRuntime, &DOMcallbacks);
#ifdef SPIDERMONKEY_PROMISE
JS::SetEnqueuePromiseJobCallback(mJSRuntime, EnqueuePromiseJobCallback, this);
#endif // SPIDERMONKEY_PROMISE
JS::dbg::SetDebuggerMallocSizeOf(mJSRuntime, moz_malloc_size_of);
nsCycleCollector_registerJSRuntime(this);
@@ -864,6 +869,45 @@ CycleCollectedJSRuntime::ContextCallback(JSContext* aContext,
return self->CustomContextCallback(aContext, aOperation);
}
class PromiseJobRunnable final : public nsRunnable
{
public:
PromiseJobRunnable(JSContext* aCx, JS::HandleObject aCallback)
: mCallback(new PromiseJobCallback(aCx, aCallback, nullptr))
{
}
virtual ~PromiseJobRunnable()
{
}
protected:
NS_IMETHOD
Run() override
{
mCallback->Call();
return NS_OK;
}
private:
RefPtr<PromiseJobCallback> mCallback;
};
/* static */
bool
CycleCollectedJSRuntime::EnqueuePromiseJobCallback(JSContext* aCx,
JS::HandleObject aJob,
void* aData)
{
CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
MOZ_ASSERT(JS_GetRuntime(aCx) == self->Runtime());
MOZ_ASSERT(Get() == self);
nsCOMPtr<nsIRunnable> runnable = new PromiseJobRunnable(aCx, aJob);
self->GetPromiseMicroTaskQueue().push(runnable);
return true;
}
struct JsGcTracer : public TraceCallbacks
{
virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
+3
View File
@@ -210,6 +210,9 @@ private:
static void LargeAllocationFailureCallback(void* aData);
static bool ContextCallback(JSContext* aCx, unsigned aOperation,
void* aData);
static bool EnqueuePromiseJobCallback(JSContext* aCx,
JS::HandleObject aJob,
void* aData);
virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
void TraceNativeGrayRoots(JSTracer* aTracer);