Merge tag 'FIREFOX_AURORA_47_BASE'
@ -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]
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
@ -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);
|
||||
});
|
@ -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', {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
16
dom/media/test/test_mediarecorder_mp4_support.html
Normal 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>
|
@ -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) {
|
||||
|
||||
|
16
dom/media/test/test_mediarecorder_webm_support.html
Normal 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>
|
@ -151,6 +151,7 @@ SpeechTaskCallback::OnDidFinishSpeaking()
|
||||
{
|
||||
mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
|
||||
// no longer needed
|
||||
[mSpeechSynthesizer setDelegate:nil];
|
||||
mTask = nullptr;
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,8 @@ interface MediaRecorder : EventTarget {
|
||||
|
||||
[Throws]
|
||||
void requestData();
|
||||
|
||||
static boolean isTypeSupported(DOMString type);
|
||||
};
|
||||
|
||||
dictionary MediaRecorderOptions {
|
||||
|
@ -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;
|
||||
|
11
image/test/crashtests/1253362-1.html
Normal 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>
|
@ -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
|
||||
|
||||
|
@ -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,33 +883,17 @@ 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;
|
||||
}
|
||||
|
||||
// Prioritized messages cannot be compressed.
|
||||
MOZ_RELEASE_ASSERT(aMsg.compress_type() == IPC::Message::COMPRESSION_NONE ||
|
||||
aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
|
||||
aMsg.priority() == IPC::Message::PRIORITY_NORMAL);
|
||||
|
||||
bool compress = false;
|
||||
if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
|
||||
@ -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,8 +999,8 @@ MessageChannel::ProcessPendingRequests(int seqno, int transaction)
|
||||
for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
|
||||
Message &msg = *it;
|
||||
|
||||
MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction,
|
||||
"Calling ShouldDeferMessage when cancelled");
|
||||
MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
|
||||
"Calling ShouldDeferMessage when cancelled");
|
||||
bool defer = ShouldDeferMessage(msg);
|
||||
|
||||
// Only log the interesting messages.
|
||||
@ -806,38 +1017,19 @@ 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
|
||||
MessageChannel::Send(Message* aMsg, Message* aReply)
|
||||
{
|
||||
@ -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(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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
||||
return SendAsyncMessage(mOutgoing[0]++);
|
||||
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:
|
||||
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
||||
return SendAsyncMessage(mOutgoing[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:
|
||||
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
||||
return SendAsyncMessage(mOutgoing[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");
|
||||
|
@ -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);
|
||||
|
@ -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,35 +162,19 @@ 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 Fail(cx, "bad expression type");
|
||||
return type == ExprType::Void ||
|
||||
CheckValType(cx, d, NonVoidToValType(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);
|
||||
|
87
js/src/asmjs/WasmBinary.cpp
Normal 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;
|
||||
}
|
@ -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,8 +523,7 @@ class Decoder
|
||||
MOZ_WARN_UNUSED_RESULT bool read(T* out) {
|
||||
if (bytesRemain() < sizeof(T))
|
||||
return false;
|
||||
if (out)
|
||||
memcpy((void*)out, cur_, sizeof(T));
|
||||
memcpy((void*)out, cur_, sizeof(T));
|
||||
cur_ += sizeof(T);
|
||||
return true;
|
||||
}
|
||||
@ -545,8 +534,7 @@ class Decoder
|
||||
uint32_t u32;
|
||||
if (!readVarU32(&u32) || u32 >= uint32_t(T::Limit))
|
||||
return false;
|
||||
if (out)
|
||||
*out = T(u32);
|
||||
*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,8 +565,7 @@ class Decoder
|
||||
if (!readFixedU8(&byte))
|
||||
return false;
|
||||
if (!(byte & 0x80)) {
|
||||
if (out)
|
||||
*out = u | UInt(byte) << shift;
|
||||
*out = u | UInt(byte) << shift;
|
||||
return true;
|
||||
}
|
||||
u |= UInt(byte & 0x7F) << shift;
|
||||
@ -586,8 +573,7 @@ class Decoder
|
||||
} while (shift != numBitsInSevens);
|
||||
if (!readFixedU8(&byte) || (byte & (unsigned(-1) << remainderBits)))
|
||||
return false;
|
||||
if (out)
|
||||
*out = u | UInt(byte) << numBitsInSevens;
|
||||
*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));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
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;
|
||||
}
|
||||
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)
|
||||
@ -706,7 +690,7 @@ class Decoder
|
||||
goto backup;
|
||||
cur_ += IdSize;
|
||||
*startOffset = before - beg_;
|
||||
return true;
|
||||
return true;
|
||||
backup:
|
||||
cur_ = before;
|
||||
*startOffset = NotStarted;
|
||||
@ -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
|
||||
|
||||
|
@ -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,13 +796,12 @@ ModuleGenerator::finishFuncDef(uint32_t funcIndex, unsigned generateTime, Functi
|
||||
{
|
||||
MOZ_ASSERT(activeFunc_ == fg);
|
||||
|
||||
UniqueFuncBytecode func =
|
||||
js::MakeUnique<FuncBytecode>(funcIndex,
|
||||
funcSig(funcIndex),
|
||||
Move(fg->bytecode_),
|
||||
fg->lineOrBytecode_,
|
||||
Move(fg->callSiteLineNums_),
|
||||
generateTime);
|
||||
auto func = js::MakeUnique<FuncBytes>(Move(fg->bytes_),
|
||||
funcIndex,
|
||||
funcSig(funcIndex),
|
||||
fg->lineOrBytecode_,
|
||||
Move(fg->callSiteLineNums_),
|
||||
generateTime);
|
||||
if (!func)
|
||||
return false;
|
||||
|
||||
|
@ -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;
|
||||
@ -288,13 +288,13 @@ class MOZ_STACK_CLASS FunctionGenerator
|
||||
{
|
||||
friend class ModuleGenerator;
|
||||
|
||||
ModuleGenerator* m_;
|
||||
IonCompileTask* task_;
|
||||
ModuleGenerator* m_;
|
||||
IonCompileTask* task_;
|
||||
|
||||
// Data created during function generation, then handed over to the
|
||||
// FuncBytecode in ModuleGenerator::finishFunc().
|
||||
UniqueBytecode bytecode_;
|
||||
Uint32Vector callSiteLineNums_;
|
||||
// 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);
|
||||
|
@ -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()))
|
||||
return false;
|
||||
}
|
||||
if (!DecodeLocalEntries(d, &locals))
|
||||
return false;
|
||||
|
||||
// Set up for Ion compilation.
|
||||
|
||||
|
@ -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_;
|
||||
uint32_t lineOrBytecode_;
|
||||
Uint32Vector callSiteLineNums_;
|
||||
unsigned generateTime_;
|
||||
|
||||
public:
|
||||
FuncBytecode(uint32_t index,
|
||||
const DeclaredSig& sig,
|
||||
UniqueBytecode bytecode,
|
||||
uint32_t lineOrBytecode,
|
||||
Uint32Vector&& callSiteLineNums,
|
||||
unsigned generateTime)
|
||||
: sig_(sig),
|
||||
FuncBytes(Bytes&& bytes,
|
||||
uint32_t index,
|
||||
const DeclaredSig& sig,
|
||||
uint32_t lineOrBytecode,
|
||||
Uint32Vector&& callSiteLineNums,
|
||||
unsigned generateTime)
|
||||
: 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_;
|
||||
JSRuntime* const runtime_;
|
||||
ModuleGeneratorThreadView& mg_;
|
||||
LifoAlloc lifo_;
|
||||
UniqueFuncBytecode func_;
|
||||
mozilla::Maybe<FuncCompileResults> results_;
|
||||
LifoAlloc lifo_;
|
||||
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();
|
||||
|
@ -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];
|
||||
|
@ -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.
|
||||
|
||||
static const uint32_t MemoryExport = UINT32_MAX;
|
||||
|
||||
struct ExportMap
|
||||
{
|
||||
static const uint32_t MemoryExport = UINT32_MAX;
|
||||
|
||||
CacheableCharsVector fieldNames;
|
||||
Uint32Vector fieldsToExports;
|
||||
Uint32Vector exportFuncIndices;
|
||||
|
@ -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;
|
||||
if (!EncodeLocalEntries(e, varTypes))
|
||||
return false;
|
||||
|
||||
for (ValType type : func.vars()) {
|
||||
if (!e.writeValType(type))
|
||||
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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
@ -594,13 +595,14 @@ static const unsigned NaN64GlobalDataOffset = HeapGlobalDataOffset + sizeof
|
||||
static const unsigned NaN32GlobalDataOffset = NaN64GlobalDataOffset + sizeof(double);
|
||||
static const unsigned InitialGlobalDataBytes = NaN32GlobalDataOffset + sizeof(float);
|
||||
|
||||
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 MaxTableElems = 128 * 1024;
|
||||
static const unsigned MaxArgsPerFunc = 4 * 1024;
|
||||
static const unsigned MaxBrTableElems = 4 * 1024;
|
||||
static const unsigned MaxSigs = 4 * 1024;
|
||||
static const unsigned MaxFuncs = 512 * 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 * 1024;
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace js
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -1,8 +1,5 @@
|
||||
load(libdir + "wasm.js");
|
||||
|
||||
if (!wasmIsSupported())
|
||||
quit();
|
||||
|
||||
function testLoad(type, ext, base, offset, align, expect) {
|
||||
assertEq(wasmEvalText(
|
||||
'(module' +
|
||||
|
@ -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/);
|
||||
|
26
js/src/jit-test/tests/wasm/memory-aliasing.js
Normal 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);
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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*)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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()) {
|
||||
|
@ -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),
|
||||
|
@ -9305,40 +9305,52 @@ PresShell::Observe(nsISupports* aSubject,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!nsCRT::strcmp(aTopic, "agent-sheet-added") && mStyleSet) {
|
||||
AddAgentSheet(aSubject);
|
||||
if (!nsCRT::strcmp(aTopic, "agent-sheet-added")) {
|
||||
if (mStyleSet) {
|
||||
AddAgentSheet(aSubject);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aTopic, "user-sheet-added") && mStyleSet) {
|
||||
AddUserSheet(aSubject);
|
||||
if (!nsCRT::strcmp(aTopic, "user-sheet-added")) {
|
||||
if (mStyleSet) {
|
||||
AddUserSheet(aSubject);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aTopic, "author-sheet-added") && mStyleSet) {
|
||||
AddAuthorSheet(aSubject);
|
||||
if (!nsCRT::strcmp(aTopic, "author-sheet-added")) {
|
||||
if (mStyleSet) {
|
||||
AddAuthorSheet(aSubject);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aTopic, "agent-sheet-removed") && mStyleSet) {
|
||||
RemoveSheet(SheetType::Agent, aSubject);
|
||||
if (!nsCRT::strcmp(aTopic, "agent-sheet-removed")) {
|
||||
if (mStyleSet) {
|
||||
RemoveSheet(SheetType::Agent, aSubject);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aTopic, "user-sheet-removed") && mStyleSet) {
|
||||
RemoveSheet(SheetType::User, aSubject);
|
||||
if (!nsCRT::strcmp(aTopic, "user-sheet-removed")) {
|
||||
if (mStyleSet) {
|
||||
RemoveSheet(SheetType::User, aSubject);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!nsCRT::strcmp(aTopic, "author-sheet-removed") && mStyleSet) {
|
||||
RemoveSheet(SheetType::Doc, aSubject);
|
||||
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()) {
|
||||
DoUpdateImageVisibility(/* aRemoveOnly = */ true);
|
||||
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
0
layout/reftests/fonts/sil/CharisSIL-R.ttf
Executable file → Normal 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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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) {
|
||||
|
@ -34,6 +34,7 @@ DIRS += [
|
||||
'gfx',
|
||||
'jsdownloads',
|
||||
'lz4',
|
||||
'narrate',
|
||||
'mediasniffer',
|
||||
'microformats',
|
||||
'osfile',
|
||||
|
93
toolkit/components/narrate/.eslintrc
Normal 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
|
||||
}
|
||||
}
|
244
toolkit/components/narrate/NarrateControls.jsm
Normal 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;
|
||||
}
|
||||
};
|
219
toolkit/components/narrate/Narrator.jsm
Normal 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();
|
||||
}
|
||||
};
|
291
toolkit/components/narrate/VoiceSelect.jsm
Normal 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 : "";
|
||||
}
|
||||
};
|
13
toolkit/components/narrate/moz.build
Normal 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']
|
21
toolkit/components/narrate/test/.eslintrc
Normal 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
|
||||
}
|
||||
}
|
114
toolkit/components/narrate/test/NarrateTestUtils.jsm
Normal 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);
|
||||
});
|
||||
}
|
||||
};
|
8
toolkit/components/narrate/test/browser.ini
Normal file
@ -0,0 +1,8 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
head.js
|
||||
NarrateTestUtils.jsm
|
||||
|
||||
[browser_narrate.js]
|
||||
[browser_narrate_disable.js]
|
||||
[browser_voiceselect.js]
|
101
toolkit/components/narrate/test/browser_narrate.js
Normal 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");
|
||||
});
|
||||
});
|
37
toolkit/components/narrate/test/browser_narrate_disable.js
Normal 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");
|
||||
});
|
||||
});
|
106
toolkit/components/narrate/test/browser_voiceselect.js
Normal 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");
|
||||
});
|
||||
});
|
67
toolkit/components/narrate/test/head.js
Normal 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);
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
let toggleHeight = dropdownToggle.offsetHeight;
|
||||
let toggleTop = dropdownToggle.offsetTop;
|
||||
let popupTop = toggleTop - toggleHeight / 2;
|
||||
|
||||
if (this._isToolbarVertical) {
|
||||
win.addEventListener("resize", event => {
|
||||
if (!event.isTrusted)
|
||||
return;
|
||||
dropdownPopup.style.top = popupTop + "px";
|
||||
},
|
||||
|
||||
// Wait for reflow before calculating the new position of the popup.
|
||||
win.setTimeout(updatePopupPosition, 0);
|
||||
}, true);
|
||||
}
|
||||
_toggleDropdownClicked: function(event) {
|
||||
let dropdown = event.target.closest('.dropdown');
|
||||
|
||||
dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
|
||||
dropdownToggle.addEventListener("click", event => {
|
||||
if (!event.isTrusted)
|
||||
return;
|
||||
if (!dropdown)
|
||||
return;
|
||||
|
||||
event.stopPropagation();
|
||||
event.stopPropagation();
|
||||
|
||||
if (dropdown.classList.contains("open")) {
|
||||
this._closeDropdown();
|
||||
} else {
|
||||
this._openDropdown();
|
||||
if (this._isToolbarVertical) {
|
||||
updatePopupPosition();
|
||||
}
|
||||
if (dropdown.classList.contains("open")) {
|
||||
this._closeDropdowns();
|
||||
} else {
|
||||
this._openDropdown(dropdown);
|
||||
if (this._isToolbarVertical) {
|
||||
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");
|
||||
this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
|
||||
},
|
||||
if (openDropdowns.length) {
|
||||
this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
19
toolkit/locales/en-US/chrome/global/narrate.properties
Normal 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)
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
11
toolkit/themes/shared/narrate.css
Normal file
@ -0,0 +1,11 @@
|
||||
body.light .narrating {
|
||||
background-color: #ffc;
|
||||
}
|
||||
|
||||
body.sepia .narrating {
|
||||
background-color: #e0d7c5;
|
||||
}
|
||||
|
||||
body.dark .narrating {
|
||||
background-color: #242424;
|
||||
}
|
3
toolkit/themes/shared/narrate/arrow.svg
Normal 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 |
15
toolkit/themes/shared/narrate/back.svg
Normal 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 |
3
toolkit/themes/shared/narrate/fast.svg
Normal 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 |
15
toolkit/themes/shared/narrate/forward.svg
Normal 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 |
3
toolkit/themes/shared/narrate/narrate.svg
Normal 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 |
6
toolkit/themes/shared/narrate/slow.svg
Normal 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 |
3
toolkit/themes/shared/narrate/start.svg
Normal 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 |
3
toolkit/themes/shared/narrate/stop.svg
Normal 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 |
184
toolkit/themes/shared/narrateControls.css
Normal 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;
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|