Merge tag 'FIREFOX_AURORA_47_BASE'

This commit is contained in:
Jacek Caban 2016-03-08 19:50:18 +01:00
commit cb8bf0787d
95 changed files with 3028 additions and 1015 deletions

View File

@ -48,6 +48,8 @@ support-files =
[browser_ext_tabs_sendMessage.js]
[browser_ext_tabs_move.js]
[browser_ext_tabs_move_window.js]
[browser_ext_tabs_move_window_multiple.js]
[browser_ext_tabs_move_window_pinned.js]
[browser_ext_tabs_onHighlighted.js]
[browser_ext_windows_create.js]
[browser_ext_windows_create_tabId.js]

View File

@ -40,82 +40,3 @@ add_task(function* () {
}
yield BrowserTestUtils.closeWindow(window1);
});
add_task(function* () {
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
let window1 = yield BrowserTestUtils.openNewBrowserWindow();
let tab1 = yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
window1.gBrowser.pinTab(tab1);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
},
background: function() {
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
let destination = tabs[0];
let source = tabs[1]; // remember, pinning moves it to the left.
browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
browser.test.assertEq(true, tabs[0].pinned);
browser.test.notifyPass("tabs.move.pin");
});
});
},
});
yield extension.startup();
yield extension.awaitFinish("tabs.move.pin");
yield extension.unload();
for (let tab of window.gBrowser.tabs) {
yield BrowserTestUtils.removeTab(tab);
}
yield BrowserTestUtils.closeWindow(window1);
});
add_task(function* () {
let window1 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, "http://example.net/");
yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, "http://example.com/");
yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.net/");
yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
},
background: function() {
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
let move1 = tabs[1];
let move3 = tabs[3];
browser.tabs.move([move1.id, move3.id], {index: 0});
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
browser.test.assertEq(tabs[0].url, move1.url);
browser.test.assertEq(tabs[2].url, move3.url);
browser.test.notifyPass("tabs.move.multiple");
});
});
},
});
yield extension.startup();
yield extension.awaitFinish("tabs.move.multiple");
yield extension.unload();
for (let tab of window.gBrowser.tabs) {
yield BrowserTestUtils.removeTab(tab);
}
yield BrowserTestUtils.closeWindow(window1);
});

View File

@ -0,0 +1,43 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* () {
let window1 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, "http://example.net/");
yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, "http://example.com/");
yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.net/");
yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
},
background: function() {
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
let move1 = tabs[1];
let move3 = tabs[3];
browser.tabs.move([move1.id, move3.id], {index: 0});
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
browser.test.assertEq(tabs[0].url, move1.url);
browser.test.assertEq(tabs[2].url, move3.url);
browser.test.notifyPass("tabs.move.multiple");
});
});
},
});
yield extension.startup();
yield extension.awaitFinish("tabs.move.multiple");
yield extension.unload();
for (let tab of window.gBrowser.tabs) {
yield BrowserTestUtils.removeTab(tab);
}
yield BrowserTestUtils.closeWindow(window1);
});

View File

@ -0,0 +1,42 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* () {
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.net/");
let window1 = yield BrowserTestUtils.openNewBrowserWindow();
let tab1 = yield BrowserTestUtils.openNewForegroundTab(window1.gBrowser, "http://example.com/");
window1.gBrowser.pinTab(tab1);
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"permissions": ["tabs"],
},
background: function() {
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
let destination = tabs[0];
let source = tabs[1]; // remember, pinning moves it to the left.
browser.tabs.move(source.id, {windowId: destination.windowId, index: 0});
browser.tabs.query(
{url: "<all_urls>"},
tabs => {
browser.test.assertEq(true, tabs[0].pinned);
browser.test.notifyPass("tabs.move.pin");
});
});
},
});
yield extension.startup();
yield extension.awaitFinish("tabs.move.pin");
yield extension.unload();
for (let tab of window.gBrowser.tabs) {
yield BrowserTestUtils.removeTab(tab);
}
yield BrowserTestUtils.closeWindow(window1);
});

View File

@ -240,9 +240,9 @@ if (typeof Mozilla == 'undefined') {
* @param {Object} extraURLCampaignParams - An object containing additional
* paramaters for the URL opened by the browser for reasons of promotional
* campaign tracking. Each attribute of the object must have a name that
* begins with "utm_" and a value that is a string that contains only only
* alphanumeric characters, dashes or underscores. The values may be any
* string and will automatically be encoded.
* is a string, begins with "utm_" and contains only only alphanumeric
* characters, dashes or underscores. The values may be any string and will
* automatically be encoded.
*/
Mozilla.UITour.showFirefoxAccounts = function(extraURLCampaignParams) {
_sendEvent('showFirefoxAccounts', {

View File

@ -27,6 +27,8 @@
#include "nsProxyRelease.h"
#include "nsTArray.h"
#include "GeckoProfiler.h"
#include "nsContentTypeParser.h"
#include "nsCharSeparatedTokenizer.h"
#ifdef LOG
#undef LOG
@ -971,6 +973,11 @@ MediaRecorder::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
if (!IsTypeSupported(aInitDict.mMimeType)) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
object->SetOptions(aInitDict);
return object.forget();
@ -1005,6 +1012,11 @@ MediaRecorder::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
if (!IsTypeSupported(aInitDict.mMimeType)) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
RefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
aSrcOutput,
ownerWindow);
@ -1035,6 +1047,107 @@ MediaRecorder::SetOptions(const MediaRecorderOptions& aInitDict)
}
}
static char const *const gWebMAudioEncoderCodecs[3] = {
"vorbis",
"opus",
// no VP9 yet
nullptr,
};
static char const *const gWebMVideoEncoderCodecs[5] = {
"vorbis",
"opus",
"vp8",
"vp8.0",
// no VP9 yet
nullptr,
};
static char const *const gOggAudioEncoderCodecs[2] = {
"opus",
// we could support vorbis here too, but don't
nullptr,
};
template <class String>
static bool
CodecListContains(char const *const * aCodecs, const String& aCodec)
{
for (int32_t i = 0; aCodecs[i]; ++i) {
if (aCodec.EqualsASCII(aCodecs[i]))
return true;
}
return false;
}
/* static */
bool
MediaRecorder::IsTypeSupported(GlobalObject& aGlobal, const nsAString& aMIMEType)
{
return IsTypeSupported(aMIMEType);
}
/* static */
bool
MediaRecorder::IsTypeSupported(const nsAString& aMIMEType)
{
char const* const* codeclist = nullptr;
if (aMIMEType.IsEmpty()) {
return true;
}
nsContentTypeParser parser(aMIMEType);
nsAutoString mimeType;
nsresult rv = parser.GetType(mimeType);
if (NS_FAILED(rv)) {
return false;
}
// effectively a 'switch (mimeType) {'
if (mimeType.EqualsLiteral(AUDIO_OGG)) {
if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) {
codeclist = gOggAudioEncoderCodecs;
}
}
#ifdef MOZ_WEBM_ENCODER
else if (mimeType.EqualsLiteral(VIDEO_WEBM) &&
MediaEncoder::IsWebMEncoderEnabled()) {
codeclist = gWebMVideoEncoderCodecs;
}
#endif
#ifdef MOZ_OMX_ENCODER
// We're working on MP4 encoder support for desktop
else if (mimeType.EqualsLiteral(VIDEO_MP4) ||
mimeType.EqualsLiteral(AUDIO_3GPP) ||
mimeType.EqualsLiteral(AUDIO_3GPP2)) {
if (MediaEncoder::IsOMXEncoderEnabled()) {
// XXX check codecs for MP4/3GPP
return true;
}
}
#endif
// codecs don't matter if we don't support the container
if (!codeclist) {
return false;
}
// now filter on codecs, and if needed rescind support
nsAutoString codecstring;
rv = parser.GetParameter("codecs", codecstring);
nsTArray<nsString> codecs;
if (!ParseCodecsString(codecstring, codecs)) {
return false;
}
for (const nsString& codec : codecs) {
if (!CodecListContains(codeclist, codec)) {
// Totally unsupported codec
return false;
}
}
return true;
}
nsresult
MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
{

View File

@ -22,6 +22,7 @@ class ErrorResult;
class MediaInputPort;
struct MediaRecorderOptions;
class MediaStream;
class GlobalObject;
namespace dom {
@ -78,6 +79,9 @@ public:
// Return the current encoding MIME type selected by the MediaEncoder.
void GetMimeType(nsString &aMimeType);
static bool IsTypeSupported(GlobalObject& aGlobal, const nsAString& aType);
static bool IsTypeSupported(const nsAString& aType);
// Construct a recorder with a DOM media stream object as its source.
static already_AddRefed<MediaRecorder>
Constructor(const GlobalObject& aGlobal,

View File

@ -1287,15 +1287,6 @@ nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
}
} else {
#ifdef DEBUG
{
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
MOZ_ASSERT((loadInfo->GetSecurityMode() &
nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) == 0,
"can not enforce CORS when calling Open2()");
}
#endif
rv = mChannel->Open2(getter_AddRefs(mInput));
}
NS_ENSURE_SUCCESS(rv, rv);
@ -1360,10 +1351,6 @@ already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaResourceCallba
nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
NS_ENSURE_TRUE(loadGroup, nullptr);
nsSecurityFlags securityFlags = element->ShouldCheckAllowOrigin()
? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
@ -1375,7 +1362,7 @@ already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaResourceCallba
NS_NewChannel(getter_AddRefs(channel),
mURI,
element,
securityFlags,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
contentPolicyType,
loadGroup,
nullptr, // aCallbacks

View File

@ -732,6 +732,12 @@ tags=msg capturestream
tags=msg capturestream
[test_mediarecorder_unsupported_src.html]
tags=msg
[test_mediarecorder_webm_support.html]
skip-if = os == 'android' || arch == 'arm' || arch == 'arm64'
tags=msg
[test_mediarecorder_mp4_support.html]
skip-if = toolkit != 'gonk' || android_version < '17' # Android/Gonk before SDK version 17 does not have the OMX Encoder API.
tags=msg
[test_mediarecorder_record_getdata_afterstart.html]
tags=msg capturestream
[test_mediatrack_consuming_mediaresource.html]

View File

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Media Recording - test mp4 MIME support</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
ok(MediaRecorder.isTypeSupported('video/mp4'), 'Should support video/mp4');
</script>
</head>
</html>

View File

@ -10,7 +10,15 @@
<pre id="test">
<script class="testbody" type="text/javascript">
function startTest() {
// also do general checks on mimetype support for audio-only
ok(MediaRecorder.isTypeSupported("audio/ogg"), 'Should support audio/ogg');
ok(MediaRecorder.isTypeSupported('audio/ogg; codecs="opus"'), 'Should support audio/ogg+opus');
ok(!MediaRecorder.isTypeSupported('audio/ogg; codecs="foobar"'), 'Should not support audio/ogg + unknown_codec');
ok(!MediaRecorder.isTypeSupported("video/webm"), 'Should not support video/webm');
ok(!MediaRecorder.isTypeSupported("video/mp4"), 'Should not support video/mp4');
navigator.mozGetUserMedia({audio: false, video: true, fake: true},
function(stream) {

View File

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Media Recording - test WebM MIME support</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
ok(MediaRecorder.isTypeSupported('video/webm'), 'Should support video/webm');
ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8, vorbis"'), 'Should support video/webm + vp8/vorbis');
ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, vorbis"'), 'Should not support video/webm + vp9/vorbis');
</script>
</head>
</html>

View File

@ -151,6 +151,7 @@ SpeechTaskCallback::OnDidFinishSpeaking()
{
mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
// no longer needed
[mSpeechSynthesizer setDelegate:nil];
mTask = nullptr;
}

View File

@ -47,6 +47,8 @@ interface MediaRecorder : EventTarget {
[Throws]
void requestData();
static boolean isTypeSupported(DOMString type);
};
dictionary MediaRecorderOptions {

View File

@ -115,6 +115,12 @@ SVGDocumentWrapper::FlushImageTransformInvalidation()
bool
SVGDocumentWrapper::IsAnimated()
{
// Can be called for animated images during shutdown, after we've
// already Observe()'d XPCOM shutdown and cleared out our mViewer pointer.
if (!mViewer) {
return false;
}
nsIDocument* doc = mViewer->GetDocument();
if (!doc) {
return false;

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div style="content: url(data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20style%3D%22background%3A%20url%28data%3Aimage%2Fsvg%2Bxml%2C%253Csvg%2520xmlns%253D%2522http%253A%252F%252Fwww.w3.org%252F2000%252Fsvg%2522%253E%253C%252Fsvg%253E%29%22%3E%3C%2Fsvg%3E%0D%0A)"></div>
</body>
</html>

View File

@ -21,6 +21,7 @@ load 1242093-1.html
load 1242778-1.png
load 1249576-1.png
load 1251091-1.html
load 1253362-1.html
load colormap-range.gif
HTTP load delayedframe.sjs # A 3-frame animated GIF with an inordinate delay between the second and third frame

View File

@ -281,6 +281,192 @@ private:
CxxStackFrame& operator=(const CxxStackFrame&) = delete;
};
class AutoEnterTransaction
{
public:
explicit AutoEnterTransaction(MessageChannel *aChan,
int32_t aMsgSeqno,
int32_t aTransactionID,
int aPriority)
: mChan(aChan),
mActive(true),
mOutgoing(true),
mPriority(aPriority),
mSeqno(aMsgSeqno),
mTransaction(aTransactionID),
mNext(mChan->mTransactionStack)
{
mChan->mMonitor->AssertCurrentThreadOwns();
mChan->mTransactionStack = this;
}
explicit AutoEnterTransaction(MessageChannel *aChan, const IPC::Message &aMessage)
: mChan(aChan),
mActive(true),
mOutgoing(false),
mPriority(aMessage.priority()),
mSeqno(aMessage.seqno()),
mTransaction(aMessage.transaction_id()),
mNext(mChan->mTransactionStack)
{
mChan->mMonitor->AssertCurrentThreadOwns();
if (!aMessage.is_sync()) {
mActive = false;
return;
}
mChan->mTransactionStack = this;
}
~AutoEnterTransaction() {
mChan->mMonitor->AssertCurrentThreadOwns();
if (mActive) {
mChan->mTransactionStack = mNext;
}
}
void Cancel() {
AutoEnterTransaction *cur = mChan->mTransactionStack;
MOZ_RELEASE_ASSERT(cur == this);
while (cur && cur->mPriority != IPC::Message::PRIORITY_NORMAL) {
// Note that, in the following situation, we will cancel multiple
// transactions:
// 1. Parent sends high prio message P1 to child.
// 2. Child sends high prio message C1 to child.
// 3. Child dispatches P1, parent blocks.
// 4. Child cancels.
// In this case, both P1 and C1 are cancelled. The parent will
// remove C1 from its queue when it gets the cancellation message.
MOZ_RELEASE_ASSERT(cur->mActive);
cur->mActive = false;
cur = cur->mNext;
}
mChan->mTransactionStack = cur;
MOZ_RELEASE_ASSERT(IsComplete());
}
bool AwaitingSyncReply() const {
MOZ_RELEASE_ASSERT(mActive);
if (mOutgoing) {
return true;
}
return mNext ? mNext->AwaitingSyncReply() : false;
}
int AwaitingSyncReplyPriority() const {
MOZ_RELEASE_ASSERT(mActive);
if (mOutgoing) {
return mPriority;
}
return mNext ? mNext->AwaitingSyncReplyPriority() : 0;
}
bool DispatchingSyncMessage() const {
MOZ_RELEASE_ASSERT(mActive);
if (!mOutgoing) {
return true;
}
return mNext ? mNext->DispatchingSyncMessage() : false;
}
int DispatchingSyncMessagePriority() const {
MOZ_RELEASE_ASSERT(mActive);
if (!mOutgoing) {
return mPriority;
}
return mNext ? mNext->DispatchingSyncMessagePriority() : 0;
}
int Priority() const {
MOZ_RELEASE_ASSERT(mActive);
return mPriority;
}
int32_t SequenceNumber() const {
MOZ_RELEASE_ASSERT(mActive);
return mSeqno;
}
int32_t TransactionID() const {
MOZ_RELEASE_ASSERT(mActive);
return mTransaction;
}
void ReceivedReply(const IPC::Message& aMessage) {
MOZ_RELEASE_ASSERT(aMessage.seqno() == mSeqno);
MOZ_RELEASE_ASSERT(aMessage.transaction_id() == mTransaction);
MOZ_RELEASE_ASSERT(!mReply);
IPC_LOG("Reply received on worker thread: seqno=%d", mSeqno);
mReply = new IPC::Message(aMessage);
MOZ_RELEASE_ASSERT(IsComplete());
}
void HandleReply(const IPC::Message& aMessage) {
AutoEnterTransaction *cur = mChan->mTransactionStack;
MOZ_RELEASE_ASSERT(cur == this);
while (cur) {
MOZ_RELEASE_ASSERT(cur->mActive);
if (aMessage.seqno() == cur->mSeqno) {
cur->ReceivedReply(aMessage);
break;
}
cur = cur->mNext;
MOZ_RELEASE_ASSERT(cur);
}
}
bool IsComplete() {
return !mActive || mReply;
}
bool IsOutgoing() {
return mOutgoing;
}
bool IsCanceled() {
return !mActive;
}
bool IsBottom() const {
return !mNext;
}
bool IsError() {
MOZ_RELEASE_ASSERT(mReply);
return mReply->is_reply_error();
}
nsAutoPtr<IPC::Message> GetReply() {
return Move(mReply);
}
private:
MessageChannel *mChan;
// Active is true if this transaction is on the mChan->mTransactionStack
// stack. Generally we're not on the stack if the transaction was canceled
// or if it was for a message that doesn't require transactions (an async
// message).
bool mActive;
// Is this stack frame for an outgoing message?
bool mOutgoing;
// Properties of the message being sent/received.
int mPriority;
int32_t mSeqno;
int32_t mTransaction;
// Next item in mChan->mTransactionStack.
AutoEnterTransaction *mNext;
// Pointer the a reply received for this message, if one was received.
nsAutoPtr<IPC::Message> mReply;
};
MessageChannel::MessageChannel(MessageListener *aListener)
: mListener(aListener),
mChannelState(ChannelClosed),
@ -293,17 +479,11 @@ MessageChannel::MessageChannel(MessageListener *aListener)
mInTimeoutSecondHalf(false),
mNextSeqno(0),
mLastSendError(SyncSendError::SendSuccess),
mAwaitingSyncReply(false),
mAwaitingSyncReplyPriority(0),
mDispatchingSyncMessage(false),
mDispatchingSyncMessagePriority(0),
mDispatchingAsyncMessage(false),
mDispatchingAsyncMessagePriority(0),
mCurrentTransaction(0),
mPendingSendPriorities(0),
mTransactionStack(nullptr),
mTimedOutMessageSeqno(0),
mTimedOutMessagePriority(0),
mRecvdErrors(0),
mRemoteStackDepthGuess(false),
mSawInterruptOutMsg(false),
mIsWaitingForIncoming(false),
@ -344,6 +524,51 @@ MessageChannel::~MessageChannel()
Clear();
}
// This function returns the current transaction ID. Since the notion of a
// "current transaction" can be hard to define when messages race with each
// other and one gets canceled and the other doesn't, we require that this
// function is only called when the current transaction is known to be for a
// high priority message. In that case, we know for sure what the caller is
// looking for.
int32_t
MessageChannel::CurrentHighPriorityTransaction() const
{
mMonitor->AssertCurrentThreadOwns();
if (!mTransactionStack) {
return 0;
}
MOZ_RELEASE_ASSERT(mTransactionStack->Priority() == IPC::Message::PRIORITY_HIGH);
return mTransactionStack->TransactionID();
}
bool
MessageChannel::AwaitingSyncReply() const
{
mMonitor->AssertCurrentThreadOwns();
return mTransactionStack ? mTransactionStack->AwaitingSyncReply() : false;
}
int
MessageChannel::AwaitingSyncReplyPriority() const
{
mMonitor->AssertCurrentThreadOwns();
return mTransactionStack ? mTransactionStack->AwaitingSyncReplyPriority() : 0;
}
bool
MessageChannel::DispatchingSyncMessage() const
{
mMonitor->AssertCurrentThreadOwns();
return mTransactionStack ? mTransactionStack->DispatchingSyncMessage() : false;
}
int
MessageChannel::DispatchingSyncMessagePriority() const
{
mMonitor->AssertCurrentThreadOwns();
return mTransactionStack ? mTransactionStack->DispatchingSyncMessagePriority() : 0;
}
static void
PrintErrorMessage(Side side, const char* channelName, const char* msg)
{
@ -405,7 +630,6 @@ MessageChannel::Clear()
// Free up any memory used by pending messages.
mPending.clear();
mRecvd = nullptr;
mOutOfTurnReplies.clear();
while (!mDeferred.empty()) {
mDeferred.pop();
@ -622,7 +846,7 @@ MessageChannel::ShouldDeferMessage(const Message& aMsg)
// child's message comes in, we can pretend the child hasn't quite
// finished sending it yet. Since the message is sync, we know that the
// child hasn't moved on yet.
return mSide == ParentSide && aMsg.transaction_id() != mCurrentTransaction;
return mSide == ParentSide && aMsg.transaction_id() != CurrentHighPriorityTransaction();
}
// Predicate that is true for messages that should be consolidated if 'compress' is set.
@ -659,26 +883,10 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg)
return;
}
MOZ_RELEASE_ASSERT(aMsg.transaction_id() == mCurrentTransaction);
MOZ_RELEASE_ASSERT(AwaitingSyncReply());
MOZ_RELEASE_ASSERT(!mRecvd);
MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
// Rather than storing errors in mRecvd, we mark them in
// mRecvdErrors. We need a counter because multiple replies can arrive
// when a timeout happens, as in the following example. Imagine the
// child is running slowly. The parent sends a sync message P1. It times
// out. The child eventually sends a sync message C1. While waiting for
// the C1 response, the child dispatches P1. In doing so, it sends sync
// message C2. At that point, it's valid for the parent to send error
// responses for both C1 and C2.
if (aMsg.is_reply_error()) {
mRecvdErrors++;
NotifyWorkerThread();
return;
}
mRecvd = new Message(aMsg);
mTransactionStack->HandleReply(aMsg);
NotifyWorkerThread();
return;
}
@ -768,8 +976,11 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg)
}
void
MessageChannel::ProcessPendingRequests(int seqno, int transaction)
MessageChannel::ProcessPendingRequests(AutoEnterTransaction& aTransaction)
{
int32_t seqno = aTransaction.SequenceNumber();
int32_t transaction = aTransaction.TransactionID();
IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d", seqno, transaction);
// Loop until there aren't any more priority messages to process.
@ -779,7 +990,7 @@ MessageChannel::ProcessPendingRequests(int seqno, int transaction)
// operating with weird state (as if no Send is in progress). That could
// cause even normal priority sync messages to be processed (but not
// normal priority async messages), which would break message ordering.
if (WasTransactionCanceled(transaction)) {
if (aTransaction.IsCanceled()) {
return;
}
@ -788,7 +999,7 @@ MessageChannel::ProcessPendingRequests(int seqno, int transaction)
for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
Message &msg = *it;
MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction,
MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
"Calling ShouldDeferMessage when cancelled");
bool defer = ShouldDeferMessage(msg);
@ -806,36 +1017,17 @@ MessageChannel::ProcessPendingRequests(int seqno, int transaction)
it++;
}
if (toProcess.empty())
if (toProcess.empty()) {
break;
}
// Processing these messages could result in more messages, so we
// loop around to check for more afterwards.
for (auto it = toProcess.begin(); it != toProcess.end(); it++)
for (auto it = toProcess.begin(); it != toProcess.end(); it++) {
ProcessPendingRequest(*it);
}
}
bool
MessageChannel::WasTransactionCanceled(int transaction)
{
if (transaction != mCurrentTransaction) {
// Imagine this scenario:
// 1. Child sends high prio sync message H1.
// 2. Parent sends reply to H1.
// 3. Parent sends high prio sync message H2.
// 4. Child's link thread receives H1 reply and H2 before worker wakes up.
// 5. Child dispatches H2 while still waiting for H1 reply.
// 6. Child cancels H2.
//
// In this case H1 will also be considered cancelled. However, its
// reply is still sitting in mRecvd, which can trip up later Sends. So
// we null it out here.
mRecvd = nullptr;
return true;
}
return false;
}
bool
@ -866,8 +1058,7 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
return false;
}
if (mCurrentTransaction &&
DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
msg->priority() > IPC::Message::PRIORITY_NORMAL)
{
// Don't allow sending CPOWs while we're dispatching a sync message.
@ -877,9 +1068,8 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
return false;
}
if (mCurrentTransaction &&
(DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT))
if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
{
// Generally only the parent dispatches urgent messages. And the only
// sync messages it can send are high-priority. Mainly we want to ensure
@ -890,27 +1080,24 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
return false;
}
if (mCurrentTransaction &&
(msg->priority() < DispatchingSyncMessagePriority() ||
msg->priority() < AwaitingSyncReplyPriority()))
if (msg->priority() < DispatchingSyncMessagePriority() ||
msg->priority() < AwaitingSyncReplyPriority())
{
MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
IPC_LOG("Cancel from Send");
CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
CancelTransaction(mCurrentTransaction);
CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
CancelTransaction(CurrentHighPriorityTransaction());
mLink->SendMessage(cancel);
}
IPC_ASSERT(msg->is_sync(), "can only Send() sync messages here");
if (mCurrentTransaction) {
IPC_ASSERT(msg->priority() >= DispatchingSyncMessagePriority(),
"can't send sync message of a lesser priority than what's being dispatched");
IPC_ASSERT(AwaitingSyncReplyPriority() <= msg->priority(),
"nested sync message sends must be of increasing priority");
IPC_ASSERT(DispatchingSyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
"not allowed to send messages while dispatching urgent messages");
}
IPC_ASSERT(DispatchingAsyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
"not allowed to send messages while dispatching urgent messages");
@ -927,27 +1114,28 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
int prio = msg->priority();
msgid_t replyType = msg->type() + 1;
AutoSetValue<bool> replies(mAwaitingSyncReply, true);
AutoSetValue<int> prioSet(mAwaitingSyncReplyPriority, prio);
AutoEnterTransaction transact(this, seqno);
AutoEnterTransaction *stackTop = mTransactionStack;
int prios = mPendingSendPriorities | (1 << prio);
AutoSetValue<int> priosSet(mPendingSendPriorities, prios);
int32_t transaction = mCurrentTransaction;
// If the most recent message on the stack is high priority, then our
// message should nest inside that and we use the same transaction
// ID. Otherwise we need a new transaction ID (so we use the seqno of the
// message we're sending).
bool nest = stackTop && stackTop->Priority() == IPC::Message::PRIORITY_HIGH;
int32_t transaction = nest ? stackTop->TransactionID() : seqno;
msg->set_transaction_id(transaction);
IPC_LOG("Send seqno=%d, xid=%d, pending=%d", seqno, transaction, prios);
bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
AutoEnterTransaction transact(this, seqno, transaction, prio);
IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction);
mLink->SendMessage(msg.forget());
while (true) {
ProcessPendingRequests(seqno, transaction);
if (WasTransactionCanceled(transaction)) {
IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
mLastSendError = SyncSendError::CancelledAfterSend;
return false;
MOZ_RELEASE_ASSERT(!transact.IsCanceled());
ProcessPendingRequests(transact);
if (transact.IsComplete()) {
break;
}
if (!Connected()) {
ReportConnectionError("MessageChannel::Send");
@ -955,23 +1143,12 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
return false;
}
// See if we've received a reply.
if (mRecvdErrors) {
IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
mRecvdErrors--;
mLastSendError = SyncSendError::ReplyError;
return false;
}
if (mRecvd) {
IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
break;
}
MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
MOZ_RELEASE_ASSERT(!transact.IsComplete());
MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
if (mListener->NeedArtificialSleep()) {
MonitorAutoUnlock unlock(*mMonitor);
mListener->ArtificialSleep();
@ -983,31 +1160,21 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
return false;
}
if (WasTransactionCanceled(transaction)) {
IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
mLastSendError = SyncSendError::CancelledAfterSend;
return false;
if (transact.IsCanceled()) {
break;
}
MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
// We only time out a message if it initiated a new transaction (i.e.,
// if neither side has any other message Sends on the stack).
bool canTimeOut = transaction == seqno;
bool canTimeOut = transact.IsBottom();
if (maybeTimedOut && canTimeOut && !ShouldContinueFromTimeout()) {
// Since ShouldContinueFromTimeout drops the lock, we need to
// re-check all our conditions here. We shouldn't time out if any of
// these things happen because there won't be a reply to the timed
// out message in these cases.
if (WasTransactionCanceled(transaction)) {
IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
mLastSendError = SyncSendError::CancelledAfterSend;
return false;
}
if (mRecvdErrors) {
mRecvdErrors--;
mLastSendError = SyncSendError::ReplyError;
return false;
}
if (mRecvd) {
if (transact.IsComplete()) {
break;
}
@ -1018,18 +1185,36 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
mLastSendError = SyncSendError::TimedOut;
return false;
}
if (transact.IsCanceled()) {
break;
}
}
MOZ_RELEASE_ASSERT(mRecvd);
MOZ_RELEASE_ASSERT(mRecvd->is_reply(), "expected reply");
MOZ_RELEASE_ASSERT(!mRecvd->is_reply_error());
MOZ_RELEASE_ASSERT(mRecvd->seqno() == seqno);
MOZ_RELEASE_ASSERT(mRecvd->type() == replyType, "wrong reply type");
MOZ_RELEASE_ASSERT(mRecvd->is_sync());
if (transact.IsCanceled()) {
IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
mLastSendError = SyncSendError::CancelledAfterSend;
return false;
}
*aReply = Move(*mRecvd);
mRecvd = nullptr;
mLastSendError = SyncSendError::SendSuccess;
if (transact.IsError()) {
IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
mLastSendError = SyncSendError::ReplyError;
return false;
}
IPC_LOG("Got reply: seqno=%d, xid=%d", seqno, transaction);
nsAutoPtr<Message> reply = transact.GetReply();
MOZ_RELEASE_ASSERT(reply);
MOZ_RELEASE_ASSERT(reply->is_reply(), "expected reply");
MOZ_RELEASE_ASSERT(!reply->is_reply_error());
MOZ_RELEASE_ASSERT(reply->seqno() == seqno);
MOZ_RELEASE_ASSERT(reply->type() == replyType, "wrong reply type");
MOZ_RELEASE_ASSERT(reply->is_sync());
*aReply = Move(*reply);
return true;
}
@ -1252,16 +1437,6 @@ MessageChannel::ProcessPendingRequest(const Message &aUrgent)
AssertWorkerThread();
mMonitor->AssertCurrentThreadOwns();
// Note that it is possible we could have sent a sync message at
// the same time the parent process sent an urgent message, and
// therefore mPendingUrgentRequest is set *and* mRecvd is set as
// well, because the link thread received both before the worker
// thread woke up.
//
// In this case, we process the urgent message first, but we need
// to save the reply.
nsAutoPtr<Message> savedReply(mRecvd.forget());
IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent.seqno(), aUrgent.transaction_id());
DispatchMessage(aUrgent);
@ -1270,13 +1445,6 @@ MessageChannel::ProcessPendingRequest(const Message &aUrgent)
return false;
}
// In between having dispatched our reply to the parent process, and
// re-acquiring the monitor, the parent process could have already
// processed that reply and sent the reply to our sync message. If so,
// our saved reply should be empty.
IPC_ASSERT(!mRecvd || !savedReply, "unknown reply");
if (!mRecvd)
mRecvd = savedReply.forget();
return true;
}
@ -1352,8 +1520,6 @@ MessageChannel::OnMaybeDequeueOne()
return false;
}
// We should not be in a transaction yet if we're not blocked.
MOZ_RELEASE_ASSERT(mCurrentTransaction == 0);
DispatchMessage(recvd);
return true;
@ -1374,7 +1540,7 @@ MessageChannel::DispatchMessage(const Message &aMsg)
AutoEnterTransaction transaction(this, aMsg);
int id = aMsg.transaction_id();
MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == mCurrentTransaction);
MOZ_RELEASE_ASSERT(!aMsg.is_sync() || id == transaction.TransactionID());
{
MonitorAutoUnlock unlock(*mMonitor);
@ -1392,7 +1558,7 @@ MessageChannel::DispatchMessage(const Message &aMsg)
mListener->ArtificialSleep();
}
if (mCurrentTransaction != id) {
if (reply && transaction.IsCanceled()) {
// The transaction has been canceled. Don't send a reply.
IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d", aMsg.seqno(), id);
reply = nullptr;
@ -1420,8 +1586,6 @@ MessageChannel::DispatchSyncMessage(const Message& aMsg, Message*& aReply)
Result rv;
{
AutoSetValue<MessageChannel*> blocked(blockingVar, this);
AutoSetValue<bool> sync(mDispatchingSyncMessage, true);
AutoSetValue<int> prioSet(mDispatchingSyncMessagePriority, prio);
rv = mListener->OnMessageReceived(aMsg, aReply);
}
@ -2123,38 +2287,7 @@ MessageChannel::CancelTransaction(int transaction)
// tampered with (by us). If so, they don't reset the variable to the old
// value.
IPC_LOG("CancelTransaction: xid=%d prios=%d", transaction, mPendingSendPriorities);
if (mPendingSendPriorities & (1 << IPC::Message::PRIORITY_NORMAL)) {
// This isn't an assert so much as an intentional crash because we're in
// a situation that we don't know how to recover from: The child is
// awaiting a reply to a normal-priority sync message. The transaction
// that this message initiated has now been canceled. That could only
// happen if a CPOW raced with the sync message and was dispatched by
// the child while the child was awaiting the sync reply; at some point
// while dispatching the CPOW, the transaction was canceled.
//
// Notes:
//
// 1. We don't want to cancel the normal-priority sync message along
// with the CPOWs because the browser relies on these messages working
// reliably.
//
// 2. Ideally we would like to avoid dispatching CPOWs while awaiting a
// sync response. This isn't possible though. To avoid deadlock, the
// parent would have to dispatch the sync message while waiting for the
// CPOW response. However, it wouldn't have dispatched async messages at
// that time, so we would have a message ordering bug. Dispatching the
// async messages first causes other hard-to-handle situations (what if
// they send CPOWs?).
//
// 3. We would like to be able to cancel the CPOWs but not the sync
// message. However, that would leave both the parent and the child
// running code at the same time, all while the sync message is still
// outstanding. That can cause a problem where message replies are
// received out of order.
mListener->IntentionalCrash();
}
IPC_LOG("CancelTransaction: xid=%d", transaction);
// An unusual case: We timed out a transaction which the other side then
// cancelled. In this case we just leave the timedout state and try to
@ -2168,17 +2301,13 @@ MessageChannel::CancelTransaction(int transaction)
// 2. Parent times out H.
// 3. Child dispatches H and sends nested message H' (same transaction).
// 4. Parent dispatches H' and cancels.
MOZ_RELEASE_ASSERT(!mCurrentTransaction || mCurrentTransaction == transaction);
mCurrentTransaction = 0;
// During a timeout Send should always fail.
MOZ_RELEASE_ASSERT(!mAwaitingSyncReply);
MOZ_RELEASE_ASSERT(!mTransactionStack || mTransactionStack->TransactionID() == transaction);
if (mTransactionStack) {
mTransactionStack->Cancel();
}
} else {
MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
mCurrentTransaction = 0;
mAwaitingSyncReply = false;
mAwaitingSyncReplyPriority = 0;
MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction);
mTransactionStack->Cancel();
}
bool foundSync = false;
@ -2187,11 +2316,8 @@ MessageChannel::CancelTransaction(int transaction)
// If there was a race between the parent and the child, then we may
// have a queued sync message. We want to drop this message from the
// queue since it will get cancelled along with the transaction being
// cancelled. We don't bother doing this for normal priority messages
// because the child is just going to crash in that case, and we want to
// avoid processing messages out of order in the short time before it
// crashes.
// queue since if will get cancelled along with the transaction being
// cancelled. This happens if the message in the queue is high priority.
if (msg.is_sync() && msg.priority() != IPC::Message::PRIORITY_NORMAL) {
MOZ_RELEASE_ASSERT(!foundSync);
MOZ_RELEASE_ASSERT(msg.transaction_id() != transaction);
@ -2201,36 +2327,32 @@ MessageChannel::CancelTransaction(int transaction)
continue;
}
// There may be messages in the queue that we expected to process from
// ProcessPendingRequests. However, Send will no longer call that
// function once it's been canceled. So we may need to process these
// messages in the normal event loop instead.
mWorkerLoop->PostTask(FROM_HERE, new DequeueTask(mDequeueOneTask));
it++;
}
}
// We could also zero out mDispatchingSyncMessage here. However, that would
// cause a race because mDispatchingSyncMessage is a worker-thread-only
// field and we can be called on the I/O thread. Luckily, we can check to
// see if mCurrentTransaction is 0 before examining DispatchSyncMessage.
bool
MessageChannel::IsInTransaction() const
{
MonitorAutoLock lock(*mMonitor);
return !!mTransactionStack;
}
void
MessageChannel::CancelCurrentTransaction()
{
MonitorAutoLock lock(*mMonitor);
if (mCurrentTransaction) {
if (DispatchingSyncMessagePriority() >= IPC::Message::PRIORITY_HIGH) {
if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
{
mListener->IntentionalCrash();
}
IPC_LOG("Cancel requested: current xid=%d", mCurrentTransaction);
IPC_LOG("Cancel requested: current xid=%d", CurrentHighPriorityTransaction());
MOZ_RELEASE_ASSERT(DispatchingSyncMessage());
CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
CancelTransaction(mCurrentTransaction);
CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
CancelTransaction(CurrentHighPriorityTransaction());
mLink->SendMessage(cancel);
}
}

View File

@ -57,6 +57,8 @@ enum class SyncSendError {
ReplyError,
};
class AutoEnterTransaction;
class MessageChannel : HasResultCodes
{
friend class ProcessLink;
@ -156,7 +158,7 @@ class MessageChannel : HasResultCodes
return !mCxxStackFrames.empty();
}
bool IsInTransaction() const { return mCurrentTransaction != 0; }
bool IsInTransaction() const;
void CancelCurrentTransaction();
/**
@ -263,7 +265,7 @@ class MessageChannel : HasResultCodes
bool InterruptEventOccurred();
bool HasPendingEvents();
void ProcessPendingRequests(int seqno, int transaction);
void ProcessPendingRequests(AutoEnterTransaction& aTransaction);
bool ProcessPendingRequest(const Message &aUrgent);
void MaybeUndeferIncall();
@ -366,15 +368,6 @@ class MessageChannel : HasResultCodes
return mInterruptStack.size();
}
// Returns true if we're blocking waiting for a reply.
bool AwaitingSyncReply() const {
mMonitor->AssertCurrentThreadOwns();
return mAwaitingSyncReply;
}
int AwaitingSyncReplyPriority() const {
mMonitor->AssertCurrentThreadOwns();
return mAwaitingSyncReplyPriority;
}
bool AwaitingInterruptReply() const {
mMonitor->AssertCurrentThreadOwns();
return !mInterruptStack.empty();
@ -404,17 +397,7 @@ class MessageChannel : HasResultCodes
};
friend class AutoEnterWaitForIncoming;
// Returns true if we're dispatching a sync message's callback.
bool DispatchingSyncMessage() const {
AssertWorkerThread();
return mDispatchingSyncMessage;
}
int DispatchingSyncMessagePriority() const {
AssertWorkerThread();
return mDispatchingSyncMessagePriority;
}
// Returns true if we're dispatching an async message's callback.
bool DispatchingAsyncMessage() const {
AssertWorkerThread();
return mDispatchingAsyncMessage;
@ -560,15 +543,6 @@ class MessageChannel : HasResultCodes
T mNew;
};
// Worker thread only.
bool mAwaitingSyncReply;
int mAwaitingSyncReplyPriority;
// Set while we are dispatching a synchronous message. Only for use on the
// worker thread.
bool mDispatchingSyncMessage;
int mDispatchingSyncMessagePriority;
bool mDispatchingAsyncMessage;
int mDispatchingAsyncMessagePriority;
@ -590,56 +564,16 @@ class MessageChannel : HasResultCodes
// To ensure IDs are unique, we use sequence numbers for transaction IDs,
// which grow in opposite directions from child to parent.
// The current transaction ID.
int32_t mCurrentTransaction;
friend class AutoEnterTransaction;
AutoEnterTransaction *mTransactionStack;
// This field describes the priorities of the sync Send calls that are
// currently on stack. If a Send call for a message with priority P is on
// the C stack, then mPendingSendPriorities & (1 << P) will be
// non-zero. Note that cancelled Send calls are not removed from this
// bitfield (until they return).
int mPendingSendPriorities;
int32_t CurrentHighPriorityTransaction() const;
class AutoEnterTransaction
{
public:
explicit AutoEnterTransaction(MessageChannel *aChan, int32_t aMsgSeqno)
: mChan(aChan),
mNewTransaction(INT32_MAX),
mOldTransaction(mChan->mCurrentTransaction)
{
mChan->mMonitor->AssertCurrentThreadOwns();
if (mChan->mCurrentTransaction == 0) {
mNewTransaction = aMsgSeqno;
mChan->mCurrentTransaction = aMsgSeqno;
}
}
explicit AutoEnterTransaction(MessageChannel *aChan, const Message &aMessage)
: mChan(aChan),
mNewTransaction(aMessage.transaction_id()),
mOldTransaction(mChan->mCurrentTransaction)
{
mChan->mMonitor->AssertCurrentThreadOwns();
bool AwaitingSyncReply() const;
int AwaitingSyncReplyPriority() const;
if (!aMessage.is_sync())
return;
MOZ_DIAGNOSTIC_ASSERT(
!(mChan->mSide == ParentSide && mOldTransaction != aMessage.transaction_id()) ||
!mOldTransaction || aMessage.priority() > mChan->AwaitingSyncReplyPriority());
mChan->mCurrentTransaction = aMessage.transaction_id();
}
~AutoEnterTransaction() {
mChan->mMonitor->AssertCurrentThreadOwns();
if (mChan->mCurrentTransaction == mNewTransaction) {
mChan->mCurrentTransaction = mOldTransaction;
}
}
private:
MessageChannel *mChan;
int32_t mNewTransaction, mOldTransaction;
};
bool DispatchingSyncMessage() const;
int DispatchingSyncMessagePriority() const;
// If a sync message times out, we store its sequence number here. Any
// future sync messages will fail immediately. Once the reply for original
@ -657,14 +591,6 @@ class MessageChannel : HasResultCodes
int32_t mTimedOutMessageSeqno;
int mTimedOutMessagePriority;
// If waiting for the reply to a sync out-message, it will be saved here
// on the I/O thread and then read and cleared by the worker thread.
nsAutoPtr<Message> mRecvd;
// If a sync message reply that is an error arrives, we increment this
// counter rather than storing it in mRecvd.
size_t mRecvdErrors;
// Queue of all incoming messages, except for replies to sync and urgent
// messages, which are delivered directly to mRecvd, and any pending urgent
// incall, which is stored in mPendingUrgentRequest.

View File

@ -207,17 +207,31 @@ TestDemonParent::RunLimitedSequence(int flags)
gStackHeight--;
}
static bool
AllowAsync(int outgoing, int incoming)
{
return incoming >= outgoing - 5;
}
bool
TestDemonParent::DoAction(int flags)
{
if (flags & ASYNC_ONLY) {
if (AllowAsync(mOutgoing[0], mIncoming[0])) {
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
return SendAsyncMessage(mOutgoing[0]++);
} else {
return true;
}
} else {
switch (Choose(3)) {
case 0:
if (AllowAsync(mOutgoing[0], mIncoming[0])) {
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
return SendAsyncMessage(mOutgoing[0]++);
} else {
return true;
}
case 1: {
DEMON_LOG("Start SendHiPrioSyncMessage");
@ -339,8 +353,12 @@ TestDemonChild::DoAction()
{
switch (Choose(6)) {
case 0:
if (AllowAsync(mOutgoing[0], mIncoming[0])) {
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
return SendAsyncMessage(mOutgoing[0]++);
} else {
return true;
}
case 1: {
DEMON_LOG("Start SendHiPrioSyncMessage");

View File

@ -2650,7 +2650,7 @@ class MOZ_STACK_CLASS FunctionValidator
if (!m_.mg().startFuncDef(line, &fg_))
return false;
encoder_.emplace(fg_.bytecode());
encoder_.emplace(fg_.bytes());
return true;
}
@ -3532,14 +3532,9 @@ CheckVariables(FunctionValidator& f, ParseNode** stmtIter)
MOZ_ASSERT(f.encoder().empty());
if (!f.encoder().writeVarU32(types.length()))
if (!EncodeLocalEntries(f.encoder(), types))
return false;
for (ValType v : types) {
if (!f.encoder().writeValType(v))
return false;
}
for (uint32_t i = 0; i < inits.length(); i++) {
NumLit lit = inits[i];
if (lit.isZeroBits())
@ -6423,7 +6418,7 @@ CheckSwitchRange(FunctionValidator& f, ParseNode* stmt, int32_t* low, int32_t* h
}
int64_t i64 = (int64_t(*high) - int64_t(*low)) + 1;
if (i64 > 4 * 1024 * 1024)
if (i64 > MaxBrTableElems)
return f.fail(initialStmt, "all switch statements generate tables; this table would be too big");
*tableLength = uint32_t(i64);

View File

@ -144,15 +144,9 @@ CheckType(FunctionDecoder& f, ExprType actual, ExprType expected)
}
static bool
DecodeExpr(FunctionDecoder& f, ExprType* type);
static bool
DecodeValType(JSContext* cx, Decoder& d, ValType *type)
CheckValType(JSContext* cx, Decoder& d, ValType type)
{
if (!d.readValType(type))
return Fail(cx, d, "bad value type");
switch (*type) {
switch (type) {
case ValType::I32:
case ValType::F32:
case ValType::F64:
@ -168,34 +162,18 @@ DecodeValType(JSContext* cx, Decoder& d, ValType *type)
break;
}
return Fail(cx, "bad value type");
return Fail(cx, d, "bad value type");
}
static bool
DecodeExprType(JSContext* cx, Decoder& d, ExprType *type)
CheckExprType(JSContext* cx, Decoder& d, ExprType type)
{
if (!d.readExprType(type))
return Fail(cx, d, "bad expression type");
switch (*type) {
case ExprType::I32:
case ExprType::F32:
case ExprType::F64:
case ExprType::Void:
return true;
case ExprType::I64:
#ifndef JS_CPU_X64
return Fail(cx, d, "i64 NYI on this platform");
#endif
return true;
default:
// Note: it's important not to remove this default since readExprType()
// can return ExprType values for which there is no enumerator.
break;
return type == ExprType::Void ||
CheckValType(cx, d, NonVoidToValType(type));
}
return Fail(cx, "bad expression type");
}
static bool
DecodeExpr(FunctionDecoder& f, ExprType* type);
static bool
DecodeNop(FunctionDecoder& f, ExprType* type)
@ -269,7 +247,8 @@ DecodeCallIndirect(FunctionDecoder& f, ExprType* type)
static bool
DecodeConstI32(FunctionDecoder& f, ExprType* type)
{
if (!f.d().readVarU32())
uint32_t _;
if (!f.d().readVarU32(&_))
return f.fail("unable to read i32.const immediate");
*type = ExprType::I32;
@ -279,7 +258,8 @@ DecodeConstI32(FunctionDecoder& f, ExprType* type)
static bool
DecodeConstI64(FunctionDecoder& f, ExprType* type)
{
if (!f.d().readVarU64())
uint64_t _;
if (!f.d().readVarU64(&_))
return f.fail("unable to read i64.const immediate");
*type = ExprType::I64;
@ -855,24 +835,6 @@ DecodeExpr(FunctionDecoder& f, ExprType* type)
return f.fail("bad expression code");
}
/*****************************************************************************/
// dynamic link data
struct ImportName
{
UniqueChars module;
UniqueChars func;
ImportName(UniqueChars module, UniqueChars func)
: module(Move(module)), func(Move(func))
{}
ImportName(ImportName&& rhs)
: module(Move(rhs.module)), func(Move(rhs.func))
{}
};
typedef Vector<ImportName, 0, SystemAllocPolicy> ImportNameVector;
/*****************************************************************************/
// wasm decoding and generation
@ -912,7 +874,10 @@ DecodeSignatures(JSContext* cx, Decoder& d, ModuleGeneratorData* init)
return Fail(cx, d, "too many arguments in signature");
ExprType result;
if (!DecodeExprType(cx, d, &result))
if (!d.readExprType(&result))
return Fail(cx, d, "bad expression type");
if (!CheckExprType(cx, d, result))
return false;
ValTypeVector args;
@ -920,7 +885,10 @@ DecodeSignatures(JSContext* cx, Decoder& d, ModuleGeneratorData* init)
return false;
for (uint32_t i = 0; i < numArgs; i++) {
if (!DecodeValType(cx, d, &args[i]))
if (!d.readValType(&args[i]))
return Fail(cx, d, "bad value type");
if (!CheckValType(cx, d, args[i]))
return false;
}
@ -1058,6 +1026,21 @@ CheckTypeForJS(JSContext* cx, Decoder& d, const Sig& sig)
return true;
}
struct ImportName
{
Bytes module;
Bytes func;
ImportName(Bytes&& module, Bytes&& func)
: module(Move(module)), func(Move(func))
{}
ImportName(ImportName&& rhs)
: module(Move(rhs.module)), func(Move(rhs.func))
{}
};
typedef Vector<ImportName, 0, SystemAllocPolicy> ImportNameVector;
static bool
DecodeImport(JSContext* cx, Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
{
@ -1071,15 +1054,15 @@ DecodeImport(JSContext* cx, Decoder& d, ModuleGeneratorData* init, ImportNameVec
if (!CheckTypeForJS(cx, d, *sig))
return false;
UniqueChars moduleName = d.readCString();
if (!moduleName)
Bytes moduleName;
if (!d.readBytes(&moduleName))
return Fail(cx, d, "expected import module name");
if (!*moduleName.get())
if (moduleName.empty())
return Fail(cx, d, "module name cannot be empty");
UniqueChars funcName = d.readCString();
if (!funcName)
Bytes funcName;
if (!d.readBytes(&funcName))
return Fail(cx, d, "expected import func name");
return importNames->emplaceBack(Move(moduleName), Move(funcName));
@ -1164,14 +1147,26 @@ DecodeMemory(JSContext* cx, Decoder& d, ModuleGenerator& mg, MutableHandle<Array
typedef HashSet<const char*, CStringHasher> CStringSet;
static UniqueChars
DecodeFieldName(JSContext* cx, Decoder& d, CStringSet* dupSet)
DecodeExportName(JSContext* cx, Decoder& d, CStringSet* dupSet)
{
UniqueChars fieldName = d.readCString();
if (!fieldName) {
Fail(cx, d, "expected export external name string");
Bytes fieldBytes;
if (!d.readBytes(&fieldBytes)) {
Fail(cx, d, "expected export name");
return nullptr;
}
if (memchr(fieldBytes.begin(), 0, fieldBytes.length())) {
Fail(cx, d, "null in export names not yet supported");
return nullptr;
}
if (!fieldBytes.append(0))
return nullptr;
UniqueChars fieldName((char*)fieldBytes.extractRawBuffer());
if (!fieldName)
return nullptr;
CStringSet::AddPtr p = dupSet->lookupForAdd(fieldName.get());
if (p) {
Fail(cx, d, "duplicate export");
@ -1197,7 +1192,7 @@ DecodeFunctionExport(JSContext* cx, Decoder& d, ModuleGenerator& mg, CStringSet*
if (!CheckTypeForJS(cx, d, mg.funcSig(funcIndex)))
return false;
UniqueChars fieldName = DecodeFieldName(cx, d, dupSet);
UniqueChars fieldName = DecodeExportName(cx, d, dupSet);
if (!fieldName)
return false;
@ -1258,15 +1253,11 @@ DecodeFunctionBody(JSContext* cx, Decoder& d, ModuleGenerator& mg, uint32_t func
if (!locals.appendAll(mg.funcSig(funcIndex).args()))
return false;
uint32_t numVars;
if (!d.readVarU32(&numVars))
return Fail(cx, d, "expected number of local vars");
if (!DecodeLocalEntries(d, &locals))
return Fail(cx, d, "failed decoding local entries");
for (uint32_t i = 0; i < numVars; i++) {
ValType type;
if (!DecodeValType(cx, d, &type))
return false;
if (!locals.append(type))
for (ValType type : locals) {
if (!CheckValType(cx, d, type))
return false;
}
@ -1285,10 +1276,10 @@ DecodeFunctionBody(JSContext* cx, Decoder& d, ModuleGenerator& mg, uint32_t func
if (d.currentPosition() != bodyEnd)
return Fail(cx, d, "function body length mismatch");
if (!fg.bytecode().resize(bodySize))
if (!fg.bytes().resize(bodySize))
return false;
memcpy(fg.bytecode().begin(), bodyBegin, bodySize);
memcpy(fg.bytes().begin(), bodyBegin, bodySize);
int64_t after = PRMJ_Now();
unsigned generateTime = (after - before) / PRMJ_USEC_PER_MSEC;
@ -1360,7 +1351,7 @@ DecodeDataSegments(JSContext* cx, Decoder& d, Handle<ArrayBufferObject*> heap)
return Fail(cx, d, "data segment does not fit in memory");
const uint8_t* src;
if (!d.readRawData(numBytes, &src))
if (!d.readBytesRaw(numBytes, &src))
return Fail(cx, d, "data segment shorter than declared");
memcpy(heapBase + dstOffset, src, numBytes);
@ -1472,9 +1463,9 @@ CheckCompilerSupport(JSContext* cx)
}
static bool
GetProperty(JSContext* cx, HandleObject obj, const char* utf8Chars, MutableHandleValue v)
GetProperty(JSContext* cx, HandleObject obj, const Bytes& bytes, MutableHandleValue v)
{
JSAtom* atom = AtomizeUTF8Chars(cx, utf8Chars, strlen(utf8Chars));
JSAtom* atom = AtomizeUTF8Chars(cx, (char*)bytes.begin(), bytes.length());
if (!atom)
return false;
@ -1491,15 +1482,15 @@ ImportFunctions(JSContext* cx, HandleObject importObj, const ImportNameVector& i
for (const ImportName& name : importNames) {
RootedValue v(cx);
if (!GetProperty(cx, importObj, name.module.get(), &v))
if (!GetProperty(cx, importObj, name.module, &v))
return false;
if (*name.func.get()) {
if (!name.func.empty()) {
if (!v.isObject())
return Fail(cx, "import object field is not an Object");
RootedObject obj(cx, &v.toObject());
if (!GetProperty(cx, obj, name.func.get(), &v))
if (!GetProperty(cx, obj, name.func, &v))
return false;
}
@ -1544,6 +1535,7 @@ wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj
UniqueExportMap exportMap;
Rooted<ArrayBufferObject*> heap(cx);
Rooted<WasmModuleObject*> moduleObj(cx);
if (!DecodeModule(cx, Move(file), bytes, length, &importNames, &exportMap, &heap, &moduleObj)) {
if (!cx->isExceptionPending())
ReportOutOfMemory(cx);

View File

@ -0,0 +1,87 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
*
* Copyright 2016 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "asmjs/WasmBinary.h"
#include "asmjs/WasmTypes.h"
using namespace js;
using namespace js::wasm;
bool
wasm::EncodeLocalEntries(Encoder& e, const ValTypeVector& locals)
{
uint32_t numLocalEntries = 0;
ValType prev = ValType::Limit;
for (ValType t : locals) {
if (t != prev) {
numLocalEntries++;
prev = t;
}
}
if (!e.writeVarU32(numLocalEntries))
return false;
if (numLocalEntries) {
prev = locals[0];
uint32_t count = 1;
for (uint32_t i = 1; i < locals.length(); i++, count++) {
if (prev != locals[i]) {
if (!e.writeVarU32(count))
return false;
if (!e.writeValType(prev))
return false;
prev = locals[i];
count = 0;
}
}
if (!e.writeVarU32(count))
return false;
if (!e.writeValType(prev))
return false;
}
return true;
}
bool
wasm::DecodeLocalEntries(Decoder& d, ValTypeVector* locals)
{
uint32_t numLocalEntries;
if (!d.readVarU32(&numLocalEntries))
return false;
for (uint32_t i = 0; i < numLocalEntries; i++) {
uint32_t count;
if (!d.readVarU32(&count))
return false;
if (MaxLocals - locals->length() < count)
return false;
ValType type;
if (!d.readValType(&type))
return false;
if (!locals->appendN(type, count))
return false;
}
return true;
}

View File

@ -19,15 +19,11 @@
#ifndef wasm_binary_h
#define wasm_binary_h
#include "mozilla/DebugOnly.h"
#include "builtin/SIMD.h"
namespace js {
namespace wasm {
using mozilla::DebugOnly;
static const uint32_t MagicNumber = 0x6d736100; // "\0asm"
static const uint32_t EncodingVersion = 0xa;
@ -328,20 +324,19 @@ enum class ExprType
typedef int32_t I32x4[4];
typedef float F32x4[4];
typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytecode;
typedef UniquePtr<Bytecode> UniqueBytecode;
typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
// The Encoder class appends bytes to the Bytecode object it is given during
// construction. The client is responsible for the Bytecode's lifetime and must
// keep the Bytecode alive as long as the Encoder is used.
// The Encoder class appends bytes to the Bytes object it is given during
// construction. The client is responsible for the Bytes's lifetime and must
// keep the Bytes alive as long as the Encoder is used.
class Encoder
{
Bytecode& bytecode_;
Bytes& bytes_;
template <class T>
MOZ_WARN_UNUSED_RESULT bool write(const T& v) {
return bytecode_.append(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
return bytes_.append(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
}
template <typename UInt>
@ -351,7 +346,7 @@ class Encoder
i >>= 7;
if (i != 0)
byte |= 0x80;
if (!bytecode_.append(byte))
if (!bytes_.append(byte))
return false;
} while(i != 0);
return true;
@ -374,42 +369,39 @@ class Encoder
assertByte |= 0x80;
patchByte |= 0x80;
}
MOZ_ASSERT(assertByte == bytecode_[offset]);
bytecode_[offset] = patchByte;
MOZ_ASSERT(assertByte == bytes_[offset]);
bytes_[offset] = patchByte;
offset++;
} while(assertBits != 0);
}
uint32_t varU32ByteLength(size_t offset) const {
size_t start = offset;
while (bytecode_[offset] & 0x80)
while (bytes_[offset] & 0x80)
offset++;
return offset - start + 1;
}
static const uint32_t EnumSentinel = 0x3fff;
template <class T>
MOZ_WARN_UNUSED_RESULT bool writePatchableEnum(size_t* offset) {
static_assert(uint32_t(T::Limit) <= EnumSentinel, "reserve enough bits");
*offset = bytecode_.length();
return writeVarU32(EnumSentinel);
*offset = bytes_.length();
return writeVarU32(uint32_t(T::Limit));
}
template <class T>
void patchEnum(size_t offset, T v) {
static_assert(uint32_t(T::Limit) <= UINT32_MAX, "fits");
MOZ_ASSERT(uint32_t(v) < uint32_t(T::Limit));
return patchVarU32(offset, uint32_t(v), EnumSentinel);
return patchVarU32(offset, uint32_t(v), uint32_t(T::Limit));
}
public:
explicit Encoder(Bytecode& bytecode)
: bytecode_(bytecode)
explicit Encoder(Bytes& bytes)
: bytes_(bytes)
{
MOZ_ASSERT(empty());
}
size_t currentOffset() const { return bytecode_.length(); }
size_t currentOffset() const { return bytes_.length(); }
bool empty() const { return currentOffset() == 0; }
// Fixed-size encoding operations simply copy the literal bytes (without
@ -452,7 +444,7 @@ class Encoder
// Variable-length encodings that allow back-patching.
MOZ_WARN_UNUSED_RESULT bool writePatchableVarU32(size_t* offset) {
*offset = bytecode_.length();
*offset = bytes_.length();
return writeVarU32(UINT32_MAX);
}
void patchVarU32(size_t offset, uint32_t patchBits) {
@ -460,7 +452,7 @@ class Encoder
}
MOZ_WARN_UNUSED_RESULT bool writePatchableVarU8(size_t* offset) {
*offset = bytecode_.length();
*offset = bytes_.length();
return writeU8(UINT8_MAX);
}
void patchVarU8(size_t offset, uint8_t patchBits) {
@ -475,20 +467,18 @@ class Encoder
patchEnum(offset, expr);
}
// C-strings are written in UTF8 and null-terminated while raw data can
// contain nulls and instead has an explicit byte length.
// Byte ranges start with an LEB128 length followed by an arbitrary sequence
// of bytes. When used for strings, bytes are to be interpreted as utf8.
MOZ_WARN_UNUSED_RESULT bool writeCString(const char* cstr) {
return bytecode_.append(reinterpret_cast<const uint8_t*>(cstr), strlen(cstr) + 1);
}
MOZ_WARN_UNUSED_RESULT bool writeRawData(const uint8_t* bytes, uint32_t numBytes) {
return bytecode_.append(bytes, numBytes);
MOZ_WARN_UNUSED_RESULT bool writeBytes(const void* bytes, uint32_t numBytes) {
return writeVarU32(numBytes) &&
bytes_.append(reinterpret_cast<const uint8_t*>(bytes), numBytes);
}
// A "section" is a contiguous range of bytes that stores its own size so
// that it may be trivially skipped without examining the contents. Sections
// require backpatching since the size of the section is only known at the
// end while the size's uint32 must be stored at the beginning. Immediately
// end while the size's varU32 must be stored at the beginning. Immediately
// after the section length is the string id of the section.
template <size_t IdSizeWith0>
@ -497,10 +487,10 @@ class Encoder
MOZ_ASSERT(id[IdSize] == '\0');
return writePatchableVarU32(offset) &&
writeVarU32(IdSize) &&
writeRawData(reinterpret_cast<const uint8_t*>(id), IdSize);
bytes_.append(reinterpret_cast<const uint8_t*>(id), IdSize);
}
void finishSection(size_t offset) {
return patchVarU32(offset, bytecode_.length() - offset - varU32ByteLength(offset));
return patchVarU32(offset, bytes_.length() - offset - varU32ByteLength(offset));
}
// Temporary encoding forms which should be removed as part of the
@ -510,12 +500,12 @@ class Encoder
return write<uint8_t>(i);
}
MOZ_WARN_UNUSED_RESULT bool writePatchableU8(size_t* offset) {
*offset = bytecode_.length();
return bytecode_.append(0xff);
*offset = bytes_.length();
return bytes_.append(0xff);
}
void patchU8(size_t offset, uint8_t i) {
MOZ_ASSERT(bytecode_[offset] == 0xff);
bytecode_[offset] = i;
MOZ_ASSERT(bytes_[offset] == 0xff);
bytes_[offset] = i;
}
};
@ -533,7 +523,6 @@ class Decoder
MOZ_WARN_UNUSED_RESULT bool read(T* out) {
if (bytesRemain() < sizeof(T))
return false;
if (out)
memcpy((void*)out, cur_, sizeof(T));
cur_ += sizeof(T);
return true;
@ -545,7 +534,6 @@ class Decoder
uint32_t u32;
if (!readVarU32(&u32) || u32 >= uint32_t(T::Limit))
return false;
if (out)
*out = T(u32);
return true;
}
@ -566,7 +554,7 @@ class Decoder
}
template <typename UInt>
MOZ_WARN_UNUSED_RESULT bool readVarU(UInt* out = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readVarU(UInt* out) {
const unsigned numBits = sizeof(UInt) * CHAR_BIT;
const unsigned remainderBits = numBits % 7;
const unsigned numBitsInSevens = numBits - remainderBits;
@ -577,7 +565,6 @@ class Decoder
if (!readFixedU8(&byte))
return false;
if (!(byte & 0x80)) {
if (out)
*out = u | UInt(byte) << shift;
return true;
}
@ -586,7 +573,6 @@ class Decoder
} while (shift != numBitsInSevens);
if (!readFixedU8(&byte) || (byte & (unsigned(-1) << remainderBits)))
return false;
if (out)
*out = u | UInt(byte) << numBitsInSevens;
return true;
}
@ -599,10 +585,10 @@ class Decoder
{
MOZ_ASSERT(begin <= end);
}
explicit Decoder(const Bytecode& bytecode)
: beg_(bytecode.begin()),
end_(bytecode.end()),
cur_(bytecode.begin())
explicit Decoder(const Bytes& bytes)
: beg_(bytes.begin()),
end_(bytes.end()),
cur_(bytes.begin())
{}
bool done() const {
@ -620,38 +606,35 @@ class Decoder
size_t currentOffset() const {
return cur_ - beg_;
}
void assertCurrentIs(const DebugOnly<size_t> offset) const {
MOZ_ASSERT(currentOffset() == offset);
}
// Fixed-size encoding operations simply copy the literal bytes (without
// attempting to align).
MOZ_WARN_UNUSED_RESULT bool readFixedU32(uint32_t* u = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readFixedU32(uint32_t* u) {
return read<uint32_t>(u);
}
MOZ_WARN_UNUSED_RESULT bool readFixedF32(float* f = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readFixedF32(float* f) {
return read<float>(f);
}
MOZ_WARN_UNUSED_RESULT bool readFixedF64(double* d = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readFixedF64(double* d) {
return read<double>(d);
}
MOZ_WARN_UNUSED_RESULT bool readFixedI32x4(I32x4* i32x4 = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readFixedI32x4(I32x4* i32x4) {
return read<I32x4>(i32x4);
}
MOZ_WARN_UNUSED_RESULT bool readFixedF32x4(F32x4* f32x4 = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readFixedF32x4(F32x4* f32x4) {
return read<F32x4>(f32x4);
}
// Variable-length encodings that all use LEB128.
MOZ_WARN_UNUSED_RESULT bool readVarU32(uint32_t* out = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readVarU32(uint32_t* out) {
return readVarU<uint32_t>(out);
}
MOZ_WARN_UNUSED_RESULT bool readVarU64(uint64_t* out = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readVarU64(uint64_t* out) {
return readVarU<uint64_t>(out);
}
MOZ_WARN_UNUSED_RESULT bool readExpr(Expr* expr = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readExpr(Expr* expr) {
return readEnum(expr);
}
MOZ_WARN_UNUSED_RESULT bool readValType(ValType* type) {
@ -661,20 +644,21 @@ class Decoder
return readEnum(type);
}
// C-strings are written in UTF8 and null-terminated while raw data can
// contain nulls and instead has an explicit byte length.
// See writeBytes comment.
MOZ_WARN_UNUSED_RESULT UniqueChars readCString() {
const char* begin = reinterpret_cast<const char*>(cur_);
for (; cur_ != end_; cur_++) {
if (!*cur_) {
cur_++;
return UniqueChars(DuplicateString(begin));
MOZ_WARN_UNUSED_RESULT bool readBytes(Bytes* bytes) {
uint32_t numBytes;
if (!readVarU32(&numBytes))
return false;
if (bytesRemain() < numBytes)
return false;
if (!bytes->resize(numBytes))
return false;
memcpy(bytes->begin(), cur_, numBytes);
cur_ += numBytes;
return true;
}
}
return nullptr;
}
MOZ_WARN_UNUSED_RESULT bool readRawData(uint32_t numBytes, const uint8_t** bytes = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readBytesRaw(uint32_t numBytes, const uint8_t** bytes) {
if (bytes)
*bytes = cur_;
if (bytesRemain() < numBytes)
@ -735,7 +719,7 @@ class Decoder
}
// The infallible "unchecked" decoding functions can be used when we are
// sure that the bytecode is well-formed (by construction or due to previous
// sure that the bytes are well-formed (by construction or due to previous
// validation).
uint32_t uncheckedReadFixedU32() {
@ -787,17 +771,25 @@ class Decoder
// Temporary encoding forms which should be removed as part of the
// conversion to wasm:
MOZ_WARN_UNUSED_RESULT bool readFixedU8(uint8_t* i = nullptr) {
MOZ_WARN_UNUSED_RESULT bool readFixedU8(uint8_t* i) {
return read<uint8_t>(i);
}
MOZ_WARN_UNUSED_RESULT bool readFixedI32(int32_t* i = nullptr) {
return read<int32_t>(i);
}
uint8_t uncheckedReadFixedU8() {
return uncheckedRead<uint8_t>();
}
};
// Reusable macro encoding/decoding functions reused by both the two
// encoders (AsmJS/WasmText) and decoders (Wasm/WasmIonCompile).
typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
bool
EncodeLocalEntries(Encoder& d, const ValTypeVector& locals);
bool
DecodeLocalEntries(Decoder& d, ValTypeVector* locals);
} // namespace wasm
} // namespace js

View File

@ -301,7 +301,7 @@ ModuleGenerator::convertOutOfRangeBranchesToThunks()
bool
ModuleGenerator::finishTask(IonCompileTask* task)
{
const FuncBytecode& func = task->func();
const FuncBytes& func = task->func();
FuncCompileResults& results = task->results();
// Before merging in the new function's code, if jumps/calls in a previous
@ -725,7 +725,7 @@ bool
ModuleGenerator::addMemoryExport(UniqueChars fieldName)
{
return exportMap_->fieldNames.append(Move(fieldName)) &&
exportMap_->fieldsToExports.append(ExportMap::MemoryExport);
exportMap_->fieldsToExports.append(MemoryExport);
}
bool
@ -782,15 +782,8 @@ ModuleGenerator::startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg)
IonCompileTask* task = freeTasks_.popCopy();
task->reset(&fg->bytecode_);
if (fg->bytecode_) {
fg->bytecode_->clear();
} else {
fg->bytecode_ = MakeUnique<Bytecode>();
if (!fg->bytecode_)
return false;
}
task->reset(&fg->bytes_);
fg->bytes_.clear();
fg->lineOrBytecode_ = lineOrBytecode;
fg->m_ = this;
fg->task_ = task;
@ -803,10 +796,9 @@ ModuleGenerator::finishFuncDef(uint32_t funcIndex, unsigned generateTime, Functi
{
MOZ_ASSERT(activeFunc_ == fg);
UniqueFuncBytecode func =
js::MakeUnique<FuncBytecode>(funcIndex,
auto func = js::MakeUnique<FuncBytes>(Move(fg->bytes_),
funcIndex,
funcSig(funcIndex),
Move(fg->bytecode_),
fg->lineOrBytecode_,
Move(fg->callSiteLineNums_),
generateTime);

View File

@ -51,7 +51,7 @@ typedef Vector<SlowFunction> SlowFunctionVector;
// is encapsulated by ModuleGenerator/ModuleGeneratorThreadView classes which
// present a race-free interface to the code in each thread assuming any given
// element is initialized by the ModuleGenerator thread before an index to that
// element is written to Bytecode sent to a ModuleGeneratorThreadView thread.
// element is written to Bytes sent to a ModuleGeneratorThreadView thread.
// Once created, the Vectors are never resized.
struct TableModuleGeneratorData
@ -202,8 +202,8 @@ class MOZ_STACK_CLASS ModuleGenerator
Vector<IonCompileTask*> freeTasks_;
// Assertions
DebugOnly<FunctionGenerator*> activeFunc_;
DebugOnly<bool> finishedFuncs_;
FunctionGenerator* activeFunc_;
bool finishedFuncs_;
bool finishOutstandingTask();
bool funcIsDefined(uint32_t funcIndex) const;
@ -292,8 +292,8 @@ class MOZ_STACK_CLASS FunctionGenerator
IonCompileTask* task_;
// Data created during function generation, then handed over to the
// FuncBytecode in ModuleGenerator::finishFunc().
UniqueBytecode bytecode_;
// FuncBytes in ModuleGenerator::finishFunc().
Bytes bytes_;
Uint32Vector callSiteLineNums_;
uint32_t lineOrBytecode_;
@ -303,8 +303,8 @@ class MOZ_STACK_CLASS FunctionGenerator
: m_(nullptr), task_(nullptr), lineOrBytecode_(0)
{}
Bytecode& bytecode() const {
return *bytecode_;
Bytes& bytes() {
return bytes_;
}
bool addCallSiteLineNum(uint32_t lineno) {
return callSiteLineNums_.append(lineno);

View File

@ -40,7 +40,7 @@ class FunctionCompiler
ModuleGeneratorThreadView& mg_;
Decoder& decoder_;
const FuncBytecode& func_;
const FuncBytes& func_;
const ValTypeVector& locals_;
size_t lastReadCallSite_;
@ -60,7 +60,7 @@ class FunctionCompiler
public:
FunctionCompiler(ModuleGeneratorThreadView& mg,
Decoder& decoder,
const FuncBytecode& func,
const FuncBytes& func,
const ValTypeVector& locals,
MIRGenerator& mirGen,
FuncCompileResults& compileResults)
@ -153,7 +153,7 @@ class FunctionCompiler
}
#endif
MOZ_ASSERT(inDeadCode());
MOZ_ASSERT(decoder_.done(), "all bytecode must be consumed");
MOZ_ASSERT(decoder_.done(), "all bytes must be consumed");
MOZ_ASSERT(func_.callSiteLineNums().length() == lastReadCallSite_);
}
@ -3048,22 +3048,18 @@ wasm::IonCompileFunction(IonCompileTask* task)
{
int64_t before = PRMJ_Now();
const FuncBytecode& func = task->func();
const FuncBytes& func = task->func();
FuncCompileResults& results = task->results();
// Read in the variable types to build the local types vector.
Decoder d(func.bytes());
Decoder d(func.bytecode());
// Build the local types vector.
ValTypeVector locals;
if (!locals.appendAll(func.sig().args()))
return false;
uint32_t numVars = d.uncheckedReadVarU32();
for (uint32_t i = 0; i < numVars; i++) {
if (!locals.append(d.uncheckedReadValType()))
if (!DecodeLocalEntries(d, &locals))
return false;
}
// Set up for Ion compilation.

View File

@ -31,52 +31,48 @@ typedef Vector<jit::MIRType, 8, SystemAllocPolicy> MIRTypeVector;
typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
// The FuncBytecode class contains the intermediate representation of a
// parsed/decoded and validated asm.js/WebAssembly function. The FuncBytecode
// lives only until it is fully compiled.
// The FuncBytes class represents a single, concurrently-compilable function.
// A FuncBytes object is composed of the wasm function body bytes along with the
// ambient metadata describing the function necessary to compile it.
class FuncBytecode
class FuncBytes
{
// Function metadata
Bytes bytes_;
uint32_t index_;
const DeclaredSig& sig_;
uint32_t lineOrBytecode_;
Uint32Vector callSiteLineNums_;
// Compilation bookkeeping
uint32_t index_;
unsigned generateTime_;
UniqueBytecode bytecode_;
public:
FuncBytecode(uint32_t index,
FuncBytes(Bytes&& bytes,
uint32_t index,
const DeclaredSig& sig,
UniqueBytecode bytecode,
uint32_t lineOrBytecode,
Uint32Vector&& callSiteLineNums,
unsigned generateTime)
: sig_(sig),
: bytes_(Move(bytes)),
index_(index),
sig_(sig),
lineOrBytecode_(lineOrBytecode),
callSiteLineNums_(Move(callSiteLineNums)),
index_(index),
generateTime_(generateTime),
bytecode_(Move(bytecode))
generateTime_(generateTime)
{}
UniqueBytecode recycleBytecode() { return Move(bytecode_); }
uint32_t lineOrBytecode() const { return lineOrBytecode_; }
const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
Bytes& bytes() { return bytes_; }
const Bytes& bytes() const { return bytes_; }
uint32_t index() const { return index_; }
const DeclaredSig& sig() const { return sig_; }
const Bytecode& bytecode() const { return *bytecode_; }
uint32_t lineOrBytecode() const { return lineOrBytecode_; }
const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
unsigned generateTime() const { return generateTime_; }
};
typedef UniquePtr<FuncBytecode> UniqueFuncBytecode;
typedef UniquePtr<FuncBytes> UniqueFuncBytes;
// The FuncCompileResults class contains the results of compiling a single
// function body, ready to be merged into the whole-module MacroAssembler.
// The FuncCompileResults contains the results of compiling a single function
// body, ready to be merged into the whole-module MacroAssembler.
class FuncCompileResults
{
jit::TempAllocator alloc_;
@ -108,13 +104,14 @@ class FuncCompileResults
// the FuncCompileResults, and finally sent back to the validation thread. To
// save time allocating and freeing memory, IonCompileTasks are reset() and
// reused.
class IonCompileTask
{
JSRuntime* const runtime_;
ModuleGeneratorThreadView& mg_;
LifoAlloc lifo_;
UniqueFuncBytecode func_;
mozilla::Maybe<FuncCompileResults> results_;
UniqueFuncBytes func_;
Maybe<FuncCompileResults> results_;
IonCompileTask(const IonCompileTask&) = delete;
IonCompileTask& operator=(const IonCompileTask&) = delete;
@ -132,21 +129,21 @@ class IonCompileTask
ModuleGeneratorThreadView& mg() const {
return mg_;
}
void init(UniqueFuncBytecode func) {
void init(UniqueFuncBytes func) {
MOZ_ASSERT(!func_);
func_ = mozilla::Move(func);
func_ = Move(func);
results_.emplace(lifo_);
}
const FuncBytecode& func() const {
const FuncBytes& func() const {
MOZ_ASSERT(func_);
return *func_;
}
FuncCompileResults& results() {
return *results_;
}
void reset(UniqueBytecode* recycled) {
void reset(Bytes* recycled) {
if (func_)
*recycled = func_->recycleBytecode();
*recycled = Move(func_->bytes());
func_.reset(nullptr);
results_.reset();
lifo_.releaseAll();

View File

@ -54,8 +54,6 @@ using mozilla::PodZero;
using mozilla::Swap;
using JS::GenericNaN;
const uint32_t ExportMap::MemoryExport;
UniqueCodePtr
wasm::AllocateCode(ExclusiveContext* cx, size_t bytes)
{
@ -1130,7 +1128,7 @@ CreateExportObject(JSContext* cx,
if (!*fieldName) {
MOZ_ASSERT(!exportObj);
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
if (exportIndex == ExportMap::MemoryExport) {
if (exportIndex == MemoryExport) {
MOZ_ASSERT(heap);
exportObj.set(heap);
} else {
@ -1167,7 +1165,7 @@ CreateExportObject(JSContext* cx,
RootedId id(cx, AtomToId(atom));
RootedValue val(cx);
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
if (exportIndex == ExportMap::MemoryExport)
if (exportIndex == MemoryExport)
val = ObjectValue(*heap);
else
val = vals[exportIndex];

View File

@ -336,10 +336,10 @@ typedef Vector<CacheableChars, 0, SystemAllocPolicy> CacheableCharsVector;
// Lastly, the 'exportFuncIndices' vector provides, for each exported function,
// the internal index of the function.
struct ExportMap
{
static const uint32_t MemoryExport = UINT32_MAX;
struct ExportMap
{
CacheableCharsVector fieldNames;
Uint32Vector fieldsToExports;
Uint32Vector exportFuncIndices;

View File

@ -784,6 +784,7 @@ class WasmToken
UnsignedInteger,
SignedInteger,
Memory,
NegativeZero,
Load,
Local,
Loop,
@ -1271,6 +1272,8 @@ WasmTokenStream::literal(const char16_t* begin)
if (*begin == '-') {
uint64_t value = u.value();
if (value == 0)
return WasmToken(WasmToken::NegativeZero, begin, cur_);
if (value > uint64_t(INT64_MIN))
return LexHexFloatLiteral(begin, end_, &cur_);
@ -1295,6 +1298,8 @@ WasmTokenStream::literal(const char16_t* begin)
if (*begin == '-') {
uint64_t value = u.value();
if (value == 0)
return WasmToken(WasmToken::NegativeZero, begin, cur_);
if (value > uint64_t(INT64_MIN))
return LexDecFloatLiteral(begin, end_, &cur_);
@ -2270,6 +2275,9 @@ ParseFloatLiteral(WasmParseContext& c, WasmToken token, Float* result)
case WasmToken::SignedInteger:
*result = token.sint();
return true;
case WasmToken::NegativeZero:
*result = -0.0;
return true;
case WasmToken::Float:
break;
default:
@ -2345,6 +2353,8 @@ ParseConst(WasmParseContext& c, WasmToken constToken)
break;
return new(c.lifo) WasmAstConst(Val(uint32_t(sint.value())));
}
case WasmToken::NegativeZero:
return new(c.lifo) WasmAstConst(Val(uint32_t(0)));
default:
break;
}
@ -2358,6 +2368,8 @@ ParseConst(WasmParseContext& c, WasmToken constToken)
return new(c.lifo) WasmAstConst(Val(val.uint()));
case WasmToken::SignedInteger:
return new(c.lifo) WasmAstConst(Val(uint64_t(val.sint())));
case WasmToken::NegativeZero:
return new(c.lifo) WasmAstConst(Val(uint32_t(0)));
default:
break;
}
@ -3770,11 +3782,11 @@ EncodeFunctionSignatures(Encoder& e, WasmAstModule& module)
}
static bool
EncodeCString(Encoder& e, WasmName wasmName)
EncodeBytes(Encoder& e, WasmName wasmName)
{
TwoByteChars range(wasmName.begin(), wasmName.length());
UniqueChars utf8(JS::CharsToNewUTF8CharsZ(nullptr, range).c_str());
return utf8 && e.writeCString(utf8.get());
return utf8 && e.writeBytes(utf8.get(), strlen(utf8.get()));
}
static bool
@ -3783,10 +3795,10 @@ EncodeImport(Encoder& e, WasmAstImport& imp)
if (!e.writeVarU32(imp.sigIndex()))
return false;
if (!EncodeCString(e, imp.module()))
if (!EncodeBytes(e, imp.module()))
return false;
if (!EncodeCString(e, imp.func()))
if (!EncodeBytes(e, imp.func()))
return false;
return true;
@ -3854,7 +3866,7 @@ EncodeFunctionExport(Encoder& e, WasmAstExport& exp)
if (!e.writeVarU32(exp.func().index()))
return false;
if (!EncodeCString(e, exp.name()))
if (!EncodeBytes(e, exp.name()))
return false;
return true;
@ -3925,13 +3937,11 @@ EncodeFunctionBody(Encoder& e, WasmAstFunc& func)
size_t beforeBody = e.currentOffset();
if (!e.writeVarU32(func.vars().length()))
ValTypeVector varTypes;
if (!varTypes.appendAll(func.vars()))
return false;
for (ValType type : func.vars()) {
if (!e.writeValType(type))
if (!EncodeLocalEntries(e, varTypes))
return false;
}
for (WasmAstExpr* expr : func.body()) {
if (!EncodeExpr(e, *expr))
@ -3981,10 +3991,7 @@ EncodeDataSegment(Encoder& e, WasmAstSegment& segment)
bytes.infallibleAppend(byte);
}
if (!e.writeVarU32(bytes.length()))
return false;
if (!e.writeRawData(bytes.begin(), bytes.length()))
if (!e.writeBytes(bytes.begin(), bytes.length()))
return false;
return true;
@ -4014,60 +4021,56 @@ EncodeDataSegments(Encoder& e, WasmAstModule& module)
return true;
}
static UniqueBytecode
EncodeModule(WasmAstModule& module)
static bool
EncodeModule(WasmAstModule& module, Bytes* bytes)
{
UniqueBytecode bytecode = MakeUnique<Bytecode>();
if (!bytecode)
return nullptr;
Encoder e(*bytecode);
Encoder e(*bytes);
if (!e.writeFixedU32(MagicNumber))
return nullptr;
return false;
if (!e.writeFixedU32(EncodingVersion))
return nullptr;
return false;
if (!EncodeSignatures(e, module))
return nullptr;
return false;
if (!EncodeImportTable(e, module))
return nullptr;
return false;
if (!EncodeFunctionSignatures(e, module))
return nullptr;
return false;
if (!EncodeFunctionTable(e, module))
return nullptr;
return false;
if (!EncodeMemory(e, module))
return nullptr;
return false;
if (!EncodeExportTable(e, module))
return nullptr;
return false;
if (!EncodeFunctionBodies(e, module))
return nullptr;
return false;
if (!EncodeDataSegments(e, module))
return nullptr;
return false;
return Move(bytecode);
return true;
}
/*****************************************************************************/
UniqueBytecode
wasm::TextToBinary(const char16_t* text, UniqueChars* error)
bool
wasm::TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error)
{
LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
WasmAstModule* module = ParseModule(text, lifo, error);
if (!module)
return nullptr;
return false;
if (!ResolveModule(lifo, module, error))
return nullptr;
return false;
return EncodeModule(*module);
return EncodeModule(*module, bytes);
}

View File

@ -26,11 +26,11 @@ namespace js {
namespace wasm {
// Translate the textual representation of a wasm module (given by a
// null-terminated char16_t array) into a Bytecode object. If there is an error
// null-terminated char16_t array) into serialized bytes. If there is an error
// other than out-of-memory an error message string will be stored in 'error'.
extern UniqueBytecode
TextToBinary(const char16_t* text, UniqueChars* error);
extern bool
TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error);
} // namespace wasm
} // namespace js

View File

@ -21,6 +21,7 @@
#include "mozilla/EnumeratedArray.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h"
#include "NamespaceImports.h"
@ -39,11 +40,11 @@ class PropertyName;
namespace wasm {
using mozilla::EnumeratedArray;
using mozilla::Maybe;
using mozilla::Move;
using mozilla::MallocSizeOf;
typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
// ValType/ExprType utilities
@ -596,11 +597,12 @@ static const unsigned InitialGlobalDataBytes = NaN32GlobalDataOffset + sizeo
static const unsigned MaxSigs = 4 * 1024;
static const unsigned MaxFuncs = 512 * 1024;
static const unsigned MaxImports = 4 * 1024;
static const unsigned MaxExports = 4 * 1024;
static const unsigned MaxLocals = 64 * 1024;
static const unsigned MaxImports = 64 * 1024;
static const unsigned MaxExports = 64 * 1024;
static const unsigned MaxTableElems = 128 * 1024;
static const unsigned MaxArgsPerFunc = 4 * 1024;
static const unsigned MaxBrTableElems = 4 * 1024;
static const unsigned MaxBrTableElems = 4 * 1024 * 1024;
} // namespace wasm
} // namespace js

View File

@ -678,7 +678,109 @@ js::obj_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp)
FromPropertyDescriptor(cx, desc, args.rval());
}
// ES6 draft rev27 (2014/08/24) 19.1.2.14 Object.keys(O)
enum EnumerableOwnPropertiesKind {
Keys,
Values,
KeysAndValues
};
// ES7 proposal 2015-12-14
// http://tc39.github.io/proposal-object-values-entries/#EnumerableOwnProperties
static bool
EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPropertiesKind kind)
{
// Step 1. (Step 1 of Object.{keys,values,entries}, really.)
RootedObject obj(cx, ToObject(cx, args.get(0)));
if (!obj)
return false;
// Step 2.
AutoIdVector ids(cx);
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_HIDDEN, &ids))
return false;
// Step 3.
AutoValueVector properties(cx);
size_t len = ids.length();
if (!properties.resize(len))
return false;
RootedId id(cx);
RootedValue key(cx);
RootedValue value(cx);
RootedNativeObject nobj(cx);
if (obj->is<NativeObject>())
nobj = &obj->as<NativeObject>();
RootedShape shape(cx);
Rooted<PropertyDescriptor> desc(cx);
// Step 4.
size_t out = 0;
for (size_t i = 0; i < len; i++) {
id = ids[i];
// Step 4.a. (Symbols were filtered out in step 2.)
MOZ_ASSERT(!JSID_IS_SYMBOL(id));
if (kind != Values) {
if (!IdToStringOrSymbol(cx, id, &key))
return false;
}
// Step 4.a.i.
if (nobj) {
if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) {
value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id));
} else {
shape = nobj->lookup(cx, id);
if (!shape || !(GetShapeAttributes(nobj, shape) & JSPROP_ENUMERATE))
continue;
if (!shape->isAccessorShape()) {
if (!NativeGetExistingProperty(cx, nobj, nobj, shape, &value))
return false;
} else if (!GetProperty(cx, obj, obj, id, &value)) {
return false;
}
}
} else {
if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
return false;
// Step 4.a.ii. (inverted.)
if (!desc.object() || !desc.enumerable())
continue;
// Step 4.a.ii.1.
// (Omitted because Object.keys doesn't use this implementation.)
// Step 4.a.ii.2.a.
if (obj->isNative() && desc.hasValue())
value = desc.value();
else if (!GetProperty(cx, obj, obj, id, &value))
return false;
}
// Steps 4.a.ii.2.b-c.
if (kind == Values)
properties[out++].set(value);
else if (!NewValuePair(cx, key, value, properties[out++]))
return false;
}
// Step 5.
// (Implemented in step 2.)
// Step 3 of Object.{keys,values,entries}
JSObject* aobj = NewDenseCopiedArray(cx, out, properties.begin());
if (!aobj)
return false;
args.rval().setObject(*aobj);
return true;
}
// ES7 proposal 2015-12-14
// http://tc39.github.io/proposal-object-values-entries/#Object.keys
static bool
obj_keys(JSContext* cx, unsigned argc, Value* vp)
{
@ -686,6 +788,24 @@ obj_keys(JSContext* cx, unsigned argc, Value* vp)
return GetOwnPropertyKeys(cx, args, JSITER_OWNONLY);
}
// ES7 proposal 2015-12-14
// http://tc39.github.io/proposal-object-values-entries/#Object.values
static bool
obj_values(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return EnumerableOwnProperties(cx, args, Values);
}
// ES7 proposal 2015-12-14
// http://tc39.github.io/proposal-object-values-entries/#Object.entries
static bool
obj_entries(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return EnumerableOwnProperties(cx, args, KeysAndValues);
}
/* ES6 draft 15.2.3.16 */
static bool
obj_is(JSContext* cx, unsigned argc, Value* vp)
@ -976,7 +1096,7 @@ static const JSFunctionSpec object_methods[] = {
JS_FN(js_toSource_str, obj_toSource, 0,0),
#endif
JS_FN(js_toString_str, obj_toString, 0,0),
JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0,JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN(js_toLocaleString_str, "Object_toLocaleString", 0, 0),
JS_FN(js_valueOf_str, obj_valueOf, 0,0),
#if JS_HAS_OBJ_WATCHPOINT
JS_FN(js_watch_str, obj_watch, 2,0),
@ -986,10 +1106,10 @@ static const JSFunctionSpec object_methods[] = {
JS_FN(js_isPrototypeOf_str, obj_isPrototypeOf, 1,0),
JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0),
#if JS_OLD_GETTER_SETTER_METHODS
JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter", 2,JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter", 2,JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter", 1,JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter", 1,JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter", 2,0),
JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter", 2,0),
JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter", 1,0),
JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter", 1,0),
#endif
JS_FS_END
};
@ -1002,22 +1122,20 @@ static const JSPropertySpec object_properties[] = {
};
static const JSFunctionSpec object_static_methods[] = {
JS_SELF_HOSTED_FN("assign", "ObjectStaticAssign", 2, JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN("assign", "ObjectStaticAssign", 2, 0),
JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, 0),
JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0),
JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2, 0),
JS_FN("keys", obj_keys, 1, 0),
#ifndef RELEASE_BUILD
JS_SELF_HOSTED_FN("values", "ObjectValues", 1, JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN("entries", "ObjectEntries", 1, JSPROP_DEFINE_LATE),
#endif
JS_FN("values", obj_values, 1, 0),
JS_FN("entries", obj_entries, 1, 0),
JS_FN("is", obj_is, 2, 0),
JS_FN("defineProperty", obj_defineProperty, 3, 0),
JS_FN("defineProperties", obj_defineProperties, 2, 0),
JS_INLINABLE_FN("create", obj_create, 2, 0, ObjectCreate),
JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1, 0),
JS_FN("getOwnPropertySymbols", obj_getOwnPropertySymbols, 1, 0),
JS_SELF_HOSTED_FN("isExtensible", "ObjectIsExtensible", 1, JSPROP_DEFINE_LATE),
JS_SELF_HOSTED_FN("isExtensible", "ObjectIsExtensible", 1, 0),
JS_FN("preventExtensions", obj_preventExtensions, 1, 0),
JS_FN("freeze", obj_freeze, 1, 0),
JS_FN("isFrozen", obj_isFrozen, 1, 0),
@ -1088,22 +1206,6 @@ FinishObjectClassInit(JSContext* cx, JS::HandleObject ctor, JS::HandleObject pro
if (!holder)
return false;
/*
* Define self-hosted functions on Object and Function after setting the
* intrinsics holder (which is needed to define self-hosted functions).
*/
if (!cx->runtime()->isSelfHostingGlobal(global)) {
if (!JS_DefineFunctions(cx, ctor, object_static_methods, OnlyDefineLateProperties))
return false;
if (!JS_DefineFunctions(cx, proto, object_methods, OnlyDefineLateProperties))
return false;
RootedObject funProto(cx, global->getOrCreateFunctionPrototype(cx));
if (!funProto)
return false;
if (!JS_DefineFunctions(cx, funProto, function_methods, OnlyDefineLateProperties))
return false;
}
/*
* The global object should have |Object.prototype| as its [[Prototype]].
* Eventually we'd like to have standard classes be there from the start,

View File

@ -139,49 +139,3 @@ function ObjectLookupGetter(name) {
object = std_Reflect_getPrototypeOf(object);
} while (object !== null);
}
// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.values
function ObjectValues(O) {
// Steps 1-2.
var object = ToObject(O);
// Steps 3-4.
// EnumerableOwnProperties is inlined here.
var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN);
var values = [];
var valuesCount = 0;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!callFunction(std_Object_propertyIsEnumerable, object, key))
continue;
var value = object[key];
_DefineDataProperty(values, valuesCount++, value);
}
// Step 5.
return values;
}
// Draft proposal http://tc39.github.io/proposal-object-values-entries/#Object.entries
function ObjectEntries(O) {
// Steps 1-2.
var object = ToObject(O);
// Steps 3-4.
// EnumerableOwnProperties is inlined here.
var keys = OwnPropertyKeys(object, JSITER_OWNONLY | JSITER_HIDDEN);
var entries = [];
var entriesCount = 0;
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!callFunction(std_Object_propertyIsEnumerable, object, key))
continue;
var value = object[key];
_DefineDataProperty(entries, entriesCount++, [key, value]);
}
// Step 5.
return entries;
}

View File

@ -525,19 +525,19 @@ WasmTextToBinary(JSContext* cx, unsigned argc, Value* vp)
if (!twoByteChars.initTwoByte(cx, args[0].toString()))
return false;
wasm::Bytes bytes;
UniqueChars error;
wasm::UniqueBytecode bytes = wasm::TextToBinary(twoByteChars.twoByteChars(), &error);
if (!bytes) {
if (!wasm::TextToBinary(twoByteChars.twoByteChars(), &bytes, &error)) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
error.get() ? error.get() : "out of memory");
return false;
}
RootedObject obj(cx, JS_NewUint8Array(cx, bytes->length()));
RootedObject obj(cx, JS_NewUint8Array(cx, bytes.length()));
if (!obj)
return false;
memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes->begin(), bytes->length());
memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes.begin(), bytes.length());
args.rval().setObject(*obj);
return true;

View File

@ -314,8 +314,8 @@ class StoreBuffer
// overlap.
void merge(const SlotsEdge& other) {
MOZ_ASSERT(overlaps(other));
auto end = std::max(start_ + count_, other.start_ + other.count_);
start_ = std::min(start_, other.start_);
auto end = Max(start_ + count_, other.start_ + other.count_);
start_ = Min(start_, other.start_);
count_ = end - start_;
}

View File

@ -41,9 +41,11 @@ testConst('i32', '0xffffffff', -1);
//testConst('i64', '0xffffffffffffffff', -1); // TODO: NYI
testConst('f32', '0.0', 0.0);
testConst('f32', '-0', -0.0);
testConst('f32', '-0.0', -0.0);
testConst('f32', '0x0.0', 0.0);
testConst('f32', '-0x0.0', -0.0);
testConst('f32', '-0x0', -0.0);
testConst('f32', '0x0.0p0', 0.0);
testConst('f32', '-0x0.0p0', -0.0);
testConst('f32', 'infinity', Infinity);
@ -126,8 +128,10 @@ testConst('f32', '0', 0);
testConst('f64', '0.0', 0.0);
testConst('f64', '-0.0', -0.0);
testConst('f64', '-0', -0.0);
testConst('f64', '0x0.0', 0.0);
testConst('f64', '-0x0.0', -0.0);
testConst('f64', '-0x0', -0.0);
testConst('f64', '0x0.0p0', 0.0);
testConst('f64', '-0x0.0p0', -0.0);
testConst('f64', 'infinity', Infinity);

View File

@ -1,8 +1,5 @@
load(libdir + "wasm.js");
if (!wasmIsSupported())
quit();
function testLoad(type, ext, base, offset, align, expect) {
assertEq(wasmEvalText(
'(module' +

View File

@ -79,16 +79,20 @@ function cstring(name) {
}
function string(name) {
return name.split('').map(c => c.charCodeAt(0));
var nameBytes = name.split('').map(c => {
var code = c.charCodeAt(0);
assertEq(code < 128, true); // TODO
return code
});
return varU32(nameBytes.length).concat(nameBytes);
}
function moduleWithSections(sectionArray) {
var bytes = moduleHeaderThen();
for (let section of sectionArray) {
var nameLength = varU32(section.name.length);
bytes.push(...varU32(nameLength.length + section.name.length + section.body.length));
bytes.push(...nameLength);
bytes.push(...string(section.name));
var sectionName = string(section.name);
bytes.push(...varU32(sectionName.length + section.body.length));
bytes.push(...sectionName);
bytes.push(...section.body);
}
return toU8(bytes);
@ -133,8 +137,8 @@ function importSection(imports) {
body.push(...varU32(imports.length));
for (let imp of imports) {
body.push(...varU32(imp.sigIndex));
body.push(...cstring(imp.module));
body.push(...cstring(imp.func));
body.push(...string(imp.module));
body.push(...string(imp.func));
}
return { name: importId, body };
}
@ -163,8 +167,8 @@ wasmEval(moduleWithSections([sigSection([v2vSig])]));
wasmEval(moduleWithSections([sigSection([i2vSig])]));
wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])]));
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /bad expression type/);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), TypeError, /bad value type/);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /expression type/);
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), TypeError, /value type/);
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([]), declSection([0])])), TypeError, /signature index out of range/);
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([1])])), TypeError, /signature index out of range/);

View File

@ -0,0 +1,26 @@
load(libdir + "wasm.js");
var i = wasmEvalText(
`(module
(memory 1 (segment 0 "\\01\\02\\03\\04\\05\\06\\07\\08"))
(func $off1 (param $base i32) (result i32)
(i32.add
(i32.load8_u (get_local $base))
(i32.load8_u offset=1 (get_local $base)))
)
(export "off1" $off1)
(func $off2 (param $base i32) (result i32)
(i32.add
(i32.load8_u offset=1 (get_local $base))
(i32.load8_u offset=2 (get_local $base)))
)
(export "off2" $off2)
)`);
assertEq(i.off1(0), 3);
assertEq(i.off1(1), 5);
assertEq(i.off1(2), 7);
assertEq(i.off1(3), 9);
assertEq(i.off2(0), 5);
assertEq(i.off2(1), 7);
assertEq(i.off2(2), 9);
assertEq(i.off2(3), 11);

View File

@ -4555,7 +4555,8 @@ MAsmJSLoadHeap::mightAlias(const MDefinition* def) const
if (!base()->isConstant() || !store->base()->isConstant())
return true;
const MConstant* otherBase = store->base()->toConstant();
return base()->toConstant()->equals(otherBase);
return base()->toConstant()->equals(otherBase) &&
offset() == store->offset();
}
return true;
}
@ -4566,7 +4567,9 @@ MAsmJSLoadHeap::congruentTo(const MDefinition* ins) const
if (!ins->isAsmJSLoadHeap())
return false;
const MAsmJSLoadHeap* load = ins->toAsmJSLoadHeap();
return load->accessType() == accessType() && congruentIfOperandsEqual(load);
return load->accessType() == accessType() &&
load->offset() == offset() &&
congruentIfOperandsEqual(load);
}
bool

View File

@ -43,8 +43,8 @@ BEGIN_TEST(testWasmLEB128_encoding)
using namespace js;
using namespace wasm;
Bytecode bc;
Encoder encoder(bc);
Bytes bytes;
Encoder encoder(bytes);
bool passed;
if (!WriteValidBytes(encoder, &passed))
@ -52,18 +52,18 @@ BEGIN_TEST(testWasmLEB128_encoding)
CHECK(passed);
size_t i = 0;
CHECK(bc[i++] == 0x0);
CHECK(bc[i++] == 0x1);
CHECK(bc[i++] == 0x42);
CHECK(bytes[i++] == 0x0);
CHECK(bytes[i++] == 0x1);
CHECK(bytes[i++] == 0x42);
CHECK(bc[i++] == 0x80);
CHECK(bc[i++] == 0x01);
CHECK(bytes[i++] == 0x80);
CHECK(bytes[i++] == 0x01);
CHECK(bc[i++] == 0x80);
CHECK(bc[i++] == 0x03);
CHECK(bytes[i++] == 0x80);
CHECK(bytes[i++] == 0x03);
if (i + 1 < bc.length())
CHECK(bc[i++] == 0x00);
if (i + 1 < bytes.length())
CHECK(bytes[i++] == 0x00);
return true;
}
END_TEST(testWasmLEB128_encoding)
@ -73,19 +73,19 @@ BEGIN_TEST(testWasmLEB128_valid_decoding)
using namespace js;
using namespace wasm;
Bytecode bc;
if (!bc.append(0x0) || !bc.append(0x1) || !bc.append(0x42))
Bytes bytes;
if (!bytes.append(0x0) || !bytes.append(0x1) || !bytes.append(0x42))
return false;
if (!bc.append(0x80) || !bc.append(0x01))
if (!bytes.append(0x80) || !bytes.append(0x01))
return false;
if (!bc.append(0x80) || !bc.append(0x03))
if (!bytes.append(0x80) || !bytes.append(0x03))
return false;
{
// Fallible decoding
Decoder decoder(bc);
Decoder decoder(bytes);
uint32_t value;
CHECK(decoder.readVarU32(&value) && value == 0x0);
@ -99,7 +99,7 @@ BEGIN_TEST(testWasmLEB128_valid_decoding)
{
// Infallible decoding
Decoder decoder(bc);
Decoder decoder(bytes);
uint32_t value;
value = decoder.uncheckedReadVarU32();
@ -124,20 +124,20 @@ BEGIN_TEST(testWasmLEB128_invalid_decoding)
using namespace js;
using namespace wasm;
Bytecode bc;
Bytes bytes;
// Fill bits as per 28 encoded bits
if (!bc.append(0x80) || !bc.append(0x80) || !bc.append(0x80) || !bc.append(0x80))
if (!bytes.append(0x80) || !bytes.append(0x80) || !bytes.append(0x80) || !bytes.append(0x80))
return false;
// Test last valid values
if (!bc.append(0x00))
if (!bytes.append(0x00))
return false;
for (uint8_t i = 0; i < 0x0F; i++) {
bc[4] = i;
bytes[4] = i;
{
Decoder decoder(bc);
Decoder decoder(bytes);
uint32_t value;
CHECK(decoder.readVarU32(&value));
CHECK(value == uint32_t(i << 28));
@ -145,7 +145,7 @@ BEGIN_TEST(testWasmLEB128_invalid_decoding)
}
{
Decoder decoder(bc);
Decoder decoder(bytes);
uint32_t value = decoder.uncheckedReadVarU32();
CHECK(value == uint32_t(i << 28));
CHECK(decoder.done());
@ -154,9 +154,9 @@ BEGIN_TEST(testWasmLEB128_invalid_decoding)
// Test all invalid values of the same size
for (uint8_t i = 0x10; i < 0xF0; i++) {
bc[4] = i;
bytes[4] = i;
Decoder decoder(bc);
Decoder decoder(bytes);
uint32_t value;
CHECK(!decoder.readVarU32(&value));
}

View File

@ -3669,15 +3669,14 @@ JS_IsConstructor(JSFunction* fun)
}
JS_PUBLIC_API(bool)
JS_DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
PropertyDefinitionBehavior behavior)
JS_DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs)
{
MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
assertSameCompartment(cx, obj);
return DefineFunctions(cx, obj, fs, NotIntrinsic, behavior);
return DefineFunctions(cx, obj, fs, NotIntrinsic);
}
JS_PUBLIC_API(JSFunction*)

View File

@ -834,10 +834,7 @@ class MOZ_STACK_CLASS SourceBufferHolder final
object that delegates to a prototype
containing this property */
#define JSPROP_INTERNAL_USE_BIT 0x80 /* internal JS engine use only */
#define JSPROP_DEFINE_LATE 0x100 /* Don't define property when initially creating
the constructor. Some objects like Function/Object
have self-hosted functions that can only be defined
after the initialization is already finished. */
// 0x100 /* Unused */
#define JSFUN_STUB_GSOPS 0x200 /* use JS_PropertyStub getter/setter
instead of defaulting to class gsops
for property holding function */
@ -3593,20 +3590,8 @@ JS_IsNativeFunction(JSObject* funobj, JSNative call);
extern JS_PUBLIC_API(bool)
JS_IsConstructor(JSFunction* fun);
/**
* This enum is used to select if properties with JSPROP_DEFINE_LATE flag
* should be defined on the object.
* Normal JSAPI consumers probably always want DefineAllProperties here.
*/
enum PropertyDefinitionBehavior {
DefineAllProperties,
OnlyDefineLateProperties,
DontDefineLateProperties
};
extern JS_PUBLIC_API(bool)
JS_DefineFunctions(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* fs,
PropertyDefinitionBehavior behavior = DefineAllProperties);
JS_DefineFunctions(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* fs);
extern JS_PUBLIC_API(JSFunction*)
JS_DefineFunction(JSContext* cx, JS::Handle<JSObject*> obj, const char* name, JSNative call,

View File

@ -3679,6 +3679,20 @@ js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_
return NewCopiedArrayTryUseGroup(cx, group, vp, length);
}
bool
js::NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval)
{
JS::AutoValueArray<2> vec(cx);
vec[0].set(val1);
vec[1].set(val2);
JSObject* aobj = js::NewDenseCopiedArray(cx, 2, vec.begin());
if (!aobj)
return false;
rval.setObject(*aobj);
return true;
}
#ifdef DEBUG
bool
js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)

View File

@ -119,6 +119,9 @@ extern JSObject*
NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
HandleObject proto = nullptr);
extern bool
NewValuePair(JSContext* cx, const Value& val1, const Value& val2, MutableHandleValue rval);
/*
* Determines whether a write to the given element on |obj| should fail because
* |obj| is an Array with a non-writable length, and writing that element would

View File

@ -1595,7 +1595,7 @@ const JSFunctionSpec js::function_methods[] = {
JS_FN(js_apply_str, fun_apply, 2,0),
JS_FN(js_call_str, fun_call, 1,0),
JS_FN("isGenerator", fun_isGenerator,0,0),
JS_SELF_HOSTED_FN("bind", "FunctionBind", 2,JSPROP_DEFINE_LATE|JSFUN_HAS_REST),
JS_SELF_HOSTED_FN("bind", "FunctionBind", 2,JSFUN_HAS_REST),
JS_FS_END
};

View File

@ -84,15 +84,7 @@ typedef HashSet<jsid, IdHashPolicy> IdSet;
static inline bool
NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval)
{
JS::AutoValueArray<2> vec(cx);
vec[0].set(IdToValue(id));
vec[1].set(val);
JSObject* aobj = NewDenseCopiedArray(cx, 2, vec.begin());
if (!aobj)
return false;
rval.setObject(*aobj);
return true;
return NewValuePair(cx, IdToValue(id), val, rval);
}
static inline bool

View File

@ -2907,24 +2907,10 @@ DefineFunctionFromSpec(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs
bool
js::DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
DefineAsIntrinsic intrinsic, PropertyDefinitionBehavior behavior)
DefineAsIntrinsic intrinsic)
{
for (; fs->name; fs++) {
unsigned flags = fs->flags;
switch (behavior) {
case DefineAllProperties:
break;
case OnlyDefineLateProperties:
if (!(flags & JSPROP_DEFINE_LATE))
continue;
break;
default:
MOZ_ASSERT(behavior == DontDefineLateProperties);
if (flags & JSPROP_DEFINE_LATE)
continue;
}
if (!DefineFunctionFromSpec(cx, obj, fs, flags & ~JSPROP_DEFINE_LATE, intrinsic))
if (!DefineFunctionFromSpec(cx, obj, fs, fs->flags, intrinsic))
return false;
}
return true;

View File

@ -992,8 +992,7 @@ enum DefineAsIntrinsic {
extern bool
DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
DefineAsIntrinsic intrinsic,
PropertyDefinitionBehavior behavior = DefineAllProperties);
DefineAsIntrinsic intrinsic);
/*
* Set a watchpoint: a synchronous callback when the given property of the

View File

@ -147,6 +147,7 @@ EXPORTS.js += [
UNIFIED_SOURCES += [
'asmjs/AsmJS.cpp',
'asmjs/Wasm.cpp',
'asmjs/WasmBinary.cpp',
'asmjs/WasmFrameIterator.cpp',
'asmjs/WasmGenerator.cpp',
'asmjs/WasmIonCompile.cpp',

View File

@ -220,10 +220,12 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle<GlobalObject*> global, JS
global->setConstructorPropertySlot(key, ObjectValue(*ctor));
// Define any specified functions and properties, unless we're a dependent
// standard class (in which case they live on the prototype).
if (!StandardClassIsDependent(key)) {
// standard class (in which case they live on the prototype), or we're
// operating on the self-hosting global, in which case we don't want any
// functions and properties on the builtins and their prototypes.
if (!StandardClassIsDependent(key) && !cx->runtime()->isSelfHostingGlobal(global)) {
if (const JSFunctionSpec* funs = clasp->spec.prototypeFunctions()) {
if (!JS_DefineFunctions(cx, proto, funs, DontDefineLateProperties))
if (!JS_DefineFunctions(cx, proto, funs))
return false;
}
if (const JSPropertySpec* props = clasp->spec.prototypeProperties()) {
@ -231,7 +233,7 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle<GlobalObject*> global, JS
return false;
}
if (const JSFunctionSpec* funs = clasp->spec.constructorFunctions()) {
if (!JS_DefineFunctions(cx, ctor, funs, DontDefineLateProperties))
if (!JS_DefineFunctions(cx, ctor, funs))
return false;
}
if (const JSPropertySpec* props = clasp->spec.constructorProperties()) {

View File

@ -137,6 +137,59 @@ intrinsic_IsConstructor(JSContext* cx, unsigned argc, Value* vp)
return true;
}
/**
* Intrinsic for calling a wrapped self-hosted function without invoking the
* wrapper's security checks.
*
* Takes a wrapped function as the first and the receiver object as the
* second argument. Any additional arguments are passed on to the unwrapped
* function.
*
* Xray wrappers prevent lower-privileged code from passing objects to wrapped
* functions from higher-privileged realms. In some cases, this check is too
* strict, so this intrinsic allows getting around it.
*
* Note that it's not possible to replace all usages with dedicated intrinsics
* as the function in question might be an inner function that closes over
* state relevant to its execution.
*
* Right now, this is used for the Promise implementation to enable creating
* resolution functions for xrayed Promises in the privileged realm and then
* creating the Promise instance in the non-privileged one. The callbacks have
* to be called by non-privileged code in various places, in many cases
* passing objects as arguments.
*/
static bool
intrinsic_UnsafeCallWrappedFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() >= 2);
MOZ_ASSERT(IsCallable(args[0]));
MOZ_ASSERT(IsWrapper(&args[0].toObject()));
MOZ_ASSERT(args[1].isObject() || args[1].isUndefined());
MOZ_RELEASE_ASSERT(args[0].isObject());
RootedObject wrappedFun(cx, &args[0].toObject());
RootedObject fun(cx, UncheckedUnwrap(wrappedFun));
MOZ_RELEASE_ASSERT(fun->is<JSFunction>());
MOZ_RELEASE_ASSERT(fun->as<JSFunction>().isSelfHostedBuiltin());
InvokeArgs args2(cx);
if (!args2.init(args.length() - 2))
return false;
args2.setThis(args[1]);
for (size_t i = 0; i < args2.length(); i++)
args2[i].set(args[i + 2]);
AutoWaivePolicy waivePolicy(cx, wrappedFun, JSID_VOIDHANDLE, BaseProxyHandler::CALL);
if (!CrossCompartmentWrapper::singleton.call(cx, wrappedFun, args2))
return false;
args.rval().set(args2.rval());
return true;
}
template<typename T>
static bool
intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
@ -1842,6 +1895,8 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("UnsafeGetBooleanFromReservedSlot", intrinsic_UnsafeGetBooleanFromReservedSlot,2,0,
IntrinsicUnsafeGetBooleanFromReservedSlot),
JS_FN("UnsafeCallWrappedFunction", intrinsic_UnsafeCallWrappedFunction,2,0),
JS_FN("IsPackedArray", intrinsic_IsPackedArray, 1,0),
JS_FN("GetIteratorPrototype", intrinsic_GetIteratorPrototype, 0,0),

View File

@ -9305,40 +9305,52 @@ PresShell::Observe(nsISupports* aSubject,
}
#endif
if (!nsCRT::strcmp(aTopic, "agent-sheet-added") && mStyleSet) {
if (!nsCRT::strcmp(aTopic, "agent-sheet-added")) {
if (mStyleSet) {
AddAgentSheet(aSubject);
}
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "user-sheet-added") && mStyleSet) {
if (!nsCRT::strcmp(aTopic, "user-sheet-added")) {
if (mStyleSet) {
AddUserSheet(aSubject);
}
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "author-sheet-added") && mStyleSet) {
if (!nsCRT::strcmp(aTopic, "author-sheet-added")) {
if (mStyleSet) {
AddAuthorSheet(aSubject);
}
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "agent-sheet-removed") && mStyleSet) {
if (!nsCRT::strcmp(aTopic, "agent-sheet-removed")) {
if (mStyleSet) {
RemoveSheet(SheetType::Agent, aSubject);
}
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "user-sheet-removed") && mStyleSet) {
if (!nsCRT::strcmp(aTopic, "user-sheet-removed")) {
if (mStyleSet) {
RemoveSheet(SheetType::User, aSubject);
}
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "author-sheet-removed") && mStyleSet) {
if (!nsCRT::strcmp(aTopic, "author-sheet-removed")) {
if (mStyleSet) {
RemoveSheet(SheetType::Doc, aSubject);
}
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, "memory-pressure") &&
!AssumeAllImagesVisible() &&
mPresContext->IsRootContentDocument()) {
if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
if (!AssumeAllImagesVisible() && mPresContext->IsRootContentDocument()) {
DoUpdateImageVisibility(/* aRemoveOnly = */ true);
}
return NS_OK;
}

0
layout/reftests/fonts/sil/Charis-license.txt Executable file → Normal file
View File

0
layout/reftests/fonts/sil/CharisSIL-R.ttf Executable file → Normal file
View File

View File

@ -382,7 +382,6 @@ class Options
Mode mMode;
NumOption<size_t> mSampleBelowSize;
NumOption<uint32_t> mMaxFrames;
bool mShowDumpStats;
void BadArg(const char* aArg);
@ -404,7 +403,6 @@ public:
const char* DMDEnvVar() const { return mDMDEnvVar; }
size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
size_t MaxFrames() const { return mMaxFrames.mActual; }
size_t ShowDumpStats() const { return mShowDumpStats; }
};
@ -695,9 +693,7 @@ public:
private:
uint32_t mLength; // The number of PCs.
const void* mPcs[MaxFrames]; // The PCs themselves. If --max-frames is less
// than 24, this array is bigger than
// necessary, but that case is unusual.
const void* mPcs[MaxFrames]; // The PCs themselves.
public:
StackTrace() : mLength(0) {}
@ -779,7 +775,7 @@ StackTrace::Get(Thread* aT)
AutoUnlockState unlock;
uint32_t skipFrames = 2;
if (MozStackWalk(StackWalkCallback, skipFrames,
gOptions->MaxFrames(), &tmp, 0, nullptr)) {
MaxFrames, &tmp, 0, nullptr)) {
// Handle the common case first. All is ok. Nothing to do.
} else {
tmp.mLength = 0;
@ -1430,7 +1426,6 @@ Options::Options(const char* aDMDEnvVar)
: nullptr)
, mMode(DarkMatter)
, mSampleBelowSize(4093, 100 * 100 * 1000)
, mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames)
, mShowDumpStats(false)
{
// It's no longer necessary to set the DMD env var to "1" if you want default
@ -1473,9 +1468,6 @@ Options::Options(const char* aDMDEnvVar)
&myLong)) {
mSampleBelowSize.mActual = myLong;
} else if (GetLong(arg, "--max-frames", 1, mMaxFrames.mMax, &myLong)) {
mMaxFrames.mActual = myLong;
} else if (GetBool(arg, "--show-dump-stats", &myBool)) {
mShowDumpStats = myBool;
@ -1502,22 +1494,7 @@ Options::BadArg(const char* aArg)
{
StatusMsg("\n");
StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
StatusMsg("\n");
StatusMsg("$DMD must be a whitespace-separated list of |--option=val|\n");
StatusMsg("entries.\n");
StatusMsg("\n");
StatusMsg("The following options are allowed; defaults are shown in [].\n");
StatusMsg(" --mode=<mode> Profiling mode [dark-matter]\n");
StatusMsg(" where <mode> is one of: live, dark-matter, cumulative\n");
StatusMsg(" --sample-below=<1..%d> Sample blocks smaller than this [%d]\n",
int(mSampleBelowSize.mMax),
int(mSampleBelowSize.mDefault));
StatusMsg(" (prime numbers are recommended)\n");
StatusMsg(" --max-frames=<1..%d> Max. depth of stack traces [%d]\n",
int(mMaxFrames.mMax),
int(mMaxFrames.mDefault));
StatusMsg(" --show-dump-stats=<yes|no> Show stats about dumps? [no]\n");
StatusMsg("\n");
StatusMsg("See the output of |mach help run| for the allowed options.\n");
exit(1);
}

View File

@ -13,7 +13,6 @@ import org.mozilla.gecko.dlc.DownloadContentService;
import org.mozilla.gecko.home.HomePanelsManager;
import org.mozilla.gecko.lwt.LightweightTheme;
import org.mozilla.gecko.mdns.MulticastDNSManager;
import org.mozilla.gecko.push.PushService;
import org.mozilla.gecko.util.Clipboard;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.ThreadUtils;
@ -166,27 +165,6 @@ public class GeckoApplication extends Application
super.onCreate();
if (AppConstants.MOZ_ANDROID_GCM) {
// TODO: only run in main process.
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override
public void run() {
// It's fine to throw GCM initialization onto a background thread; the registration process requires
// network access, so is naturally asynchronous. This, of course, races against Gecko page load of
// content requiring GCM-backed services, like Web Push. There's nothing to be done here.
PushService.createInstance(context);
PushService.registerGeckoEventListener();
try {
PushService.getInstance().onStartup();
} catch (Exception e) {
Log.e(LOG_TAG, "Got exception during startup; ignoring.", e);
return;
}
}
});
}
if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
DownloadContentService.startStudy(this);
}

View File

@ -5162,6 +5162,16 @@ pref("reader.has_used_toolbar", false);
// Whether to use a vertical or horizontal toolbar.
pref("reader.toolbar.vertical", true);
#if !defined(ANDROID)
pref("narrate.enabled", true);
#else
pref("narrate.enabled", false);
#endif
pref("narrate.test", false);
pref("narrate.rate", 0);
pref("narrate.voice", "automatic");
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
// Whether to allow, on a Linux system that doesn't support the necessary sandboxing
// features, loading Gecko Media Plugins unsandboxed. However, EME CDMs will not be

View File

@ -1118,13 +1118,10 @@ class RunProgram(MachCommandBase):
help='Profiling mode. The default is \'dark-matter\'.')
@CommandArgument('--sample-below', default=None, type=str, group='DMD',
help='Sample blocks smaller than this. Use 1 for no sampling. The default is 4093.')
@CommandArgument('--max-frames', default=None, type=str, group='DMD',
help='The maximum depth of stack traces. The default and maximum is 24.')
@CommandArgument('--show-dump-stats', action='store_true', group='DMD',
help='Show stats when doing dumps.')
def run(self, params, remote, background, noprofile, debug, debugger,
debugparams, slowscript, dmd, mode, sample_below, max_frames,
show_dump_stats):
debugparams, slowscript, dmd, mode, sample_below, show_dump_stats):
if conditions.is_android(self):
# Running Firefox for Android is completely different
@ -1207,8 +1204,6 @@ class RunProgram(MachCommandBase):
dmd_params.append('--mode=' + mode)
if sample_below:
dmd_params.append('--sample-below=' + sample_below)
if max_frames:
dmd_params.append('--max-frames=' + max_frames)
if show_dump_stats:
dmd_params.append('--show-dump-stats=yes')

View File

@ -53,7 +53,7 @@ function WebRequestEventManager(context, eventName) {
return;
}
let optional = ["requestHeaders", "responseHeaders", "statusCode", "redirectUrl"];
let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "redirectUrl"];
for (let opt of optional) {
if (opt in data) {
data2[opt] = data[opt];

View File

@ -96,6 +96,20 @@ function backgroundScript() {
return url.startsWith(BASE) || /^data:.*\bwebRequestTest\b/.test(url);
}
let statuses = [
{url: /_script_good\b/, code: 200, line: /^HTTP\/1.1 200 OK\b/i},
{url: /\bredirection\b/, code: 302, line: /^HTTP\/1.1 302\b/},
{url: /\bnonexistent_script_/, code: 404, line: /^HTTP\/1.1 404 Not Found\b/i},
];
function checkStatus(details) {
for (let {url, code, line} of statuses) {
if (url.test(details.url)) {
browser.test.assertTrue(code === details.statusCode, `HTTP status code ${code} for ${details.url} (found ${details.statusCode})`);
browser.test.assertTrue(line.test(details.statusLine), `HTTP status line ${line} for ${details.url} (found ${details.statusLine})`);
}
}
}
function checkType(details) {
let expected_type = "???";
if (details.url.indexOf("style") != -1) {
@ -302,6 +316,7 @@ function backgroundScript() {
browser.test.assertEq(details.tabId, savedTabId, "correct tab ID");
checkType(details);
checkStatus(details);
let id = frameIDs.get(details.url);
browser.test.assertEq(id, details.frameId, "frame ID same in onBeforeRedirect as onBeforeRequest");
@ -344,6 +359,7 @@ function backgroundScript() {
}
completedUrls[kind].add(details.url);
}
checkStatus(details);
}
function onHeadersReceived(details) {

View File

@ -34,6 +34,7 @@ DIRS += [
'gfx',
'jsdownloads',
'lz4',
'narrate',
'mediasniffer',
'microformats',
'osfile',

View File

@ -0,0 +1,93 @@
{
"extends": [
"../../.eslintrc"
],
"globals": {
"Components": true,
"dump": true,
"Iterator": true
},
"env": { "browser": true },
"rules": {
// Mozilla stuff
"mozilla/no-aArgs": 1,
"mozilla/reject-importGlobalProperties": 1,
"mozilla/var-only-at-top-level": 1,
"block-scoped-var": 2,
"brace-style": [1, "1tbs", {"allowSingleLine": false}],
"camelcase": 1,
"comma-dangle": 1,
"comma-spacing": [1, {"before": false, "after": true}],
"comma-style": [1, "last"],
"complexity": 1,
"consistent-return": 2,
"curly": 2,
"dot-location": [1, "property"],
"dot-notation": 2,
"eol-last": 2,
"generator-star-spacing": [1, "after"],
"indent": [1, 2, {"SwitchCase": 1}],
"key-spacing": [1, {"beforeColon": false, "afterColon": true}],
"max-len": [1, 80, 2, {"ignoreUrls": true}],
"max-nested-callbacks": [2, 3],
"new-cap": [2, {"capIsNew": false}],
"new-parens": 2,
"no-array-constructor": 2,
"no-cond-assign": 2,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-dupe-args": 2,
"no-dupe-keys": 2,
"no-duplicate-case": 2,
"no-else-return": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-extra-boolean-cast": 2,
"no-extra-semi": 1,
"no-fallthrough": 2,
"no-inline-comments": 1,
"no-lonely-if": 2,
"no-mixed-spaces-and-tabs": 2,
"no-multi-spaces": 1,
"no-multi-str": 1,
"no-multiple-empty-lines": [1, {"max": 1}],
"no-native-reassign": 2,
"no-nested-ternary": 2,
"no-redeclare": 2,
"no-return-assign": 2,
"no-self-compare": 2,
"no-sequences": 2,
"no-shadow": 1,
"no-shadow-restricted-names": 2,
"no-spaced-func": 1,
"no-throw-literal": 2,
"no-trailing-spaces": 2,
"no-undef": 2,
"no-unneeded-ternary": 2,
"no-unreachable": 2,
"no-unused-vars": 2,
"no-with": 2,
"padded-blocks": [1, "never"],
"quotes": [1, "double", "avoid-escape"],
"semi": [1, "always"],
"semi-spacing": [1, {"before": false, "after": true}],
"space-after-keywords": [1, "always"],
"space-before-blocks": [1, "always"],
"space-before-function-paren": [1, "never"],
"space-in-parens": [1, "never"],
"space-infix-ops": [1, {"int32Hint": true}],
"space-return-throw-case": 1,
"space-unary-ops": [1, { "words": true, "nonwords": false }],
"spaced-comment": [1, "always"],
"strict": [2, "global"],
"use-isnan": 2,
"valid-typeof": 2,
"yoda": 2
}
}

View File

@ -0,0 +1,244 @@
/* 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/. */
"use strict";
const Cu = Components.utils;
Cu.import("resource://gre/modules/narrate/VoiceSelect.jsm");
Cu.import("resource://gre/modules/narrate/Narrator.jsm");
Cu.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = ["NarrateControls"];
var gStrings = Services.strings.createBundle("chrome://global/locale/narrate.properties");
function NarrateControls(mm, win) {
this._mm = mm;
this._winRef = Cu.getWeakReference(win);
// Append content style sheet in document head
let style = win.document.createElement("link");
style.rel = "stylesheet";
style.href = "chrome://global/skin/narrate.css";
win.document.head.appendChild(style);
function localize(pieces, ...substitutions) {
let result = pieces[0];
for (let i = 0; i < substitutions.length; ++i) {
result += gStrings.GetStringFromName(substitutions[i]) + pieces[i + 1];
}
return result;
}
let dropdown = win.document.createElement("ul");
dropdown.className = "dropdown";
dropdown.id = "narrate-dropdown";
dropdown.innerHTML =
localize`<style scoped>
@import url("chrome://global/skin/narrateControls.css");
</style>
<li>
<button class="dropdown-toggle button"
id="narrate-toggle" title="${"narrate"}"></button>
</li>
<li class="dropdown-popup">
<div id="narrate-control" class="narrate-row">
<button disabled id="narrate-skip-previous"
title="${"back"}"></button>
<button id="narrate-start-stop" title="${"start"}"></button>
<button disabled id="narrate-skip-next"
title="${"forward"}"></button>
</div>
<div id="narrate-rate" class="narrate-row">
<input id="narrate-rate-input" value="0" title="${"speed"}"
step="25" max="400" min="-400" type="range">
</div>
<div id="narrate-voices" class="narrate-row"></div>
<div class="dropdown-arrow"></div>
</li>`;
this.narrator = new Narrator(win);
let selectLabel = gStrings.GetStringFromName("selectvoicelabel");
let comparer = win.Intl ?
(new Intl.Collator()).compare : (a, b) => a.localeCompare(b);
let options = this.narrator.getVoiceOptions().map(v => {
return {
label: this._createVoiceLabel(v),
value: v.voiceURI
};
}).sort((a, b) => comparer(a.label, b.label));
options.unshift({
label: gStrings.GetStringFromName("defaultvoice"),
value: "automatic"
});
this.voiceSelect = new VoiceSelect(win, selectLabel, options);
this.voiceSelect.element.addEventListener("change", this);
this.voiceSelect.element.id = "voice-select";
dropdown.querySelector("#narrate-voices").appendChild(
this.voiceSelect.element);
dropdown.addEventListener("click", this, true);
let rateRange = dropdown.querySelector("#narrate-rate > input");
rateRange.addEventListener("input", this);
rateRange.addEventListener("mousedown", this);
rateRange.addEventListener("mouseup", this);
let branch = Services.prefs.getBranch("narrate.");
this.voiceSelect.value = branch.getCharPref("voice");
// The rate is stored as an integer.
rateRange.value = branch.getIntPref("rate");
let tb = win.document.getElementById("reader-toolbar");
tb.appendChild(dropdown);
}
NarrateControls.prototype = {
handleEvent: function(evt) {
switch (evt.type) {
case "mousedown":
this._rateMousedown = true;
break;
case "mouseup":
this._rateMousedown = false;
break;
case "input":
this._onRateInput(evt);
break;
case "change":
this._onVoiceChange();
break;
case "click":
this._onButtonClick(evt);
break;
}
},
_onRateInput: function(evt) {
if (!this._rateMousedown) {
this._mm.sendAsyncMessage("Reader:SetIntPref",
{ name: "narrate.rate", value: evt.target.value });
this.narrator.setRate(this._convertRate(evt.target.value));
}
},
_onVoiceChange: function() {
let voice = this.voice;
this._mm.sendAsyncMessage("Reader:SetCharPref",
{ name: "narrate.voice", value: voice });
this.narrator.setVoice(voice);
},
_onButtonClick: function(evt) {
switch (evt.target.id) {
case "narrate-skip-previous":
this.narrator.skipPrevious();
break;
case "narrate-skip-next":
this.narrator.skipNext();
break;
case "narrate-start-stop":
if (this.narrator.speaking) {
this.narrator.stop();
} else {
this._updateSpeechControls(true);
let options = { rate: this.rate, voice: this.voice };
this.narrator.start(options).then(() => {
this._updateSpeechControls(false);
});
}
break;
case "narrate-toggle":
let dropdown = this._doc.getElementById("narrate-dropdown");
if (dropdown.classList.contains("open")) {
if (this.narrator.speaking) {
this.narrator.stop();
}
// We need to remove "keep-open" class here so that AboutReader
// closes this dropdown properly. This class is eventually removed in
// _updateSpeechControls which gets called after narration stops,
// but that happend asynchronously and is too late.
dropdown.classList.remove("keep-open");
}
break;
}
},
_updateSpeechControls: function(speaking) {
let dropdown = this._doc.getElementById("narrate-dropdown");
dropdown.classList.toggle("keep-open", speaking);
let startStopButton = this._doc.getElementById("narrate-start-stop");
startStopButton.classList.toggle("speaking", speaking);
startStopButton.title =
gStrings.GetStringFromName(speaking ? "start" : "stop");
this._doc.getElementById("narrate-skip-previous").disabled = !speaking;
this._doc.getElementById("narrate-skip-next").disabled = !speaking;
},
_createVoiceLabel: function(voice) {
// This is a highly imperfect method of making human-readable labels
// for system voices. Because each platform has a different naming scheme
// for voices, we use a different method for each platform.
switch (Services.appinfo.OS) {
case "WINNT":
// On windows the language is included in the name, so just use the name
return voice.name;
case "Linux":
// On Linux, the name is usually the unlocalized language name.
// Use a localized language name, and have the language tag in
// parenthisis. This is to avoid six languages called "English".
return gStrings.formatStringFromName("voiceLabel",
[this._getLanguageName(voice.lang) || voice.name, voice.lang], 2);
default:
// On Mac the language is not included in the name, find a localized
// language name or show the tag if none exists.
// This is the ideal naming scheme so it is also the "default".
return gStrings.formatStringFromName("voiceLabel",
[voice.name, this._getLanguageName(voice.lang) || voice.lang], 2);
}
},
_getLanguageName: function(lang) {
if (!this._langStrings) {
this._langStrings = Services.strings.createBundle(
"chrome://global/locale/languageNames.properties ");
}
try {
// language tags will be lower case ascii between 2 and 3 characters long.
return this._langStrings.GetStringFromName(lang.match(/^[a-z]{2,3}/)[0]);
} catch (e) {
return "";
}
},
_convertRate: function(rate) {
// We need to convert a relative percentage value to a fraction rate value.
// eg. -100 is half the speed, 100 is twice the speed in percentage,
// 0.5 is half the speed and 2 is twice the speed in fractions.
return Math.pow(Math.abs(rate / 100) + 1, rate < 0 ? -1 : 1);
},
get _win() {
return this._winRef.get();
},
get _doc() {
return this._win.document;
},
get rate() {
return this._convertRate(
this._doc.getElementById("narrate-rate-input").value);
},
get voice() {
return this.voiceSelect.value;
}
};

View File

@ -0,0 +1,219 @@
/* 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/. */
"use strict";
const { interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
"resource:///modules/translation/LanguageDetector.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = [ "Narrator" ];
// Maximum time into paragraph when pressing "skip previous" will go
// to previous paragraph and not the start of current one.
const PREV_THRESHOLD = 2000;
function Narrator(win) {
this._winRef = Cu.getWeakReference(win);
this._inTest = Services.prefs.getBoolPref("narrate.test");
this._speechOptions = {};
this._startTime = 0;
this._stopped = false;
}
Narrator.prototype = {
get _doc() {
return this._winRef.get().document;
},
get _win() {
return this._winRef.get();
},
get _voiceMap() {
if (!this._voiceMapInner) {
this._voiceMapInner = new Map();
for (let voice of this._win.speechSynthesis.getVoices()) {
this._voiceMapInner.set(voice.voiceURI, voice);
}
}
return this._voiceMapInner;
},
get _paragraphs() {
if (!this._paragraphsInner) {
let wu = this._win.QueryInterface(
Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
let queryString = "#reader-header > *:not(style):not(:empty), " +
"#moz-reader-content > .page > * > *:not(style):not(:empty)";
// filter out zero sized paragraphs.
let paragraphs = Array.from(this._doc.querySelectorAll(queryString));
paragraphs = paragraphs.filter(p => {
let bb = wu.getBoundsWithoutFlushing(p);
return bb.width && bb.height;
});
this._paragraphsInner = paragraphs.map(Cu.getWeakReference);
}
return this._paragraphsInner;
},
get _timeIntoParagraph() {
let rv = Date.now() - this._startTime;
return rv;
},
get speaking() {
return this._win.speechSynthesis.speaking ||
this._win.speechSynthesis.pending;
},
_getParagraphAt: function(index) {
let paragraph = this._paragraphsInner[index];
return paragraph ? paragraph.get() : null;
},
_isParagraphInView: function(paragraphRef) {
let paragraph = paragraphRef && paragraphRef.get && paragraphRef.get();
if (!paragraph) {
return false;
}
let bb = paragraph.getBoundingClientRect();
return bb.top >= 0 && bb.top < this._win.innerHeight;
},
_detectLanguage: function() {
if (this._speechOptions.lang || this._speechOptions.voice) {
return Promise.resolve();
}
let sampleText = this._doc.getElementById(
"moz-reader-content").textContent.substring(0, 60 * 1024);
return LanguageDetector.detectLanguage(sampleText).then(result => {
if (result.confident) {
this._speechOptions.lang = result.language;
}
});
},
_sendTestEvent: function(eventType, detail) {
let win = this._win;
win.dispatchEvent(new win.CustomEvent(eventType,
{ detail: Cu.cloneInto(detail, win.document) }));
},
_speakInner: function() {
this._win.speechSynthesis.cancel();
let paragraph = this._getParagraphAt(this._index);
let utterance = new this._win.SpeechSynthesisUtterance(
paragraph.textContent);
utterance.rate = this._speechOptions.rate;
if (this._speechOptions.voice) {
utterance.voice = this._speechOptions.voice;
} else {
utterance.lang = this._speechOptions.lang;
}
this._startTime = Date.now();
return new Promise(resolve => {
utterance.addEventListener("start", () => {
paragraph.classList.add("narrating");
let bb = paragraph.getBoundingClientRect();
if (bb.top < 0 || bb.bottom > this._win.innerHeight) {
paragraph.scrollIntoView({ behavior: "smooth", block: "start"});
}
if (this._inTest) {
this._sendTestEvent("paragraphstart", {
voice: utterance.chosenVoiceURI,
rate: utterance.rate,
paragraph: this._index
});
}
});
utterance.addEventListener("end", () => {
if (!this._win) {
// page got unloaded, don't do anything.
return;
}
paragraph.classList.remove("narrating");
this._startTime = 0;
if (this._inTest) {
this._sendTestEvent("paragraphend", {});
}
if (this._index + 1 >= this._paragraphs.length || this._stopped) {
// We reached the end of the document, or the user pressed stopped.
resolve();
} else {
this._index++;
this._speakInner().then(resolve);
}
});
this._win.speechSynthesis.speak(utterance);
});
},
getVoiceOptions: function() {
return Array.from(this._voiceMap.values());
},
start: function(speechOptions) {
this._speechOptions = {
rate: speechOptions.rate,
voice: this._voiceMap.get(speechOptions.voice)
};
this._stopped = false;
return this._detectLanguage().then(() => {
if (!this._isParagraphInView(this._paragraphs[this._index])) {
this._index = this._paragraphs.findIndex(
this._isParagraphInView.bind(this));
}
return this._speakInner();
});
},
stop: function() {
this._stopped = true;
this._win.speechSynthesis.cancel();
},
skipNext: function() {
this._win.speechSynthesis.cancel();
},
skipPrevious: function() {
this._index -=
this._index > 0 && this._timeIntoParagraph < PREV_THRESHOLD ? 2 : 1;
this._win.speechSynthesis.cancel();
},
setRate: function(rate) {
this._speechOptions.rate = rate;
/* repeat current paragraph */
this._index--;
this._win.speechSynthesis.cancel();
},
setVoice: function(voice) {
this._speechOptions.voice = this._voiceMap.get(voice);
/* repeat current paragraph */
this._index--;
this._win.speechSynthesis.cancel();
}
};

View File

@ -0,0 +1,291 @@
/* 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/. */
"use strict";
const Cu = Components.utils;
this.EXPORTED_SYMBOLS = ["VoiceSelect"];
function VoiceSelect(win, label, options = []) {
this._winRef = Cu.getWeakReference(win);
let element = win.document.createElement("div");
element.classList.add("voiceselect");
element.innerHTML =
`<button class="select-toggle" aria-controls="voice-options">
<span class="label">${label}</span> <span class="current-voice"></span>
</button>
<div class="options" id="voice-options" role="listbox"></div>`;
this._elementRef = Cu.getWeakReference(element);
let button = this.selectToggle;
button.addEventListener("click", this);
button.addEventListener("keypress", this);
let listbox = this.listbox;
listbox.addEventListener("click", this);
listbox.addEventListener("mousemove", this);
listbox.addEventListener("keypress", this);
listbox.addEventListener("wheel", this, true);
win.addEventListener("resize", () => {
this._updateDropdownHeight();
});
for (let option of options) {
this.add(option.label, option.value);
}
this.selectedIndex = 0;
}
VoiceSelect.prototype = {
add: function(label, value) {
let option = this._doc.createElement("button");
option.dataset.value = value;
option.classList.add("option");
option.tabIndex = "-1";
option.setAttribute("role", "option");
option.textContent = label;
this.listbox.appendChild(option);
},
toggleList: function(force, focus = true) {
if (this.element.classList.toggle("open", force)) {
if (focus) {
(this.selected || this.options[0]).focus();
}
this._updateDropdownHeight(true);
this.listbox.setAttribute("aria-expanded", true);
this._win.addEventListener("focus", this, true);
} else {
if (focus) {
this.element.querySelector(".select-toggle").focus();
}
this.listbox.setAttribute("aria-expanded", false);
this._win.removeEventListener("focus", this, true);
}
},
handleEvent: function(evt) {
let target = evt.target;
switch (evt.type) {
case "click":
if (target.classList.contains("option")) {
if (!target.classList.contains("selected")) {
this.selected = target;
}
this.toggleList(false);
} else if (target.classList.contains("select-toggle")) {
this.toggleList();
}
break;
case "mousemove":
this.listbox.classList.add("hovering");
break;
case "keypress":
if (target.classList.contains("select-toggle")) {
if (evt.altKey) {
this.toggleList(true);
} else {
this._keyPressedButton(evt);
}
} else {
this.listbox.classList.remove("hovering");
this._keyPressedInBox(evt);
}
break;
case "wheel":
// Don't let wheel events bubble to document. It will scroll the page
// and close the entire narrate dialog.
evt.stopPropagation();
break;
case "focus":
this._win.console.log(evt);
if (!evt.target.closest('.options')) {
this.toggleList(false, false);
}
break;
}
},
_getPagedOption: function(option, up) {
let height = elem => elem.getBoundingClientRect().height;
let listboxHeight = height(this.listbox);
let next = option;
for (let delta = 0; delta < listboxHeight; delta += height(next)) {
let sibling = up ? next.previousElementSibling : next.nextElementSibling;
if (!sibling) {
break;
}
next = sibling;
}
return next;
},
_keyPressedButton: function(evt) {
if (evt.altKey && (evt.key === "ArrowUp" || evt.key === "ArrowUp")) {
this.toggleList(true);
return;
}
let toSelect;
switch (evt.key) {
case "PageUp":
case "ArrowUp":
toSelect = this.selected.previousElementSibling;
break;
case "PageDown":
case "ArrowDown":
toSelect = this.selected.nextElementSibling;
break;
case "Home":
toSelect = this.selected.parentNode.firstElementChild;
break;
case "End":
toSelect = this.selected.parentNode.lastElementChild;
break;
}
if (toSelect && toSelect.classList.contains("option")) {
evt.preventDefault();
this.selected = toSelect;
}
},
_keyPressedInBox: function(evt) {
let toFocus;
let cur = this._doc.activeElement;
switch (evt.key) {
case "ArrowUp":
toFocus = cur.previousElementSibling || this.listbox.lastElementChild;
break;
case "ArrowDown":
toFocus = cur.nextElementSibling || this.listbox.firstElementChild;
break;
case "PageUp":
toFocus = this._getPagedOption(cur, true);
break;
case "PageDown":
toFocus = this._getPagedOption(cur, false);
break;
case "Home":
toFocus = cur.parentNode.firstElementChild;
break;
case "End":
toFocus = cur.parentNode.lastElementChild;
break;
case "Escape":
this.toggleList(false);
break;
}
if (toFocus && toFocus.classList.contains("option")) {
evt.preventDefault();
toFocus.focus();
}
},
_select: function(option) {
let oldSelected = this.selected;
if (oldSelected) {
oldSelected.removeAttribute("aria-selected");
oldSelected.classList.remove("selected");
}
if (option) {
option.setAttribute("aria-selected", true);
option.classList.add("selected");
this.element.querySelector(".current-voice").textContent =
option.textContent;
}
let evt = this.element.ownerDocument.createEvent("Event");
evt.initEvent("change", true, true);
this.element.dispatchEvent(evt);
},
_updateDropdownHeight: function(now) {
let updateInner = () => {
let winHeight = this._win.innerHeight;
let listbox = this.listbox;
let listboxTop = listbox.getBoundingClientRect().top;
listbox.style.maxHeight = (winHeight - listboxTop - 10) + "px";
};
if (now) {
updateInner();
} else if (!this._pendingDropdownUpdate) {
this._pendingDropdownUpdate = true;
this._win.requestAnimationFrame(() => {
updateInner();
delete this._pendingDropdownUpdate;
});
}
},
get element() {
return this._elementRef.get();
},
get listbox() {
return this._elementRef.get().querySelector(".options");
},
get selectToggle() {
return this._elementRef.get().querySelector(".select-toggle");
},
get _win() {
return this._winRef.get();
},
get _doc() {
return this._win.document;
},
set selected(option) {
this._select(option);
},
get selected() {
return this.element.querySelector(".options > .option.selected");
},
get options() {
return this.element.querySelectorAll(".options > .option");
},
set selectedIndex(index) {
this._select(this.options[index]);
},
get selectedIndex() {
return Array.from(this.options).indexOf(this.selected);
},
set value(value) {
let option = Array.from(this.options).find(o => o.dataset.value === value);
this._select(option);
},
get value() {
let selected = this.selected;
return selected ? selected.dataset.value : "";
}
};

View File

@ -0,0 +1,13 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_JS_MODULES.narrate = [
'NarrateControls.jsm',
'Narrator.jsm',
'VoiceSelect.jsm'
]
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']

View File

@ -0,0 +1,21 @@
{
"extends": [
"../.eslintrc"
],
"globals": {
"is": true,
"isnot": true,
"ok": true,
"NarrateTestUtils": true,
"content": true,
"ContentTaskUtils": true,
"ContentTask": true,
"BrowserTestUtils": true,
"gBrowser": true,
},
"rules": {
"mozilla/import-headjs-globals": 1
}
}

View File

@ -0,0 +1,114 @@
/* 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/. */
"use strict";
Components.utils.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = [ "NarrateTestUtils" ];
this.NarrateTestUtils = {
TOGGLE: "#narrate-toggle",
POPUP: "#narrate-dropdown .dropdown-popup",
VOICE_SELECT: "#narrate-voices .select-toggle",
VOICE_OPTIONS: "#narrate-voices .options",
VOICE_SELECTED: "#narrate-voices .options .option.selected",
VOICE_SELECT_LABEL: "#narrate-voices .select-toggle .current-voice",
RATE: "#narrate-rate-input",
START: "#narrate-start-stop:not(.speaking)",
STOP: "#narrate-start-stop.speaking",
BACK: "#narrate-skip-previous",
FORWARD: "#narrate-skip-next",
isVisible: function(element) {
let style = element.ownerDocument.defaultView.getComputedStyle(element, "");
if (style.display == "none") {
return false;
} else if (style.visibility != "visible") {
return false;
} else if (style.display == "-moz-popup" && element.state != "open") {
return false;
}
// Hiding a parent element will hide all its children
if (element.parentNode != element.ownerDocument) {
return this.isVisible(element.parentNode);
}
return true;
},
isStoppedState: function(window, ok) {
let $ = window.document.querySelector.bind(window.document);
ok($(this.BACK).disabled, "back button is disabled");
ok($(this.FORWARD).disabled, "forward button is disabled");
ok(!!$(this.START), "start button is showing");
ok(!$(this.STOP), "stop button is hidden");
},
isStartedState: function(window, ok) {
let $ = window.document.querySelector.bind(window.document);
ok(!$(this.BACK).disabled, "back button is enabled");
ok(!$(this.FORWARD).disabled, "forward button is enabled");
ok(!$(this.START), "start button is hidden");
ok(!!$(this.STOP), "stop button is showing");
},
selectVoice: function(window, voiceUri) {
if (!this.isVisible(window.document.querySelector(this.VOICE_OPTIONS))) {
window.document.querySelector(this.VOICE_SELECT).click();
}
let voiceOption = window.document.querySelector(
`#narrate-voices .option[data-value="${voiceUri}"]`);
voiceOption.focus();
voiceOption.click();
return voiceOption.classList.contains("selected");
},
getEventUtils: function(window) {
let eventUtils = {
"_EU_Ci": Components.interfaces,
"_EU_Cc": Components.classes,
window: window,
parent: window,
navigator: window.navigator,
KeyboardEvent: window.KeyboardEvent,
KeyEvent: window.KeyEvent
};
Services.scriptloader.loadSubScript(
"chrome://mochikit/content/tests/SimpleTest/EventUtils.js", eventUtils);
return eventUtils;
},
getReaderReadyPromise: function(window) {
return new Promise(resolve => {
function observeReady(subject, topic) {
if (subject == window) {
Services.obs.removeObserver(observeReady, topic);
resolve();
}
}
if (window.document.body.classList.contains("loaded")) {
resolve();
} else {
Services.obs.addObserver(observeReady, "AboutReader:Ready", false);
}
});
},
waitForPrefChange: function(pref) {
return new Promise(resolve => {
function observeChange() {
Services.prefs.removeObserver(pref, observeChange);
resolve();
}
Services.prefs.addObserver(pref, observeChange, false);
});
}
};

View File

@ -0,0 +1,8 @@
[DEFAULT]
support-files =
head.js
NarrateTestUtils.jsm
[browser_narrate.js]
[browser_narrate_disable.js]
[browser_voiceselect.js]

View File

@ -0,0 +1,101 @@
/* 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/. */
/* globals is, isnot, registerCleanupFunction, add_task */
"use strict";
registerCleanupFunction(teardown);
add_task(function* testNarrate() {
setup();
yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
let TEST_VOICE = "urn:moz-tts:fake-indirect:teresa";
let $ = content.document.querySelector.bind(content.document);
let popup = $(NarrateTestUtils.POPUP);
ok(!NarrateTestUtils.isVisible(popup), "popup is initially hidden");
let toggle = $(NarrateTestUtils.TOGGLE);
toggle.click();
ok(NarrateTestUtils.isVisible(popup), "popup toggled");
let voiceOptions = $(NarrateTestUtils.VOICE_OPTIONS);
ok(!NarrateTestUtils.isVisible(voiceOptions),
"voice options are initially hidden");
$(NarrateTestUtils.VOICE_SELECT).click();
ok(NarrateTestUtils.isVisible(voiceOptions), "voice options pop up");
let prefChanged = NarrateTestUtils.waitForPrefChange("narrate.voice");
ok(NarrateTestUtils.selectVoice(content, TEST_VOICE),
"test voice selected");
yield prefChanged;
ok(!NarrateTestUtils.isVisible(voiceOptions), "voice options hidden again");
NarrateTestUtils.isStoppedState(content, ok);
let promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
$(NarrateTestUtils.START).click();
let speechinfo = (yield promiseEvent).detail;
is(speechinfo.voice, TEST_VOICE, "correct voice is being used");
is(speechinfo.paragraph, 0, "first paragraph is being spoken");
NarrateTestUtils.isStartedState(content, ok);
promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
$(NarrateTestUtils.FORWARD).click();
speechinfo = (yield promiseEvent).detail;
is(speechinfo.voice, TEST_VOICE, "same voice is used");
is(speechinfo.paragraph, 1, "second paragraph is being spoken");
NarrateTestUtils.isStartedState(content, ok);
let eventUtils = NarrateTestUtils.getEventUtils(content);
promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
prefChanged = NarrateTestUtils.waitForPrefChange("narrate.rate");
$(NarrateTestUtils.RATE).focus();
eventUtils.sendKey("PAGE_UP", content);
let newspeechinfo = (yield promiseEvent).detail;
is(newspeechinfo.paragraph, speechinfo.paragraph, "same paragraph");
isnot(newspeechinfo.rate, speechinfo.rate, "rate changed");
yield prefChanged;
promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphend");
$(NarrateTestUtils.STOP).click();
yield promiseEvent;
yield ContentTaskUtils.waitForCondition(
() => !$(NarrateTestUtils.STOP), "transitioned to stopped state");
NarrateTestUtils.isStoppedState(content, ok);
promiseEvent = ContentTaskUtils.waitForEvent(content, "scroll");
content.scrollBy(0, 10);
yield promiseEvent;
ok(!NarrateTestUtils.isVisible(popup), "popup is hidden after scroll");
toggle.click();
ok(NarrateTestUtils.isVisible(popup), "popup is toggled again");
promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphstart");
$(NarrateTestUtils.START).click();
yield promiseEvent;
NarrateTestUtils.isStartedState(content, ok);
promiseEvent = ContentTaskUtils.waitForEvent(content, "scroll");
content.scrollBy(0, -10);
yield promiseEvent;
ok(NarrateTestUtils.isVisible(popup), "popup stays visible after scroll");
promiseEvent = ContentTaskUtils.waitForEvent(content, "paragraphend");
toggle.click();
yield promiseEvent;
ok(!NarrateTestUtils.isVisible(popup), "popup is dismissed while speaking");
ok(true, "speech stopped when popup is dismissed");
});
});

View File

@ -0,0 +1,37 @@
/* 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/. */
/* globals registerCleanupFunction, add_task */
"use strict";
const ENABLE_PREF = "narrate.enabled";
registerCleanupFunction(() => {
clearUserPref(ENABLE_PREF);
teardown();
});
add_task(function* testNarratePref() {
setup();
yield spawnInNewReaderTab(TEST_ARTICLE, function() {
is(content.document.querySelectorAll(NarrateTestUtils.TOGGLE).length, 1,
"narrate is inserted by default");
});
setBoolPref(ENABLE_PREF, false);
yield spawnInNewReaderTab(TEST_ARTICLE, function() {
ok(!content.document.querySelector(NarrateTestUtils.TOGGLE),
"narrate is disabled and is not in reader mode");
});
setBoolPref(ENABLE_PREF, true);
yield spawnInNewReaderTab(TEST_ARTICLE, function() {
is(content.document.querySelectorAll(NarrateTestUtils.TOGGLE).length, 1,
"narrate is re-enabled and appears only once");
});
});

View File

@ -0,0 +1,106 @@
/* 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/. */
/* globals registerCleanupFunction, add_task, is, isnot */
"use strict";
registerCleanupFunction(teardown);
add_task(function* testVoiceselectDropdownAutoclose() {
setup();
yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
let $ = content.document.querySelector.bind(content.document);
$(NarrateTestUtils.TOGGLE).click();
ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
"popup is toggled");
ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
"voice options are initially hidden");
$(NarrateTestUtils.VOICE_SELECT).click();
ok(NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
"voice options are toggled");
$(NarrateTestUtils.TOGGLE).click();
// A focus will follow a real click.
$(NarrateTestUtils.TOGGLE).focus();
ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
"narrate popup is dismissed");
$(NarrateTestUtils.TOGGLE).click();
// A focus will follow a real click.
$(NarrateTestUtils.TOGGLE).focus();
ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
"narrate popup is showing again");
ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
"voice options are hidden after popup comes back");
});
});
add_task(function* testVoiceselectLabelChange() {
setup();
yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
let $ = content.document.querySelector.bind(content.document);
$(NarrateTestUtils.TOGGLE).click();
ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
"popup is toggled");
ok(NarrateTestUtils.selectVoice(content, "urn:moz-tts:fake-direct:lenny"),
"voice selected");
let selectedOption = $(NarrateTestUtils.VOICE_SELECTED);
let selectLabel = $(NarrateTestUtils.VOICE_SELECT_LABEL);
is(selectedOption.textContent, selectLabel.textContent,
"new label matches selected voice");
});
});
add_task(function* testVoiceselectKeyboard() {
setup();
yield spawnInNewReaderTab(TEST_ARTICLE, function* () {
let $ = content.document.querySelector.bind(content.document);
$(NarrateTestUtils.TOGGLE).click();
ok(NarrateTestUtils.isVisible($(NarrateTestUtils.POPUP)),
"popup is toggled");
let eventUtils = NarrateTestUtils.getEventUtils(content);
let firstValue = $(NarrateTestUtils.VOICE_SELECTED).dataset.value;
ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
"voice options initially are hidden");
$(NarrateTestUtils.VOICE_SELECT).focus();
eventUtils.sendKey("DOWN", content);
yield ContentTaskUtils.waitForCondition(
() => $(NarrateTestUtils.VOICE_SELECTED).dataset.value != firstValue,
"value changed after pressing DOWN key");
eventUtils.sendKey("RETURN", content);
ok(NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
"voice options showing after pressing RETURN");
eventUtils.sendKey("UP", content);
eventUtils.sendKey("RETURN", content);
ok(!NarrateTestUtils.isVisible($(NarrateTestUtils.VOICE_OPTIONS)),
"voice options hidden after pressing RETURN");
yield ContentTaskUtils.waitForCondition(
() => $(NarrateTestUtils.VOICE_SELECTED).dataset.value == firstValue,
"value changed back to original after pressing RETURN");
});
});

View File

@ -0,0 +1,67 @@
/* 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/. */
/* exported teardown, setup, toggleExtension,
spawnInNewReaderTab, TEST_ARTICLE */
"use strict";
const TEST_ARTICLE = "http://example.com/browser/browser/base/content/test/" +
"general/readerModeArticle.html";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
const TEST_PREFS = [
["reader.parse-on-load.enabled", true],
["media.webspeech.synth.enabled", true],
["media.webspeech.synth.test", true],
["narrate.enabled", true],
["narrate.test", true]
];
function setup() {
// Set required test prefs.
TEST_PREFS.forEach(([name, value]) => {
setBoolPref(name, value);
});
}
function teardown() {
// Reset test prefs.
TEST_PREFS.forEach(pref => {
clearUserPref(pref[0]);
});
}
function spawnInNewReaderTab(url, func) {
return BrowserTestUtils.withNewTab(
{ gBrowser,
url: `about:reader?url=${encodeURIComponent(url)}` },
function* (browser) {
yield ContentTask.spawn(browser, null, function* () {
Components.utils.import("chrome://mochitests/content/browser/" +
"toolkit/components/narrate/test/NarrateTestUtils.jsm");
yield NarrateTestUtils.getReaderReadyPromise(content);
});
yield ContentTask.spawn(browser, null, func);
});
}
function setBoolPref(name, value) {
Services.prefs.setBoolPref(name, value);
}
function clearUserPref(name) {
Services.prefs.clearUserPref(name);
}

View File

@ -15,6 +15,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NarrateControls", "resource://gre/modules/narrate/NarrateControls.jsm");
var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
@ -102,6 +103,10 @@ var AboutReader = function(mm, win, articlePromise) {
this._setupFontSizeButtons();
if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
new NarrateControls(mm, win);
}
this._loadArticle();
}
@ -168,7 +173,7 @@ AboutReader.prototype = {
// Triggered by Android user pressing BACK while the banner font-dropdown is open.
case "Reader:CloseDropdown": {
// Just close it.
this._closeDropdown();
this._closeDropdowns();
break;
}
@ -209,19 +214,27 @@ AboutReader.prototype = {
switch (aEvent.type) {
case "click":
let target = aEvent.target;
while (target && target.id != "reader-popup")
target = target.parentNode;
if (!target)
this._closeDropdown();
if (target.classList.contains('dropdown-toggle')) {
this._toggleDropdownClicked(aEvent);
} else if (!target.closest('.dropdown-popup')) {
this._closeDropdowns();
}
break;
case "scroll":
this._closeDropdown();
this._closeDropdowns();
let isScrollingUp = this._scrollOffset > aEvent.pageY;
this._setSystemUIVisibility(isScrollingUp);
this._scrollOffset = aEvent.pageY;
break;
case "resize":
this._updateImageMargins();
if (this._isToolbarVertical) {
this._win.setTimeout(() => {
for (let dropdown of this._doc.querySelectorAll('.dropdown.open')) {
this._updatePopupPosition(dropdown);
}
}, 0);
}
break;
case "devicelight":
@ -234,7 +247,7 @@ AboutReader.prototype = {
case "unload":
// Close the Banners Font-dropdown, cleanup Android BackPressListener.
this._closeDropdown();
this._closeDropdowns();
this._mm.removeMessageListener("Reader:CloseDropdown", this);
this._mm.removeMessageListener("Reader:AddButton", this);
@ -603,7 +616,7 @@ AboutReader.prototype = {
this._requestFavicon();
this._doc.body.classList.add("loaded");
Services.obs.notifyObservers(null, "AboutReader:Ready", "");
Services.obs.notifyObservers(this._win, "AboutReader:Ready", "");
},
_hideContent: function() {
@ -718,75 +731,66 @@ AboutReader.prototype = {
},
_setupStyleDropdown: function() {
let doc = this._doc;
let win = this._win;
let dropdownToggle = this._doc.querySelector("#style-dropdown .dropdown-toggle");
dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
},
let dropdown = doc.getElementById("style-dropdown");
_updatePopupPosition: function(dropdown) {
let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
let dropdownPopup = dropdown.querySelector(".dropdown-popup");
// Helper function used to position the popup on desktop,
// where there is a vertical toolbar.
function updatePopupPosition() {
let toggleHeight = dropdownToggle.offsetHeight;
let toggleTop = dropdownToggle.offsetTop;
let popupTop = toggleTop - toggleHeight / 2;
dropdownPopup.style.top = popupTop + "px";
}
},
if (this._isToolbarVertical) {
win.addEventListener("resize", event => {
if (!event.isTrusted)
return;
_toggleDropdownClicked: function(event) {
let dropdown = event.target.closest('.dropdown');
// Wait for reflow before calculating the new position of the popup.
win.setTimeout(updatePopupPosition, 0);
}, true);
}
dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
dropdownToggle.addEventListener("click", event => {
if (!event.isTrusted)
if (!dropdown)
return;
event.stopPropagation();
if (dropdown.classList.contains("open")) {
this._closeDropdown();
this._closeDropdowns();
} else {
this._openDropdown();
this._openDropdown(dropdown);
if (this._isToolbarVertical) {
updatePopupPosition();
this._updatePopupPosition(dropdown);
}
}
}, true);
},
/*
* If the ReaderView banner font-dropdown is closed, open it.
*/
_openDropdown: function() {
let dropdown = this._doc.getElementById("style-dropdown");
_openDropdown: function(dropdown) {
if (dropdown.classList.contains("open")) {
return;
}
this._closeDropdowns();
// Trigger BackPressListener initialization in Android.
dropdown.classList.add("open");
this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
},
/*
* If the ReaderView banner font-dropdown is opened, close it.
* If the ReaderView has open dropdowns, close them.
*/
_closeDropdown: function() {
let dropdown = this._doc.getElementById("style-dropdown");
if (!dropdown.classList.contains("open")) {
return;
_closeDropdowns: function() {
let openDropdowns = this._doc.querySelectorAll(".dropdown.open:not(.keep-open)");
for (let dropdown of openDropdowns) {
dropdown.classList.remove("open");
}
// Trigger BackPressListener cleanup in Android.
dropdown.classList.remove("open");
if (openDropdowns.length) {
this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
},
}
}
};

View File

@ -0,0 +1,19 @@
# 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/.
# Narrate, meaning "read the page out loud". This is the name of the feature
# and it is the label for the popup button.
narrate = Narrate
back = Back
start = Start
stop = Stop
forward = Forward
speed = Speed
selectvoicelabel = Voice:
# Default voice is determined by the language of the document.
defaultvoice = Default
# Voice name and language.
# eg. David (English)
voiceLabel = %S (%S)

View File

@ -62,6 +62,7 @@
locale/@AB_CD@/global/keys.properties (%chrome/global/keys.properties)
locale/@AB_CD@/global/languageNames.properties (%chrome/global/languageNames.properties)
locale/@AB_CD@/global/mozilla.dtd (%chrome/global/mozilla.dtd)
locale/@AB_CD@/global/narrate.properties (%chrome/global/narrate.properties)
locale/@AB_CD@/global/notification.dtd (%chrome/global/notification.dtd)
locale/@AB_CD@/global/preferences.dtd (%chrome/global/preferences.dtd)
locale/@AB_CD@/global/printdialog.dtd (%chrome/global/printdialog.dtd)

View File

@ -87,6 +87,20 @@ function parseExtra(extra, allowed) {
return result;
}
function mergeStatus(data, channel) {
try {
data.statusCode = channel.responseStatus;
let statusText = channel.responseStatusText;
let maj = {};
let min = {};
channel.QueryInterface(Ci.nsIHttpChannelInternal).getResponseVersion(maj, min);
data.statusLine = `HTTP/${maj.value}.${min.value} ${data.statusCode} ${statusText}`;
} catch (e) {
// NS_ERROR_NOT_AVAILABLE might be thrown.
Cu.reportError(e);
}
}
var HttpObserverManager;
var ContentPolicyManager = {
@ -404,7 +418,7 @@ HttpObserverManager = {
let responseHeaderNames;
let includeStatus = kind === "headersReceived" ||
kind === "onBeforeRedirect" ||
kind === "onRedirect" ||
kind === "onStart" ||
kind === "onStop";
@ -443,7 +457,7 @@ HttpObserverManager = {
responseHeaderNames = data.responseHeaders.map(h => h.name);
}
if (includeStatus) {
data.statusCode = channel.responseStatus;
mergeStatus(data, channel);
}
let result = null;

View File

@ -55,7 +55,9 @@ ol,
li,
figure,
.wp-caption {
margin: 0 0 30px 0;
margin: -10px -10px 20px -10px;
padding: 10px;
border-radius: 5px;
}
p > img:only-child,

View File

@ -120,7 +120,7 @@
padding: 0;
}
/*======= Font style popup =======*/
/*======= Popup =======*/
.dropdown-popup {
min-width: 300px;
@ -136,6 +136,10 @@
box-shadow: 0 1px 12px #666;
}
.keep-open .dropdown-popup {
z-index: initial;
}
.dropdown-popup > hr {
display: none;
}
@ -154,6 +158,8 @@
display: block;
}
/*======= Font style popup =======*/
#font-type-buttons,
#font-size-buttons,
#color-scheme-buttons {

View File

@ -26,6 +26,16 @@ toolkit.jar:
skin/classic/global/icons/loading-inverted@2x.png (../../shared/icons/loading-inverted@2x.png)
skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg)
skin/classic/global/alerts/alert-common.css (../../shared/alert-common.css)
skin/classic/global/narrate.css (../../shared/narrate.css)
skin/classic/global/narrateControls.css (../../shared/narrateControls.css)
skin/classic/global/narrate/arrow.svg (../../shared/narrate/arrow.svg)
skin/classic/global/narrate/back.svg (../../shared/narrate/back.svg)
skin/classic/global/narrate/fast.svg (../../shared/narrate/fast.svg)
skin/classic/global/narrate/forward.svg (../../shared/narrate/forward.svg)
skin/classic/global/narrate/narrate.svg (../../shared/narrate/narrate.svg)
skin/classic/global/narrate/slow.svg (../../shared/narrate/slow.svg)
skin/classic/global/narrate/start.svg (../../shared/narrate/start.svg)
skin/classic/global/narrate/stop.svg (../../shared/narrate/stop.svg)
skin/classic/global/menu/shared-menu-check@2x.png (../../shared/menu-check@2x.png)
skin/classic/global/menu/shared-menu-check.png (../../shared/menu-check.png)
skin/classic/global/menu/shared-menu-check-active.svg (../../shared/menu-check-active.svg)

View File

@ -0,0 +1,11 @@
body.light .narrating {
background-color: #ffc;
}
body.sepia .narrating {
background-color: #e0d7c5;
}
body.dark .narrating {
background-color: #242424;
}

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12">
<path d="M6 9L1 4l1-1 4 4 4-4 1 1z" fill="#4C4C4C"/>
</svg>

After

Width:  |  Height:  |  Size: 166 B

View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<defs>
<style>
use:not(:target) {
display: none;
}
#disabled {
opacity: 0.5;
}
</style>
<path id="shape" d="M 5 0 C 4.446 0 4 0.446 4 1 L 4 23 C 4 23.554 4.446 24 5 24 L 7 24 C 7.554 24 8 23.554 8 23 L 8 12.404297 C 8.04108 12.509297 8.109944 12.610125 8.203125 12.703125 L 19.296875 23.775391 C 19.495259 23.972391 19.661613 24.039562 19.796875 23.976562 C 19.932137 23.915564 20 23.748516 20 23.478516 L 20 0.52148438 C 20 0.25248437 19.93214 0.084484365 19.796875 0.021484375 C 19.661613 -0.040515625 19.495259 0.02856248 19.296875 0.2265625 L 8.203125 11.298828 C 8.1099445 11.381828 8.04108 11.481703 8 11.595703 L 8 1 C 8 0.446 7.554 0 7 0 L 5 0 z " fill="gray"/>
</defs>
<use id="enabled" xlink:href="#shape"/>
<use id="disabled" xlink:href="#shape"/>
</svg>

After

Width:  |  Height:  |  Size: 930 B

View File

@ -0,0 +1,3 @@
<svg id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20.4">
<path fill="gray" d="M14.42 16.68a.77.77 0 0 0 .54.7l2.51.68a1.58 1.58 0 0 1 1.06 1.22l.05.39-3.89-.53a4.34 4.34 0 0 1-1.74-.72L7.2 14.03a5.79 5.79 0 0 1-5.34-4.88h-.82a1 1 0 0 1-1-1l2.9-3.24a6.16 6.16 0 0 1 4.7-2.39 5.88 5.88 0 0 1 .77.05 5 5 0 0 1 .87.15c3.75 1 6.5 5.84 6.5 5.84a2.27 2.27 0 0 0 1.14.85h.17a1.27 1.27 0 0 0 1.22-.4l.78-1-2.47-1.2c-3.38-1.46-2.46-5.71-2.46-5.71 0-.26.23-.32.42-.14l5.32 5-4.31-4.81a1.39 1.39 0 0 1 .81-1.22l4.17 6.65.33.31 2.19 1.54a2.44 2.44 0 0 1 .92 1.75v2.77l-.16.13a1.66 1.66 0 0 1-1.63.19l-.75-.36a2.57 2.57 0 0 0-2.55.32l-2.18 1.82a4.28 4.28 0 0 1-.89.55 10.18 10.18 0 0 0-4.62-8.46c-.27-.16-.66.31-.47.48a10.52 10.52 0 0 1 3.68 8.5v.48zm8.38-5.42a.49.49 0 1 0-.49-.49.49.49 0 0 0 .49.49zm-18 9.14v-.52a1.39 1.39 0 0 1 .93-1.25s2.7-.66 3.43-1.84l2.06 1.63a25.62 25.62 0 0 1-6.43 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 912 B

View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<defs>
<style>
use:not(:target) {
display: none;
}
#disabled {
opacity: 0.5;
}
</style>
<path id="shape" d="m 19,0 c 0.554,0 1,0.446 1,1 l 0,22 c 0,0.554 -0.446,1 -1,1 l -2,0 c -0.554,0 -1,-0.446 -1,-1 l 0,-10.595703 c -0.04108,0.105 -0.109944,0.205828 -0.203125,0.298828 L 4.703125,23.775391 c -0.198384,0.197 -0.364738,0.264171 -0.5,0.201171 C 4.067863,23.915564 4,23.748516 4,23.478516 L 4,0.52148438 c 0,-0.26900001 0.06786,-0.43700001 0.203125,-0.5 0.135262,-0.062 0.301616,0.0070781 0.5,0.20507812 l 11.09375,11.0722655 c 0.09318,0.083 0.162045,0.182875 0.203125,0.296875 L 16,1 c 0,-0.554 0.446,-1 1,-1 l 2,0 z" fill="gray"/>
</defs>
<use id="enabled" xlink:href="#shape"/>
<use id="disabled" xlink:href="#shape"/>
</svg>

After

Width:  |  Height:  |  Size: 893 B

View File

@ -0,0 +1,3 @@
<svg id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 18.77">
<path fill="gray" d="M3.13 13.72a1.57 1.57 0 0 1-3.13 0V5.41a1.57 1.57 0 0 1 3.13 0v8.31zm6.29 3.62a1.57 1.57 0 0 1-3.13 0V1.44a1.57 1.57 0 0 1 3.13 0v15.9zm6.29-2.9a1.57 1.57 0 0 1-3.13 0V4.83a1.57 1.57 0 0 1 3.13 0v9.61zM22 12.62a1.57 1.57 0 0 1-3.13 0V6.15a1.57 1.57 0 0 1 3.13 0v6.47z"/>
</svg>

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g fill="gray">
<path d="M1.684,13.486c-0.209,0-0.404-0.132-0.474-0.341c-0.528-1.58-0.23-5.767,4.097-7.921 c1.315-0.656,2.589-0.988,3.787-0.988c3.237,0,5.096,2.341,5.99,3.465c0.158,0.199,0.181,0.533,0,0.713 c-0.793,0.794-1.852,1.542-3.231,2.286c-2.46,1.327-5.045,1.775-7.121,2.134c-1.123,0.194-2.093,0.361-2.89,0.627 C1.789,13.479,1.735,13.486,1.684,13.486L1.684,13.486z"/>
<path d="M23.185,5.465c-0.86-1.121-2.074-1.819-3.168-1.819c-0.641,0-1.556,0.23-2.273,1.328 c-0.374,0.571-0.577,1.161-0.773,1.73c-0.512,1.482-1.041,3.016-4.662,4.969c-2.316,1.249-4.707,1.664-6.815,2.03 c-2.524,0.438-4.704,0.814-5.455,2.622c-0.069,0.165-0.045,0.354,0.062,0.495c0.107,0.143,0.281,0.217,0.46,0.193 c0.667-0.081,1.533,0.041,2.434,0.217c-0.122,0.146-0.261,0.286-0.391,0.418c-0.38,0.385-0.774,0.783-0.657,1.292 c0.108,0.474,0.604,0.699,0.966,0.828c0.399,0.142,0.843,0.217,1.283,0.217c1.241,0,2.216-0.579,2.649-1.539 c1.704,0.287,3.487,0.313,5.043,0.313l1.639-0.006c0.066,0.056,0.178,0.166,0.264,0.25c0.504,0.506,1.348,1.351,2.721,1.351 c0.129,0,0.264-0.008,0.416-0.026c0.687-0.102,1.351-0.267,1.574-0.787c0.227-0.528-0.123-1.023-0.526-1.597 c-0.481-0.685-1.08-1.532-0.998-2.652c0.196-0.397,0.368-0.824,0.546-1.267c0.479-1.19,0.975-2.421,2.12-3.513 c0.431,0.343,1.022,0.549,1.63,0.549l0,0c0.439,0,0.876-0.102,1.295-0.3c0.624-0.293,1.104-0.967,1.316-1.847 C24.175,7.707,23.914,6.418,23.185,5.465L23.185,5.465z M20.397,7.757c-0.276,0-0.5-0.224-0.5-0.5s0.224-0.5,0.5-0.5 c0.275,0,0.5,0.224,0.5,0.5S20.674,7.757,20.397,7.757z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M21.64 12.44L2.827 22.895c-.217.123-.403.137-.56.042-.155-.094-.233-.264-.233-.51V1.572c0-.244.08-.414.233-.51.157-.093.343-.08.56.044L21.642 11.56c.217.124.326.27.326.44 0 .17-.11.316-.327.44z" fill="gray"/>
</svg>

After

Width:  |  Height:  |  Size: 290 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<rect ry="1" rx="1" y="2" x="2" height="20" width="20" fill="gray"/>
</svg>

After

Width:  |  Height:  |  Size: 141 B

View File

@ -0,0 +1,184 @@
:scope {
--border-color: #e5e5e5;
}
#narrate-toggle {
background-image: url("chrome://global/skin/narrate/narrate.svg");
}
.dropdown-popup button {
background-color: transparent;
}
.dropdown-popup button:hover:not(:disabled) {
background-color: #eaeaea;
}
.narrate-row {
display: flex;
align-items: center;
min-height: 40px;
box-sizing: border-box;
}
.narrate-row:not(:first-child) {
border-top: 1px solid var(--border-color);
}
/* Control buttons */
#narrate-control > button {
background-size: 24px 24px;
background-repeat: no-repeat;
background-position: center center;
height: 64px;
width: 100px;
border: none;
color: #666;
box-sizing: border-box;
}
#narrate-control > button:not(:first-child) {
border-left: 1px solid var(--border-color);
}
#narrate-skip-previous {
border-top-left-radius: 3px;
background-image: url("chrome://global/skin/narrate/back.svg#enabled");
}
#narrate-skip-next {
border-top-right-radius: 3px;
background-image: url("chrome://global/skin/narrate/forward.svg#enabled");
}
#narrate-skip-previous:disabled {
background-image: url("chrome://global/skin/narrate/back.svg#disabled");
}
#narrate-skip-next:disabled {
background-image: url("chrome://global/skin/narrate/forward.svg#disabled");
}
#narrate-start-stop {
background-image: url("chrome://global/skin/narrate/start.svg");
}
#narrate-start-stop.speaking {
background-image: url("chrome://global/skin/narrate/stop.svg");
}
/* Rate control */
#narrate-rate::before, #narrate-rate::after {
content: '';
width: 48px;
height: 40px;
background-position: center;
background-repeat: no-repeat;
background-size: 24px auto;
}
#narrate-rate::before {
background-image: url("chrome://global/skin/narrate/slow.svg");
}
#narrate-rate::after {
background-image: url("chrome://global/skin/narrate/fast.svg");
}
#narrate-rate-input {
margin: 0 1px;
flex-grow: 1;
}
#narrate-rate-input::-moz-range-track {
background-color: #979797;
height: 2px;
}
#narrate-rate-input::-moz-range-progress {
background-color: #2EA3FF;
height: 2px;
}
#narrate-rate-input::-moz-range-thumb {
background-color: #808080;
height: 16px;
width: 16px;
border-radius: 8px;
border-width: 0;
}
#narrate-rate-input:active::-moz-range-thumb {
background-color: #2EA3FF;
}
/* Voice selection */
.voiceselect {
width: 100%;
}
.voiceselect > button.select-toggle,
.voiceselect > .options > button.option {
-moz-appearance: none;
border: none;
width: 100%;
min-height: 40px;
}
.voiceselect.open > button.select-toggle {
border-bottom: 1px solid var(--border-color);
}
.voiceselect > button.select-toggle::after {
content: '';
background-image: url("chrome://global/skin/narrate/arrow.svg");
background-position: center;
background-repeat: no-repeat;
background-size: 12px 12px;
display: inline-block;
width: 1.5em;
height: 1em;
vertical-align: middle;
}
.voiceselect > .options > button.option:not(:first-child) {
border-top: 1px solid var(--border-color);
}
.voiceselect > .options > button.option {
box-sizing: border-box;
}
.voiceselect > .options:not(.hovering) > button.option:focus {
background-color: #eaeaea;
}
.voiceselect > .options:not(.hovering) > button.option:hover:not(:focus) {
background-color: transparent;
}
.voiceselect > .options > button.option::-moz-focus-inner {
outline: none;
border: 0;
}
.voiceselect > .options {
display: none;
overflow-y: auto;
}
.voiceselect.open > .options {
display: block;
}
.current-voice {
color: #7f7f7f;
}
.voiceselect:not(.open) > button,
.option:last-child {
border-radius: 0 0 3px 3px;
}

View File

@ -67,15 +67,6 @@ interface nsIMemory : nsISupports
*/
void heapMinimize(in boolean immediate);
/**
* This predicate can be used to determine if we're in a low-memory
* situation (what constitutes low-memory is platform dependent). This
* can be used to trigger the memory pressure observers.
*
* DEPRECATED - Always returns false. See bug 592308.
*/
boolean isLowMemory();
/**
* This predicate can be used to determine if the platform is a "low-memory"
* platform. Callers may use this to dynamically tune their behaviour

View File

@ -34,14 +34,6 @@ nsMemoryImpl::HeapMinimize(bool aImmediate)
return FlushMemory(MOZ_UTF16("heap-minimize"), aImmediate);
}
NS_IMETHODIMP
nsMemoryImpl::IsLowMemory(bool* aResult)
{
NS_ERROR("IsLowMemory is deprecated. See bug 592308.");
*aResult = false;
return NS_OK;
}
NS_IMETHODIMP
nsMemoryImpl::IsLowMemoryPlatform(bool* aResult)
{