Merge tag 'FIREFOX_AURORA_47_BASE'
@ -48,6 +48,8 @@ support-files =
|
|||||||
[browser_ext_tabs_sendMessage.js]
|
[browser_ext_tabs_sendMessage.js]
|
||||||
[browser_ext_tabs_move.js]
|
[browser_ext_tabs_move.js]
|
||||||
[browser_ext_tabs_move_window.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_tabs_onHighlighted.js]
|
||||||
[browser_ext_windows_create.js]
|
[browser_ext_windows_create.js]
|
||||||
[browser_ext_windows_create_tabId.js]
|
[browser_ext_windows_create_tabId.js]
|
||||||
|
@ -40,82 +40,3 @@ add_task(function* () {
|
|||||||
}
|
}
|
||||||
yield BrowserTestUtils.closeWindow(window1);
|
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
|
* @param {Object} extraURLCampaignParams - An object containing additional
|
||||||
* paramaters for the URL opened by the browser for reasons of promotional
|
* paramaters for the URL opened by the browser for reasons of promotional
|
||||||
* campaign tracking. Each attribute of the object must have a name that
|
* 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
|
* is a string, begins with "utm_" and contains only only alphanumeric
|
||||||
* alphanumeric characters, dashes or underscores. The values may be any
|
* characters, dashes or underscores. The values may be any string and will
|
||||||
* string and will automatically be encoded.
|
* automatically be encoded.
|
||||||
*/
|
*/
|
||||||
Mozilla.UITour.showFirefoxAccounts = function(extraURLCampaignParams) {
|
Mozilla.UITour.showFirefoxAccounts = function(extraURLCampaignParams) {
|
||||||
_sendEvent('showFirefoxAccounts', {
|
_sendEvent('showFirefoxAccounts', {
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
#include "nsProxyRelease.h"
|
#include "nsProxyRelease.h"
|
||||||
#include "nsTArray.h"
|
#include "nsTArray.h"
|
||||||
#include "GeckoProfiler.h"
|
#include "GeckoProfiler.h"
|
||||||
|
#include "nsContentTypeParser.h"
|
||||||
|
#include "nsCharSeparatedTokenizer.h"
|
||||||
|
|
||||||
#ifdef LOG
|
#ifdef LOG
|
||||||
#undef LOG
|
#undef LOG
|
||||||
@ -971,6 +973,11 @@ MediaRecorder::Constructor(const GlobalObject& aGlobal,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!IsTypeSupported(aInitDict.mMimeType)) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
|
RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
|
||||||
object->SetOptions(aInitDict);
|
object->SetOptions(aInitDict);
|
||||||
return object.forget();
|
return object.forget();
|
||||||
@ -1005,6 +1012,11 @@ MediaRecorder::Constructor(const GlobalObject& aGlobal,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!IsTypeSupported(aInitDict.mMimeType)) {
|
||||||
|
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
|
RefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
|
||||||
aSrcOutput,
|
aSrcOutput,
|
||||||
ownerWindow);
|
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
|
nsresult
|
||||||
MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
|
MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@ class ErrorResult;
|
|||||||
class MediaInputPort;
|
class MediaInputPort;
|
||||||
struct MediaRecorderOptions;
|
struct MediaRecorderOptions;
|
||||||
class MediaStream;
|
class MediaStream;
|
||||||
|
class GlobalObject;
|
||||||
|
|
||||||
namespace dom {
|
namespace dom {
|
||||||
|
|
||||||
@ -78,6 +79,9 @@ public:
|
|||||||
// Return the current encoding MIME type selected by the MediaEncoder.
|
// Return the current encoding MIME type selected by the MediaEncoder.
|
||||||
void GetMimeType(nsString &aMimeType);
|
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.
|
// Construct a recorder with a DOM media stream object as its source.
|
||||||
static already_AddRefed<MediaRecorder>
|
static already_AddRefed<MediaRecorder>
|
||||||
Constructor(const GlobalObject& aGlobal,
|
Constructor(const GlobalObject& aGlobal,
|
||||||
|
@ -1287,15 +1287,6 @@ nsresult FileMediaResource::Open(nsIStreamListener** aStreamListener)
|
|||||||
rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
|
rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
|
||||||
}
|
}
|
||||||
} else {
|
} 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));
|
rv = mChannel->Open2(getter_AddRefs(mInput));
|
||||||
}
|
}
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
@ -1360,10 +1351,6 @@ already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaResourceCallba
|
|||||||
nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
|
nsCOMPtr<nsILoadGroup> loadGroup = element->GetDocumentLoadGroup();
|
||||||
NS_ENSURE_TRUE(loadGroup, nullptr);
|
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));
|
MOZ_ASSERT(element->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
|
||||||
nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
|
nsContentPolicyType contentPolicyType = element->IsHTMLElement(nsGkAtoms::audio) ?
|
||||||
nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
|
nsIContentPolicy::TYPE_INTERNAL_AUDIO : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
|
||||||
@ -1375,7 +1362,7 @@ already_AddRefed<MediaResource> FileMediaResource::CloneData(MediaResourceCallba
|
|||||||
NS_NewChannel(getter_AddRefs(channel),
|
NS_NewChannel(getter_AddRefs(channel),
|
||||||
mURI,
|
mURI,
|
||||||
element,
|
element,
|
||||||
securityFlags,
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
|
||||||
contentPolicyType,
|
contentPolicyType,
|
||||||
loadGroup,
|
loadGroup,
|
||||||
nullptr, // aCallbacks
|
nullptr, // aCallbacks
|
||||||
|
@ -732,6 +732,12 @@ tags=msg capturestream
|
|||||||
tags=msg capturestream
|
tags=msg capturestream
|
||||||
[test_mediarecorder_unsupported_src.html]
|
[test_mediarecorder_unsupported_src.html]
|
||||||
tags=msg
|
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]
|
[test_mediarecorder_record_getdata_afterstart.html]
|
||||||
tags=msg capturestream
|
tags=msg capturestream
|
||||||
[test_mediatrack_consuming_mediaresource.html]
|
[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">
|
<pre id="test">
|
||||||
<script class="testbody" type="text/javascript">
|
<script class="testbody" type="text/javascript">
|
||||||
|
|
||||||
|
|
||||||
function startTest() {
|
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},
|
navigator.mozGetUserMedia({audio: false, video: true, fake: true},
|
||||||
function(stream) {
|
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);
|
mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
|
||||||
// no longer needed
|
// no longer needed
|
||||||
|
[mSpeechSynthesizer setDelegate:nil];
|
||||||
mTask = nullptr;
|
mTask = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ interface MediaRecorder : EventTarget {
|
|||||||
|
|
||||||
[Throws]
|
[Throws]
|
||||||
void requestData();
|
void requestData();
|
||||||
|
|
||||||
|
static boolean isTypeSupported(DOMString type);
|
||||||
};
|
};
|
||||||
|
|
||||||
dictionary MediaRecorderOptions {
|
dictionary MediaRecorderOptions {
|
||||||
|
@ -115,6 +115,12 @@ SVGDocumentWrapper::FlushImageTransformInvalidation()
|
|||||||
bool
|
bool
|
||||||
SVGDocumentWrapper::IsAnimated()
|
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();
|
nsIDocument* doc = mViewer->GetDocument();
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return false;
|
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 1242778-1.png
|
||||||
load 1249576-1.png
|
load 1249576-1.png
|
||||||
load 1251091-1.html
|
load 1251091-1.html
|
||||||
|
load 1253362-1.html
|
||||||
load colormap-range.gif
|
load colormap-range.gif
|
||||||
HTTP load delayedframe.sjs # A 3-frame animated GIF with an inordinate delay between the second and third frame
|
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;
|
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)
|
MessageChannel::MessageChannel(MessageListener *aListener)
|
||||||
: mListener(aListener),
|
: mListener(aListener),
|
||||||
mChannelState(ChannelClosed),
|
mChannelState(ChannelClosed),
|
||||||
@ -293,17 +479,11 @@ MessageChannel::MessageChannel(MessageListener *aListener)
|
|||||||
mInTimeoutSecondHalf(false),
|
mInTimeoutSecondHalf(false),
|
||||||
mNextSeqno(0),
|
mNextSeqno(0),
|
||||||
mLastSendError(SyncSendError::SendSuccess),
|
mLastSendError(SyncSendError::SendSuccess),
|
||||||
mAwaitingSyncReply(false),
|
|
||||||
mAwaitingSyncReplyPriority(0),
|
|
||||||
mDispatchingSyncMessage(false),
|
|
||||||
mDispatchingSyncMessagePriority(0),
|
|
||||||
mDispatchingAsyncMessage(false),
|
mDispatchingAsyncMessage(false),
|
||||||
mDispatchingAsyncMessagePriority(0),
|
mDispatchingAsyncMessagePriority(0),
|
||||||
mCurrentTransaction(0),
|
mTransactionStack(nullptr),
|
||||||
mPendingSendPriorities(0),
|
|
||||||
mTimedOutMessageSeqno(0),
|
mTimedOutMessageSeqno(0),
|
||||||
mTimedOutMessagePriority(0),
|
mTimedOutMessagePriority(0),
|
||||||
mRecvdErrors(0),
|
|
||||||
mRemoteStackDepthGuess(false),
|
mRemoteStackDepthGuess(false),
|
||||||
mSawInterruptOutMsg(false),
|
mSawInterruptOutMsg(false),
|
||||||
mIsWaitingForIncoming(false),
|
mIsWaitingForIncoming(false),
|
||||||
@ -344,6 +524,51 @@ MessageChannel::~MessageChannel()
|
|||||||
Clear();
|
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
|
static void
|
||||||
PrintErrorMessage(Side side, const char* channelName, const char* msg)
|
PrintErrorMessage(Side side, const char* channelName, const char* msg)
|
||||||
{
|
{
|
||||||
@ -405,7 +630,6 @@ MessageChannel::Clear()
|
|||||||
|
|
||||||
// Free up any memory used by pending messages.
|
// Free up any memory used by pending messages.
|
||||||
mPending.clear();
|
mPending.clear();
|
||||||
mRecvd = nullptr;
|
|
||||||
mOutOfTurnReplies.clear();
|
mOutOfTurnReplies.clear();
|
||||||
while (!mDeferred.empty()) {
|
while (!mDeferred.empty()) {
|
||||||
mDeferred.pop();
|
mDeferred.pop();
|
||||||
@ -622,7 +846,7 @@ MessageChannel::ShouldDeferMessage(const Message& aMsg)
|
|||||||
// child's message comes in, we can pretend the child hasn't quite
|
// 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
|
// finished sending it yet. Since the message is sync, we know that the
|
||||||
// child hasn't moved on yet.
|
// 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.
|
// Predicate that is true for messages that should be consolidated if 'compress' is set.
|
||||||
@ -659,33 +883,17 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(aMsg.transaction_id() == mCurrentTransaction);
|
|
||||||
MOZ_RELEASE_ASSERT(AwaitingSyncReply());
|
MOZ_RELEASE_ASSERT(AwaitingSyncReply());
|
||||||
MOZ_RELEASE_ASSERT(!mRecvd);
|
|
||||||
MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
|
MOZ_RELEASE_ASSERT(!mTimedOutMessageSeqno);
|
||||||
|
|
||||||
// Rather than storing errors in mRecvd, we mark them in
|
mTransactionStack->HandleReply(aMsg);
|
||||||
// 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);
|
|
||||||
NotifyWorkerThread();
|
NotifyWorkerThread();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prioritized messages cannot be compressed.
|
// Prioritized messages cannot be compressed.
|
||||||
MOZ_RELEASE_ASSERT(aMsg.compress_type() == IPC::Message::COMPRESSION_NONE ||
|
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;
|
bool compress = false;
|
||||||
if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
|
if (aMsg.compress_type() == IPC::Message::COMPRESSION_ENABLED) {
|
||||||
@ -768,8 +976,11 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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);
|
IPC_LOG("ProcessPendingRequests for seqno=%d, xid=%d", seqno, transaction);
|
||||||
|
|
||||||
// Loop until there aren't any more priority messages to process.
|
// 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
|
// operating with weird state (as if no Send is in progress). That could
|
||||||
// cause even normal priority sync messages to be processed (but not
|
// cause even normal priority sync messages to be processed (but not
|
||||||
// normal priority async messages), which would break message ordering.
|
// normal priority async messages), which would break message ordering.
|
||||||
if (WasTransactionCanceled(transaction)) {
|
if (aTransaction.IsCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -788,8 +999,8 @@ MessageChannel::ProcessPendingRequests(int seqno, int transaction)
|
|||||||
for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
|
for (MessageQueue::iterator it = mPending.begin(); it != mPending.end(); ) {
|
||||||
Message &msg = *it;
|
Message &msg = *it;
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction,
|
MOZ_RELEASE_ASSERT(!aTransaction.IsCanceled(),
|
||||||
"Calling ShouldDeferMessage when cancelled");
|
"Calling ShouldDeferMessage when cancelled");
|
||||||
bool defer = ShouldDeferMessage(msg);
|
bool defer = ShouldDeferMessage(msg);
|
||||||
|
|
||||||
// Only log the interesting messages.
|
// Only log the interesting messages.
|
||||||
@ -806,38 +1017,19 @@ MessageChannel::ProcessPendingRequests(int seqno, int transaction)
|
|||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toProcess.empty())
|
if (toProcess.empty()) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Processing these messages could result in more messages, so we
|
// Processing these messages could result in more messages, so we
|
||||||
// loop around to check for more afterwards.
|
// 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);
|
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
|
bool
|
||||||
MessageChannel::Send(Message* aMsg, Message* aReply)
|
MessageChannel::Send(Message* aMsg, Message* aReply)
|
||||||
{
|
{
|
||||||
@ -866,8 +1058,7 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCurrentTransaction &&
|
if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
|
||||||
DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_NORMAL &&
|
|
||||||
msg->priority() > IPC::Message::PRIORITY_NORMAL)
|
msg->priority() > IPC::Message::PRIORITY_NORMAL)
|
||||||
{
|
{
|
||||||
// Don't allow sending CPOWs while we're dispatching a sync message.
|
// Don't allow sending CPOWs while we're dispatching a sync message.
|
||||||
@ -877,9 +1068,8 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCurrentTransaction &&
|
if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
|
||||||
(DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
|
DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
|
||||||
DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT))
|
|
||||||
{
|
{
|
||||||
// Generally only the parent dispatches urgent messages. And the only
|
// Generally only the parent dispatches urgent messages. And the only
|
||||||
// sync messages it can send are high-priority. Mainly we want to ensure
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCurrentTransaction &&
|
if (msg->priority() < DispatchingSyncMessagePriority() ||
|
||||||
(msg->priority() < DispatchingSyncMessagePriority() ||
|
msg->priority() < AwaitingSyncReplyPriority())
|
||||||
msg->priority() < AwaitingSyncReplyPriority()))
|
|
||||||
{
|
{
|
||||||
MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
|
MOZ_RELEASE_ASSERT(DispatchingSyncMessage() || DispatchingAsyncMessage());
|
||||||
IPC_LOG("Cancel from Send");
|
IPC_LOG("Cancel from Send");
|
||||||
CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
|
CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
|
||||||
CancelTransaction(mCurrentTransaction);
|
CancelTransaction(CurrentHighPriorityTransaction());
|
||||||
mLink->SendMessage(cancel);
|
mLink->SendMessage(cancel);
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC_ASSERT(msg->is_sync(), "can only Send() sync messages here");
|
IPC_ASSERT(msg->is_sync(), "can only Send() sync messages here");
|
||||||
|
|
||||||
if (mCurrentTransaction) {
|
IPC_ASSERT(msg->priority() >= DispatchingSyncMessagePriority(),
|
||||||
IPC_ASSERT(msg->priority() >= DispatchingSyncMessagePriority(),
|
"can't send sync message of a lesser priority than what's being dispatched");
|
||||||
"can't send sync message of a lesser priority than what's being dispatched");
|
IPC_ASSERT(AwaitingSyncReplyPriority() <= msg->priority(),
|
||||||
IPC_ASSERT(AwaitingSyncReplyPriority() <= msg->priority(),
|
"nested sync message sends must be of increasing priority");
|
||||||
"nested sync message sends must be of increasing priority");
|
IPC_ASSERT(DispatchingSyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
|
||||||
IPC_ASSERT(DispatchingSyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
|
"not allowed to send messages while dispatching urgent messages");
|
||||||
"not allowed to send messages while dispatching urgent messages");
|
|
||||||
}
|
|
||||||
|
|
||||||
IPC_ASSERT(DispatchingAsyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
|
IPC_ASSERT(DispatchingAsyncMessagePriority() != IPC::Message::PRIORITY_URGENT,
|
||||||
"not allowed to send messages while dispatching urgent messages");
|
"not allowed to send messages while dispatching urgent messages");
|
||||||
@ -927,27 +1114,28 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
|
|||||||
int prio = msg->priority();
|
int prio = msg->priority();
|
||||||
msgid_t replyType = msg->type() + 1;
|
msgid_t replyType = msg->type() + 1;
|
||||||
|
|
||||||
AutoSetValue<bool> replies(mAwaitingSyncReply, true);
|
AutoEnterTransaction *stackTop = mTransactionStack;
|
||||||
AutoSetValue<int> prioSet(mAwaitingSyncReplyPriority, prio);
|
|
||||||
AutoEnterTransaction transact(this, seqno);
|
|
||||||
|
|
||||||
int prios = mPendingSendPriorities | (1 << prio);
|
// If the most recent message on the stack is high priority, then our
|
||||||
AutoSetValue<int> priosSet(mPendingSendPriorities, prios);
|
// 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
|
||||||
int32_t transaction = mCurrentTransaction;
|
// 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);
|
msg->set_transaction_id(transaction);
|
||||||
|
|
||||||
IPC_LOG("Send seqno=%d, xid=%d, pending=%d", seqno, transaction, prios);
|
|
||||||
|
|
||||||
bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
|
bool handleWindowsMessages = mListener->HandleWindowsMessages(*aMsg);
|
||||||
|
AutoEnterTransaction transact(this, seqno, transaction, prio);
|
||||||
|
|
||||||
|
IPC_LOG("Send seqno=%d, xid=%d", seqno, transaction);
|
||||||
|
|
||||||
mLink->SendMessage(msg.forget());
|
mLink->SendMessage(msg.forget());
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
ProcessPendingRequests(seqno, transaction);
|
MOZ_RELEASE_ASSERT(!transact.IsCanceled());
|
||||||
if (WasTransactionCanceled(transaction)) {
|
ProcessPendingRequests(transact);
|
||||||
IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
|
if (transact.IsComplete()) {
|
||||||
mLastSendError = SyncSendError::CancelledAfterSend;
|
break;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (!Connected()) {
|
if (!Connected()) {
|
||||||
ReportConnectionError("MessageChannel::Send");
|
ReportConnectionError("MessageChannel::Send");
|
||||||
@ -955,23 +1143,12 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
|
|||||||
return false;
|
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(!mTimedOutMessageSeqno);
|
||||||
|
MOZ_RELEASE_ASSERT(!transact.IsComplete());
|
||||||
|
MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
|
|
||||||
bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
|
bool maybeTimedOut = !WaitForSyncNotify(handleWindowsMessages);
|
||||||
|
|
||||||
if (mListener->NeedArtificialSleep()) {
|
if (mListener->NeedArtificialSleep()) {
|
||||||
MonitorAutoUnlock unlock(*mMonitor);
|
MonitorAutoUnlock unlock(*mMonitor);
|
||||||
mListener->ArtificialSleep();
|
mListener->ArtificialSleep();
|
||||||
@ -983,31 +1160,21 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WasTransactionCanceled(transaction)) {
|
if (transact.IsCanceled()) {
|
||||||
IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
|
break;
|
||||||
mLastSendError = SyncSendError::CancelledAfterSend;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOZ_RELEASE_ASSERT(mTransactionStack == &transact);
|
||||||
|
|
||||||
// We only time out a message if it initiated a new transaction (i.e.,
|
// 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).
|
// if neither side has any other message Sends on the stack).
|
||||||
bool canTimeOut = transaction == seqno;
|
bool canTimeOut = transact.IsBottom();
|
||||||
if (maybeTimedOut && canTimeOut && !ShouldContinueFromTimeout()) {
|
if (maybeTimedOut && canTimeOut && !ShouldContinueFromTimeout()) {
|
||||||
// Since ShouldContinueFromTimeout drops the lock, we need to
|
// Since ShouldContinueFromTimeout drops the lock, we need to
|
||||||
// re-check all our conditions here. We shouldn't time out if any of
|
// 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
|
// these things happen because there won't be a reply to the timed
|
||||||
// out message in these cases.
|
// out message in these cases.
|
||||||
if (WasTransactionCanceled(transaction)) {
|
if (transact.IsComplete()) {
|
||||||
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) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1018,18 +1185,36 @@ MessageChannel::Send(Message* aMsg, Message* aReply)
|
|||||||
mLastSendError = SyncSendError::TimedOut;
|
mLastSendError = SyncSendError::TimedOut;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transact.IsCanceled()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(mRecvd);
|
if (transact.IsCanceled()) {
|
||||||
MOZ_RELEASE_ASSERT(mRecvd->is_reply(), "expected reply");
|
IPC_LOG("Other side canceled seqno=%d, xid=%d", seqno, transaction);
|
||||||
MOZ_RELEASE_ASSERT(!mRecvd->is_reply_error());
|
mLastSendError = SyncSendError::CancelledAfterSend;
|
||||||
MOZ_RELEASE_ASSERT(mRecvd->seqno() == seqno);
|
return false;
|
||||||
MOZ_RELEASE_ASSERT(mRecvd->type() == replyType, "wrong reply type");
|
}
|
||||||
MOZ_RELEASE_ASSERT(mRecvd->is_sync());
|
|
||||||
|
|
||||||
*aReply = Move(*mRecvd);
|
if (transact.IsError()) {
|
||||||
mRecvd = nullptr;
|
IPC_LOG("Error: seqno=%d, xid=%d", seqno, transaction);
|
||||||
mLastSendError = SyncSendError::SendSuccess;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1252,16 +1437,6 @@ MessageChannel::ProcessPendingRequest(const Message &aUrgent)
|
|||||||
AssertWorkerThread();
|
AssertWorkerThread();
|
||||||
mMonitor->AssertCurrentThreadOwns();
|
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());
|
IPC_LOG("Process pending: seqno=%d, xid=%d", aUrgent.seqno(), aUrgent.transaction_id());
|
||||||
|
|
||||||
DispatchMessage(aUrgent);
|
DispatchMessage(aUrgent);
|
||||||
@ -1270,13 +1445,6 @@ MessageChannel::ProcessPendingRequest(const Message &aUrgent)
|
|||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1352,8 +1520,6 @@ MessageChannel::OnMaybeDequeueOne()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should not be in a transaction yet if we're not blocked.
|
|
||||||
MOZ_RELEASE_ASSERT(mCurrentTransaction == 0);
|
|
||||||
DispatchMessage(recvd);
|
DispatchMessage(recvd);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -1374,7 +1540,7 @@ MessageChannel::DispatchMessage(const Message &aMsg)
|
|||||||
AutoEnterTransaction transaction(this, aMsg);
|
AutoEnterTransaction transaction(this, aMsg);
|
||||||
|
|
||||||
int id = aMsg.transaction_id();
|
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);
|
MonitorAutoUnlock unlock(*mMonitor);
|
||||||
@ -1392,7 +1558,7 @@ MessageChannel::DispatchMessage(const Message &aMsg)
|
|||||||
mListener->ArtificialSleep();
|
mListener->ArtificialSleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCurrentTransaction != id) {
|
if (reply && transaction.IsCanceled()) {
|
||||||
// The transaction has been canceled. Don't send a reply.
|
// 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);
|
IPC_LOG("Nulling out reply due to cancellation, seqno=%d, xid=%d", aMsg.seqno(), id);
|
||||||
reply = nullptr;
|
reply = nullptr;
|
||||||
@ -1420,8 +1586,6 @@ MessageChannel::DispatchSyncMessage(const Message& aMsg, Message*& aReply)
|
|||||||
Result rv;
|
Result rv;
|
||||||
{
|
{
|
||||||
AutoSetValue<MessageChannel*> blocked(blockingVar, this);
|
AutoSetValue<MessageChannel*> blocked(blockingVar, this);
|
||||||
AutoSetValue<bool> sync(mDispatchingSyncMessage, true);
|
|
||||||
AutoSetValue<int> prioSet(mDispatchingSyncMessagePriority, prio);
|
|
||||||
rv = mListener->OnMessageReceived(aMsg, aReply);
|
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
|
// tampered with (by us). If so, they don't reset the variable to the old
|
||||||
// value.
|
// value.
|
||||||
|
|
||||||
IPC_LOG("CancelTransaction: xid=%d prios=%d", transaction, mPendingSendPriorities);
|
IPC_LOG("CancelTransaction: xid=%d", transaction);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An unusual case: We timed out a transaction which the other side then
|
// 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
|
// 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.
|
// 2. Parent times out H.
|
||||||
// 3. Child dispatches H and sends nested message H' (same transaction).
|
// 3. Child dispatches H and sends nested message H' (same transaction).
|
||||||
// 4. Parent dispatches H' and cancels.
|
// 4. Parent dispatches H' and cancels.
|
||||||
MOZ_RELEASE_ASSERT(!mCurrentTransaction || mCurrentTransaction == transaction);
|
MOZ_RELEASE_ASSERT(!mTransactionStack || mTransactionStack->TransactionID() == transaction);
|
||||||
mCurrentTransaction = 0;
|
if (mTransactionStack) {
|
||||||
|
mTransactionStack->Cancel();
|
||||||
// During a timeout Send should always fail.
|
}
|
||||||
MOZ_RELEASE_ASSERT(!mAwaitingSyncReply);
|
|
||||||
} else {
|
} else {
|
||||||
MOZ_RELEASE_ASSERT(mCurrentTransaction == transaction);
|
MOZ_RELEASE_ASSERT(mTransactionStack->TransactionID() == transaction);
|
||||||
mCurrentTransaction = 0;
|
mTransactionStack->Cancel();
|
||||||
|
|
||||||
mAwaitingSyncReply = false;
|
|
||||||
mAwaitingSyncReplyPriority = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool foundSync = false;
|
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
|
// 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
|
// have a queued sync message. We want to drop this message from the
|
||||||
// queue since it will get cancelled along with the transaction being
|
// queue since if will get cancelled along with the transaction being
|
||||||
// cancelled. We don't bother doing this for normal priority messages
|
// cancelled. This happens if the message in the queue is high priority.
|
||||||
// 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.
|
|
||||||
if (msg.is_sync() && msg.priority() != IPC::Message::PRIORITY_NORMAL) {
|
if (msg.is_sync() && msg.priority() != IPC::Message::PRIORITY_NORMAL) {
|
||||||
MOZ_RELEASE_ASSERT(!foundSync);
|
MOZ_RELEASE_ASSERT(!foundSync);
|
||||||
MOZ_RELEASE_ASSERT(msg.transaction_id() != transaction);
|
MOZ_RELEASE_ASSERT(msg.transaction_id() != transaction);
|
||||||
@ -2201,36 +2327,32 @@ MessageChannel::CancelTransaction(int transaction)
|
|||||||
continue;
|
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++;
|
it++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We could also zero out mDispatchingSyncMessage here. However, that would
|
bool
|
||||||
// cause a race because mDispatchingSyncMessage is a worker-thread-only
|
MessageChannel::IsInTransaction() const
|
||||||
// field and we can be called on the I/O thread. Luckily, we can check to
|
{
|
||||||
// see if mCurrentTransaction is 0 before examining DispatchSyncMessage.
|
MonitorAutoLock lock(*mMonitor);
|
||||||
|
return !!mTransactionStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MessageChannel::CancelCurrentTransaction()
|
MessageChannel::CancelCurrentTransaction()
|
||||||
{
|
{
|
||||||
MonitorAutoLock lock(*mMonitor);
|
MonitorAutoLock lock(*mMonitor);
|
||||||
if (mCurrentTransaction) {
|
if (DispatchingSyncMessagePriority() >= IPC::Message::PRIORITY_HIGH) {
|
||||||
if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
|
if (DispatchingSyncMessagePriority() == IPC::Message::PRIORITY_URGENT ||
|
||||||
DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
|
DispatchingAsyncMessagePriority() == IPC::Message::PRIORITY_URGENT)
|
||||||
{
|
{
|
||||||
mListener->IntentionalCrash();
|
mListener->IntentionalCrash();
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC_LOG("Cancel requested: current xid=%d", mCurrentTransaction);
|
IPC_LOG("Cancel requested: current xid=%d", CurrentHighPriorityTransaction());
|
||||||
MOZ_RELEASE_ASSERT(DispatchingSyncMessage());
|
MOZ_RELEASE_ASSERT(DispatchingSyncMessage());
|
||||||
CancelMessage *cancel = new CancelMessage(mCurrentTransaction);
|
CancelMessage *cancel = new CancelMessage(CurrentHighPriorityTransaction());
|
||||||
CancelTransaction(mCurrentTransaction);
|
CancelTransaction(CurrentHighPriorityTransaction());
|
||||||
mLink->SendMessage(cancel);
|
mLink->SendMessage(cancel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,8 @@ enum class SyncSendError {
|
|||||||
ReplyError,
|
ReplyError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AutoEnterTransaction;
|
||||||
|
|
||||||
class MessageChannel : HasResultCodes
|
class MessageChannel : HasResultCodes
|
||||||
{
|
{
|
||||||
friend class ProcessLink;
|
friend class ProcessLink;
|
||||||
@ -156,7 +158,7 @@ class MessageChannel : HasResultCodes
|
|||||||
return !mCxxStackFrames.empty();
|
return !mCxxStackFrames.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsInTransaction() const { return mCurrentTransaction != 0; }
|
bool IsInTransaction() const;
|
||||||
void CancelCurrentTransaction();
|
void CancelCurrentTransaction();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -263,7 +265,7 @@ class MessageChannel : HasResultCodes
|
|||||||
bool InterruptEventOccurred();
|
bool InterruptEventOccurred();
|
||||||
bool HasPendingEvents();
|
bool HasPendingEvents();
|
||||||
|
|
||||||
void ProcessPendingRequests(int seqno, int transaction);
|
void ProcessPendingRequests(AutoEnterTransaction& aTransaction);
|
||||||
bool ProcessPendingRequest(const Message &aUrgent);
|
bool ProcessPendingRequest(const Message &aUrgent);
|
||||||
|
|
||||||
void MaybeUndeferIncall();
|
void MaybeUndeferIncall();
|
||||||
@ -366,15 +368,6 @@ class MessageChannel : HasResultCodes
|
|||||||
return mInterruptStack.size();
|
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 {
|
bool AwaitingInterruptReply() const {
|
||||||
mMonitor->AssertCurrentThreadOwns();
|
mMonitor->AssertCurrentThreadOwns();
|
||||||
return !mInterruptStack.empty();
|
return !mInterruptStack.empty();
|
||||||
@ -404,17 +397,7 @@ class MessageChannel : HasResultCodes
|
|||||||
};
|
};
|
||||||
friend class AutoEnterWaitForIncoming;
|
friend class AutoEnterWaitForIncoming;
|
||||||
|
|
||||||
// Returns true if we're dispatching a sync message's callback.
|
// Returns true if we're dispatching an async message's callback.
|
||||||
bool DispatchingSyncMessage() const {
|
|
||||||
AssertWorkerThread();
|
|
||||||
return mDispatchingSyncMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DispatchingSyncMessagePriority() const {
|
|
||||||
AssertWorkerThread();
|
|
||||||
return mDispatchingSyncMessagePriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DispatchingAsyncMessage() const {
|
bool DispatchingAsyncMessage() const {
|
||||||
AssertWorkerThread();
|
AssertWorkerThread();
|
||||||
return mDispatchingAsyncMessage;
|
return mDispatchingAsyncMessage;
|
||||||
@ -560,15 +543,6 @@ class MessageChannel : HasResultCodes
|
|||||||
T mNew;
|
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;
|
bool mDispatchingAsyncMessage;
|
||||||
int mDispatchingAsyncMessagePriority;
|
int mDispatchingAsyncMessagePriority;
|
||||||
|
|
||||||
@ -590,56 +564,16 @@ class MessageChannel : HasResultCodes
|
|||||||
// To ensure IDs are unique, we use sequence numbers for transaction IDs,
|
// To ensure IDs are unique, we use sequence numbers for transaction IDs,
|
||||||
// which grow in opposite directions from child to parent.
|
// which grow in opposite directions from child to parent.
|
||||||
|
|
||||||
// The current transaction ID.
|
friend class AutoEnterTransaction;
|
||||||
int32_t mCurrentTransaction;
|
AutoEnterTransaction *mTransactionStack;
|
||||||
|
|
||||||
// This field describes the priorities of the sync Send calls that are
|
int32_t CurrentHighPriorityTransaction() const;
|
||||||
// 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;
|
|
||||||
|
|
||||||
class AutoEnterTransaction
|
bool AwaitingSyncReply() const;
|
||||||
{
|
int AwaitingSyncReplyPriority() const;
|
||||||
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();
|
|
||||||
|
|
||||||
if (!aMessage.is_sync())
|
bool DispatchingSyncMessage() const;
|
||||||
return;
|
int DispatchingSyncMessagePriority() const;
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
// If a sync message times out, we store its sequence number here. Any
|
// If a sync message times out, we store its sequence number here. Any
|
||||||
// future sync messages will fail immediately. Once the reply for original
|
// future sync messages will fail immediately. Once the reply for original
|
||||||
@ -657,14 +591,6 @@ class MessageChannel : HasResultCodes
|
|||||||
int32_t mTimedOutMessageSeqno;
|
int32_t mTimedOutMessageSeqno;
|
||||||
int mTimedOutMessagePriority;
|
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
|
// Queue of all incoming messages, except for replies to sync and urgent
|
||||||
// messages, which are delivered directly to mRecvd, and any pending urgent
|
// messages, which are delivered directly to mRecvd, and any pending urgent
|
||||||
// incall, which is stored in mPendingUrgentRequest.
|
// incall, which is stored in mPendingUrgentRequest.
|
||||||
|
@ -207,17 +207,31 @@ TestDemonParent::RunLimitedSequence(int flags)
|
|||||||
gStackHeight--;
|
gStackHeight--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
AllowAsync(int outgoing, int incoming)
|
||||||
|
{
|
||||||
|
return incoming >= outgoing - 5;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TestDemonParent::DoAction(int flags)
|
TestDemonParent::DoAction(int flags)
|
||||||
{
|
{
|
||||||
if (flags & ASYNC_ONLY) {
|
if (flags & ASYNC_ONLY) {
|
||||||
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
if (AllowAsync(mOutgoing[0], mIncoming[0])) {
|
||||||
return SendAsyncMessage(mOutgoing[0]++);
|
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
||||||
|
return SendAsyncMessage(mOutgoing[0]++);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (Choose(3)) {
|
switch (Choose(3)) {
|
||||||
case 0:
|
case 0:
|
||||||
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
if (AllowAsync(mOutgoing[0], mIncoming[0])) {
|
||||||
return SendAsyncMessage(mOutgoing[0]++);
|
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
||||||
|
return SendAsyncMessage(mOutgoing[0]++);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
case 1: {
|
case 1: {
|
||||||
DEMON_LOG("Start SendHiPrioSyncMessage");
|
DEMON_LOG("Start SendHiPrioSyncMessage");
|
||||||
@ -339,8 +353,12 @@ TestDemonChild::DoAction()
|
|||||||
{
|
{
|
||||||
switch (Choose(6)) {
|
switch (Choose(6)) {
|
||||||
case 0:
|
case 0:
|
||||||
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
if (AllowAsync(mOutgoing[0], mIncoming[0])) {
|
||||||
return SendAsyncMessage(mOutgoing[0]++);
|
DEMON_LOG("SendAsyncMessage [%d]", mOutgoing[0]);
|
||||||
|
return SendAsyncMessage(mOutgoing[0]++);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
case 1: {
|
case 1: {
|
||||||
DEMON_LOG("Start SendHiPrioSyncMessage");
|
DEMON_LOG("Start SendHiPrioSyncMessage");
|
||||||
|
@ -2650,7 +2650,7 @@ class MOZ_STACK_CLASS FunctionValidator
|
|||||||
if (!m_.mg().startFuncDef(line, &fg_))
|
if (!m_.mg().startFuncDef(line, &fg_))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
encoder_.emplace(fg_.bytecode());
|
encoder_.emplace(fg_.bytes());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3532,14 +3532,9 @@ CheckVariables(FunctionValidator& f, ParseNode** stmtIter)
|
|||||||
|
|
||||||
MOZ_ASSERT(f.encoder().empty());
|
MOZ_ASSERT(f.encoder().empty());
|
||||||
|
|
||||||
if (!f.encoder().writeVarU32(types.length()))
|
if (!EncodeLocalEntries(f.encoder(), types))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (ValType v : types) {
|
|
||||||
if (!f.encoder().writeValType(v))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < inits.length(); i++) {
|
for (uint32_t i = 0; i < inits.length(); i++) {
|
||||||
NumLit lit = inits[i];
|
NumLit lit = inits[i];
|
||||||
if (lit.isZeroBits())
|
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;
|
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");
|
return f.fail(initialStmt, "all switch statements generate tables; this table would be too big");
|
||||||
|
|
||||||
*tableLength = uint32_t(i64);
|
*tableLength = uint32_t(i64);
|
||||||
|
@ -144,15 +144,9 @@ CheckType(FunctionDecoder& f, ExprType actual, ExprType expected)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
DecodeExpr(FunctionDecoder& f, ExprType* type);
|
CheckValType(JSContext* cx, Decoder& d, ValType type)
|
||||||
|
|
||||||
static bool
|
|
||||||
DecodeValType(JSContext* cx, Decoder& d, ValType *type)
|
|
||||||
{
|
{
|
||||||
if (!d.readValType(type))
|
switch (type) {
|
||||||
return Fail(cx, d, "bad value type");
|
|
||||||
|
|
||||||
switch (*type) {
|
|
||||||
case ValType::I32:
|
case ValType::I32:
|
||||||
case ValType::F32:
|
case ValType::F32:
|
||||||
case ValType::F64:
|
case ValType::F64:
|
||||||
@ -168,35 +162,19 @@ DecodeValType(JSContext* cx, Decoder& d, ValType *type)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Fail(cx, "bad value type");
|
return Fail(cx, d, "bad value type");
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
DecodeExprType(JSContext* cx, Decoder& d, ExprType *type)
|
CheckExprType(JSContext* cx, Decoder& d, ExprType type)
|
||||||
{
|
{
|
||||||
if (!d.readExprType(type))
|
return type == ExprType::Void ||
|
||||||
return Fail(cx, d, "bad expression type");
|
CheckValType(cx, d, NonVoidToValType(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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
DecodeExpr(FunctionDecoder& f, ExprType* type);
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
DecodeNop(FunctionDecoder& f, ExprType* type)
|
DecodeNop(FunctionDecoder& f, ExprType* type)
|
||||||
{
|
{
|
||||||
@ -269,7 +247,8 @@ DecodeCallIndirect(FunctionDecoder& f, ExprType* type)
|
|||||||
static bool
|
static bool
|
||||||
DecodeConstI32(FunctionDecoder& f, ExprType* type)
|
DecodeConstI32(FunctionDecoder& f, ExprType* type)
|
||||||
{
|
{
|
||||||
if (!f.d().readVarU32())
|
uint32_t _;
|
||||||
|
if (!f.d().readVarU32(&_))
|
||||||
return f.fail("unable to read i32.const immediate");
|
return f.fail("unable to read i32.const immediate");
|
||||||
|
|
||||||
*type = ExprType::I32;
|
*type = ExprType::I32;
|
||||||
@ -279,7 +258,8 @@ DecodeConstI32(FunctionDecoder& f, ExprType* type)
|
|||||||
static bool
|
static bool
|
||||||
DecodeConstI64(FunctionDecoder& f, ExprType* type)
|
DecodeConstI64(FunctionDecoder& f, ExprType* type)
|
||||||
{
|
{
|
||||||
if (!f.d().readVarU64())
|
uint64_t _;
|
||||||
|
if (!f.d().readVarU64(&_))
|
||||||
return f.fail("unable to read i64.const immediate");
|
return f.fail("unable to read i64.const immediate");
|
||||||
|
|
||||||
*type = ExprType::I64;
|
*type = ExprType::I64;
|
||||||
@ -855,24 +835,6 @@ DecodeExpr(FunctionDecoder& f, ExprType* type)
|
|||||||
return f.fail("bad expression code");
|
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
|
// wasm decoding and generation
|
||||||
|
|
||||||
@ -912,7 +874,10 @@ DecodeSignatures(JSContext* cx, Decoder& d, ModuleGeneratorData* init)
|
|||||||
return Fail(cx, d, "too many arguments in signature");
|
return Fail(cx, d, "too many arguments in signature");
|
||||||
|
|
||||||
ExprType result;
|
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;
|
return false;
|
||||||
|
|
||||||
ValTypeVector args;
|
ValTypeVector args;
|
||||||
@ -920,7 +885,10 @@ DecodeSignatures(JSContext* cx, Decoder& d, ModuleGeneratorData* init)
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < numArgs; i++) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1058,6 +1026,21 @@ CheckTypeForJS(JSContext* cx, Decoder& d, const Sig& sig)
|
|||||||
return true;
|
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
|
static bool
|
||||||
DecodeImport(JSContext* cx, Decoder& d, ModuleGeneratorData* init, ImportNameVector* importNames)
|
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))
|
if (!CheckTypeForJS(cx, d, *sig))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
UniqueChars moduleName = d.readCString();
|
Bytes moduleName;
|
||||||
if (!moduleName)
|
if (!d.readBytes(&moduleName))
|
||||||
return Fail(cx, d, "expected import module name");
|
return Fail(cx, d, "expected import module name");
|
||||||
|
|
||||||
if (!*moduleName.get())
|
if (moduleName.empty())
|
||||||
return Fail(cx, d, "module name cannot be empty");
|
return Fail(cx, d, "module name cannot be empty");
|
||||||
|
|
||||||
UniqueChars funcName = d.readCString();
|
Bytes funcName;
|
||||||
if (!funcName)
|
if (!d.readBytes(&funcName))
|
||||||
return Fail(cx, d, "expected import func name");
|
return Fail(cx, d, "expected import func name");
|
||||||
|
|
||||||
return importNames->emplaceBack(Move(moduleName), Move(funcName));
|
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;
|
typedef HashSet<const char*, CStringHasher> CStringSet;
|
||||||
|
|
||||||
static UniqueChars
|
static UniqueChars
|
||||||
DecodeFieldName(JSContext* cx, Decoder& d, CStringSet* dupSet)
|
DecodeExportName(JSContext* cx, Decoder& d, CStringSet* dupSet)
|
||||||
{
|
{
|
||||||
UniqueChars fieldName = d.readCString();
|
Bytes fieldBytes;
|
||||||
if (!fieldName) {
|
if (!d.readBytes(&fieldBytes)) {
|
||||||
Fail(cx, d, "expected export external name string");
|
Fail(cx, d, "expected export name");
|
||||||
return nullptr;
|
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());
|
CStringSet::AddPtr p = dupSet->lookupForAdd(fieldName.get());
|
||||||
if (p) {
|
if (p) {
|
||||||
Fail(cx, d, "duplicate export");
|
Fail(cx, d, "duplicate export");
|
||||||
@ -1197,7 +1192,7 @@ DecodeFunctionExport(JSContext* cx, Decoder& d, ModuleGenerator& mg, CStringSet*
|
|||||||
if (!CheckTypeForJS(cx, d, mg.funcSig(funcIndex)))
|
if (!CheckTypeForJS(cx, d, mg.funcSig(funcIndex)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
UniqueChars fieldName = DecodeFieldName(cx, d, dupSet);
|
UniqueChars fieldName = DecodeExportName(cx, d, dupSet);
|
||||||
if (!fieldName)
|
if (!fieldName)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -1258,15 +1253,11 @@ DecodeFunctionBody(JSContext* cx, Decoder& d, ModuleGenerator& mg, uint32_t func
|
|||||||
if (!locals.appendAll(mg.funcSig(funcIndex).args()))
|
if (!locals.appendAll(mg.funcSig(funcIndex).args()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
uint32_t numVars;
|
if (!DecodeLocalEntries(d, &locals))
|
||||||
if (!d.readVarU32(&numVars))
|
return Fail(cx, d, "failed decoding local entries");
|
||||||
return Fail(cx, d, "expected number of local vars");
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < numVars; i++) {
|
for (ValType type : locals) {
|
||||||
ValType type;
|
if (!CheckValType(cx, d, type))
|
||||||
if (!DecodeValType(cx, d, &type))
|
|
||||||
return false;
|
|
||||||
if (!locals.append(type))
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1285,10 +1276,10 @@ DecodeFunctionBody(JSContext* cx, Decoder& d, ModuleGenerator& mg, uint32_t func
|
|||||||
if (d.currentPosition() != bodyEnd)
|
if (d.currentPosition() != bodyEnd)
|
||||||
return Fail(cx, d, "function body length mismatch");
|
return Fail(cx, d, "function body length mismatch");
|
||||||
|
|
||||||
if (!fg.bytecode().resize(bodySize))
|
if (!fg.bytes().resize(bodySize))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
memcpy(fg.bytecode().begin(), bodyBegin, bodySize);
|
memcpy(fg.bytes().begin(), bodyBegin, bodySize);
|
||||||
|
|
||||||
int64_t after = PRMJ_Now();
|
int64_t after = PRMJ_Now();
|
||||||
unsigned generateTime = (after - before) / PRMJ_USEC_PER_MSEC;
|
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");
|
return Fail(cx, d, "data segment does not fit in memory");
|
||||||
|
|
||||||
const uint8_t* src;
|
const uint8_t* src;
|
||||||
if (!d.readRawData(numBytes, &src))
|
if (!d.readBytesRaw(numBytes, &src))
|
||||||
return Fail(cx, d, "data segment shorter than declared");
|
return Fail(cx, d, "data segment shorter than declared");
|
||||||
|
|
||||||
memcpy(heapBase + dstOffset, src, numBytes);
|
memcpy(heapBase + dstOffset, src, numBytes);
|
||||||
@ -1472,9 +1463,9 @@ CheckCompilerSupport(JSContext* cx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
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)
|
if (!atom)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -1491,15 +1482,15 @@ ImportFunctions(JSContext* cx, HandleObject importObj, const ImportNameVector& i
|
|||||||
|
|
||||||
for (const ImportName& name : importNames) {
|
for (const ImportName& name : importNames) {
|
||||||
RootedValue v(cx);
|
RootedValue v(cx);
|
||||||
if (!GetProperty(cx, importObj, name.module.get(), &v))
|
if (!GetProperty(cx, importObj, name.module, &v))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (*name.func.get()) {
|
if (!name.func.empty()) {
|
||||||
if (!v.isObject())
|
if (!v.isObject())
|
||||||
return Fail(cx, "import object field is not an Object");
|
return Fail(cx, "import object field is not an Object");
|
||||||
|
|
||||||
RootedObject obj(cx, &v.toObject());
|
RootedObject obj(cx, &v.toObject());
|
||||||
if (!GetProperty(cx, obj, name.func.get(), &v))
|
if (!GetProperty(cx, obj, name.func, &v))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1544,6 +1535,7 @@ wasm::Eval(JSContext* cx, Handle<TypedArrayObject*> code, HandleObject importObj
|
|||||||
UniqueExportMap exportMap;
|
UniqueExportMap exportMap;
|
||||||
Rooted<ArrayBufferObject*> heap(cx);
|
Rooted<ArrayBufferObject*> heap(cx);
|
||||||
Rooted<WasmModuleObject*> moduleObj(cx);
|
Rooted<WasmModuleObject*> moduleObj(cx);
|
||||||
|
|
||||||
if (!DecodeModule(cx, Move(file), bytes, length, &importNames, &exportMap, &heap, &moduleObj)) {
|
if (!DecodeModule(cx, Move(file), bytes, length, &importNames, &exportMap, &heap, &moduleObj)) {
|
||||||
if (!cx->isExceptionPending())
|
if (!cx->isExceptionPending())
|
||||||
ReportOutOfMemory(cx);
|
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
|
#ifndef wasm_binary_h
|
||||||
#define wasm_binary_h
|
#define wasm_binary_h
|
||||||
|
|
||||||
#include "mozilla/DebugOnly.h"
|
|
||||||
|
|
||||||
#include "builtin/SIMD.h"
|
#include "builtin/SIMD.h"
|
||||||
|
|
||||||
namespace js {
|
namespace js {
|
||||||
namespace wasm {
|
namespace wasm {
|
||||||
|
|
||||||
using mozilla::DebugOnly;
|
|
||||||
|
|
||||||
static const uint32_t MagicNumber = 0x6d736100; // "\0asm"
|
static const uint32_t MagicNumber = 0x6d736100; // "\0asm"
|
||||||
static const uint32_t EncodingVersion = 0xa;
|
static const uint32_t EncodingVersion = 0xa;
|
||||||
|
|
||||||
@ -328,20 +324,19 @@ enum class ExprType
|
|||||||
|
|
||||||
typedef int32_t I32x4[4];
|
typedef int32_t I32x4[4];
|
||||||
typedef float F32x4[4];
|
typedef float F32x4[4];
|
||||||
typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytecode;
|
typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
|
||||||
typedef UniquePtr<Bytecode> UniqueBytecode;
|
|
||||||
|
|
||||||
// The Encoder class appends bytes to the Bytecode object it is given during
|
// The Encoder class appends bytes to the Bytes object it is given during
|
||||||
// construction. The client is responsible for the Bytecode's lifetime and must
|
// construction. The client is responsible for the Bytes's lifetime and must
|
||||||
// keep the Bytecode alive as long as the Encoder is used.
|
// keep the Bytes alive as long as the Encoder is used.
|
||||||
|
|
||||||
class Encoder
|
class Encoder
|
||||||
{
|
{
|
||||||
Bytecode& bytecode_;
|
Bytes& bytes_;
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
MOZ_WARN_UNUSED_RESULT bool write(const T& v) {
|
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>
|
template <typename UInt>
|
||||||
@ -351,7 +346,7 @@ class Encoder
|
|||||||
i >>= 7;
|
i >>= 7;
|
||||||
if (i != 0)
|
if (i != 0)
|
||||||
byte |= 0x80;
|
byte |= 0x80;
|
||||||
if (!bytecode_.append(byte))
|
if (!bytes_.append(byte))
|
||||||
return false;
|
return false;
|
||||||
} while(i != 0);
|
} while(i != 0);
|
||||||
return true;
|
return true;
|
||||||
@ -374,42 +369,39 @@ class Encoder
|
|||||||
assertByte |= 0x80;
|
assertByte |= 0x80;
|
||||||
patchByte |= 0x80;
|
patchByte |= 0x80;
|
||||||
}
|
}
|
||||||
MOZ_ASSERT(assertByte == bytecode_[offset]);
|
MOZ_ASSERT(assertByte == bytes_[offset]);
|
||||||
bytecode_[offset] = patchByte;
|
bytes_[offset] = patchByte;
|
||||||
offset++;
|
offset++;
|
||||||
} while(assertBits != 0);
|
} while(assertBits != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t varU32ByteLength(size_t offset) const {
|
uint32_t varU32ByteLength(size_t offset) const {
|
||||||
size_t start = offset;
|
size_t start = offset;
|
||||||
while (bytecode_[offset] & 0x80)
|
while (bytes_[offset] & 0x80)
|
||||||
offset++;
|
offset++;
|
||||||
return offset - start + 1;
|
return offset - start + 1;
|
||||||
}
|
}
|
||||||
static const uint32_t EnumSentinel = 0x3fff;
|
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
MOZ_WARN_UNUSED_RESULT bool writePatchableEnum(size_t* offset) {
|
MOZ_WARN_UNUSED_RESULT bool writePatchableEnum(size_t* offset) {
|
||||||
static_assert(uint32_t(T::Limit) <= EnumSentinel, "reserve enough bits");
|
*offset = bytes_.length();
|
||||||
*offset = bytecode_.length();
|
return writeVarU32(uint32_t(T::Limit));
|
||||||
return writeVarU32(EnumSentinel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void patchEnum(size_t offset, T v) {
|
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));
|
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:
|
public:
|
||||||
explicit Encoder(Bytecode& bytecode)
|
explicit Encoder(Bytes& bytes)
|
||||||
: bytecode_(bytecode)
|
: bytes_(bytes)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(empty());
|
MOZ_ASSERT(empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t currentOffset() const { return bytecode_.length(); }
|
size_t currentOffset() const { return bytes_.length(); }
|
||||||
bool empty() const { return currentOffset() == 0; }
|
bool empty() const { return currentOffset() == 0; }
|
||||||
|
|
||||||
// Fixed-size encoding operations simply copy the literal bytes (without
|
// Fixed-size encoding operations simply copy the literal bytes (without
|
||||||
@ -452,7 +444,7 @@ class Encoder
|
|||||||
// Variable-length encodings that allow back-patching.
|
// Variable-length encodings that allow back-patching.
|
||||||
|
|
||||||
MOZ_WARN_UNUSED_RESULT bool writePatchableVarU32(size_t* offset) {
|
MOZ_WARN_UNUSED_RESULT bool writePatchableVarU32(size_t* offset) {
|
||||||
*offset = bytecode_.length();
|
*offset = bytes_.length();
|
||||||
return writeVarU32(UINT32_MAX);
|
return writeVarU32(UINT32_MAX);
|
||||||
}
|
}
|
||||||
void patchVarU32(size_t offset, uint32_t patchBits) {
|
void patchVarU32(size_t offset, uint32_t patchBits) {
|
||||||
@ -460,7 +452,7 @@ class Encoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOZ_WARN_UNUSED_RESULT bool writePatchableVarU8(size_t* offset) {
|
MOZ_WARN_UNUSED_RESULT bool writePatchableVarU8(size_t* offset) {
|
||||||
*offset = bytecode_.length();
|
*offset = bytes_.length();
|
||||||
return writeU8(UINT8_MAX);
|
return writeU8(UINT8_MAX);
|
||||||
}
|
}
|
||||||
void patchVarU8(size_t offset, uint8_t patchBits) {
|
void patchVarU8(size_t offset, uint8_t patchBits) {
|
||||||
@ -475,20 +467,18 @@ class Encoder
|
|||||||
patchEnum(offset, expr);
|
patchEnum(offset, expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// C-strings are written in UTF8 and null-terminated while raw data can
|
// Byte ranges start with an LEB128 length followed by an arbitrary sequence
|
||||||
// contain nulls and instead has an explicit byte length.
|
// of bytes. When used for strings, bytes are to be interpreted as utf8.
|
||||||
|
|
||||||
MOZ_WARN_UNUSED_RESULT bool writeCString(const char* cstr) {
|
MOZ_WARN_UNUSED_RESULT bool writeBytes(const void* bytes, uint32_t numBytes) {
|
||||||
return bytecode_.append(reinterpret_cast<const uint8_t*>(cstr), strlen(cstr) + 1);
|
return writeVarU32(numBytes) &&
|
||||||
}
|
bytes_.append(reinterpret_cast<const uint8_t*>(bytes), numBytes);
|
||||||
MOZ_WARN_UNUSED_RESULT bool writeRawData(const uint8_t* bytes, uint32_t numBytes) {
|
|
||||||
return bytecode_.append(bytes, numBytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A "section" is a contiguous range of bytes that stores its own size so
|
// 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
|
// that it may be trivially skipped without examining the contents. Sections
|
||||||
// require backpatching since the size of the section is only known at the
|
// 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.
|
// after the section length is the string id of the section.
|
||||||
|
|
||||||
template <size_t IdSizeWith0>
|
template <size_t IdSizeWith0>
|
||||||
@ -497,10 +487,10 @@ class Encoder
|
|||||||
MOZ_ASSERT(id[IdSize] == '\0');
|
MOZ_ASSERT(id[IdSize] == '\0');
|
||||||
return writePatchableVarU32(offset) &&
|
return writePatchableVarU32(offset) &&
|
||||||
writeVarU32(IdSize) &&
|
writeVarU32(IdSize) &&
|
||||||
writeRawData(reinterpret_cast<const uint8_t*>(id), IdSize);
|
bytes_.append(reinterpret_cast<const uint8_t*>(id), IdSize);
|
||||||
}
|
}
|
||||||
void finishSection(size_t offset) {
|
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
|
// Temporary encoding forms which should be removed as part of the
|
||||||
@ -510,12 +500,12 @@ class Encoder
|
|||||||
return write<uint8_t>(i);
|
return write<uint8_t>(i);
|
||||||
}
|
}
|
||||||
MOZ_WARN_UNUSED_RESULT bool writePatchableU8(size_t* offset) {
|
MOZ_WARN_UNUSED_RESULT bool writePatchableU8(size_t* offset) {
|
||||||
*offset = bytecode_.length();
|
*offset = bytes_.length();
|
||||||
return bytecode_.append(0xff);
|
return bytes_.append(0xff);
|
||||||
}
|
}
|
||||||
void patchU8(size_t offset, uint8_t i) {
|
void patchU8(size_t offset, uint8_t i) {
|
||||||
MOZ_ASSERT(bytecode_[offset] == 0xff);
|
MOZ_ASSERT(bytes_[offset] == 0xff);
|
||||||
bytecode_[offset] = i;
|
bytes_[offset] = i;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -533,8 +523,7 @@ class Decoder
|
|||||||
MOZ_WARN_UNUSED_RESULT bool read(T* out) {
|
MOZ_WARN_UNUSED_RESULT bool read(T* out) {
|
||||||
if (bytesRemain() < sizeof(T))
|
if (bytesRemain() < sizeof(T))
|
||||||
return false;
|
return false;
|
||||||
if (out)
|
memcpy((void*)out, cur_, sizeof(T));
|
||||||
memcpy((void*)out, cur_, sizeof(T));
|
|
||||||
cur_ += sizeof(T);
|
cur_ += sizeof(T);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -545,8 +534,7 @@ class Decoder
|
|||||||
uint32_t u32;
|
uint32_t u32;
|
||||||
if (!readVarU32(&u32) || u32 >= uint32_t(T::Limit))
|
if (!readVarU32(&u32) || u32 >= uint32_t(T::Limit))
|
||||||
return false;
|
return false;
|
||||||
if (out)
|
*out = T(u32);
|
||||||
*out = T(u32);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,7 +554,7 @@ class Decoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename UInt>
|
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 numBits = sizeof(UInt) * CHAR_BIT;
|
||||||
const unsigned remainderBits = numBits % 7;
|
const unsigned remainderBits = numBits % 7;
|
||||||
const unsigned numBitsInSevens = numBits - remainderBits;
|
const unsigned numBitsInSevens = numBits - remainderBits;
|
||||||
@ -577,8 +565,7 @@ class Decoder
|
|||||||
if (!readFixedU8(&byte))
|
if (!readFixedU8(&byte))
|
||||||
return false;
|
return false;
|
||||||
if (!(byte & 0x80)) {
|
if (!(byte & 0x80)) {
|
||||||
if (out)
|
*out = u | UInt(byte) << shift;
|
||||||
*out = u | UInt(byte) << shift;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
u |= UInt(byte & 0x7F) << shift;
|
u |= UInt(byte & 0x7F) << shift;
|
||||||
@ -586,8 +573,7 @@ class Decoder
|
|||||||
} while (shift != numBitsInSevens);
|
} while (shift != numBitsInSevens);
|
||||||
if (!readFixedU8(&byte) || (byte & (unsigned(-1) << remainderBits)))
|
if (!readFixedU8(&byte) || (byte & (unsigned(-1) << remainderBits)))
|
||||||
return false;
|
return false;
|
||||||
if (out)
|
*out = u | UInt(byte) << numBitsInSevens;
|
||||||
*out = u | UInt(byte) << numBitsInSevens;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,10 +585,10 @@ class Decoder
|
|||||||
{
|
{
|
||||||
MOZ_ASSERT(begin <= end);
|
MOZ_ASSERT(begin <= end);
|
||||||
}
|
}
|
||||||
explicit Decoder(const Bytecode& bytecode)
|
explicit Decoder(const Bytes& bytes)
|
||||||
: beg_(bytecode.begin()),
|
: beg_(bytes.begin()),
|
||||||
end_(bytecode.end()),
|
end_(bytes.end()),
|
||||||
cur_(bytecode.begin())
|
cur_(bytes.begin())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool done() const {
|
bool done() const {
|
||||||
@ -620,38 +606,35 @@ class Decoder
|
|||||||
size_t currentOffset() const {
|
size_t currentOffset() const {
|
||||||
return cur_ - beg_;
|
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
|
// Fixed-size encoding operations simply copy the literal bytes (without
|
||||||
// attempting to align).
|
// 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);
|
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);
|
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);
|
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);
|
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);
|
return read<F32x4>(f32x4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variable-length encodings that all use LEB128.
|
// 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);
|
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);
|
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);
|
return readEnum(expr);
|
||||||
}
|
}
|
||||||
MOZ_WARN_UNUSED_RESULT bool readValType(ValType* type) {
|
MOZ_WARN_UNUSED_RESULT bool readValType(ValType* type) {
|
||||||
@ -661,20 +644,21 @@ class Decoder
|
|||||||
return readEnum(type);
|
return readEnum(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// C-strings are written in UTF8 and null-terminated while raw data can
|
// See writeBytes comment.
|
||||||
// contain nulls and instead has an explicit byte length.
|
|
||||||
|
|
||||||
MOZ_WARN_UNUSED_RESULT UniqueChars readCString() {
|
MOZ_WARN_UNUSED_RESULT bool readBytes(Bytes* bytes) {
|
||||||
const char* begin = reinterpret_cast<const char*>(cur_);
|
uint32_t numBytes;
|
||||||
for (; cur_ != end_; cur_++) {
|
if (!readVarU32(&numBytes))
|
||||||
if (!*cur_) {
|
return false;
|
||||||
cur_++;
|
if (bytesRemain() < numBytes)
|
||||||
return UniqueChars(DuplicateString(begin));
|
return false;
|
||||||
}
|
if (!bytes->resize(numBytes))
|
||||||
}
|
return false;
|
||||||
return nullptr;
|
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)
|
if (bytes)
|
||||||
*bytes = cur_;
|
*bytes = cur_;
|
||||||
if (bytesRemain() < numBytes)
|
if (bytesRemain() < numBytes)
|
||||||
@ -706,7 +690,7 @@ class Decoder
|
|||||||
goto backup;
|
goto backup;
|
||||||
cur_ += IdSize;
|
cur_ += IdSize;
|
||||||
*startOffset = before - beg_;
|
*startOffset = before - beg_;
|
||||||
return true;
|
return true;
|
||||||
backup:
|
backup:
|
||||||
cur_ = before;
|
cur_ = before;
|
||||||
*startOffset = NotStarted;
|
*startOffset = NotStarted;
|
||||||
@ -735,7 +719,7 @@ class Decoder
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The infallible "unchecked" decoding functions can be used when we are
|
// 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).
|
// validation).
|
||||||
|
|
||||||
uint32_t uncheckedReadFixedU32() {
|
uint32_t uncheckedReadFixedU32() {
|
||||||
@ -787,17 +771,25 @@ class Decoder
|
|||||||
// Temporary encoding forms which should be removed as part of the
|
// Temporary encoding forms which should be removed as part of the
|
||||||
// conversion to wasm:
|
// 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);
|
return read<uint8_t>(i);
|
||||||
}
|
}
|
||||||
MOZ_WARN_UNUSED_RESULT bool readFixedI32(int32_t* i = nullptr) {
|
|
||||||
return read<int32_t>(i);
|
|
||||||
}
|
|
||||||
uint8_t uncheckedReadFixedU8() {
|
uint8_t uncheckedReadFixedU8() {
|
||||||
return uncheckedRead<uint8_t>();
|
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 wasm
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
|
||||||
|
@ -301,7 +301,7 @@ ModuleGenerator::convertOutOfRangeBranchesToThunks()
|
|||||||
bool
|
bool
|
||||||
ModuleGenerator::finishTask(IonCompileTask* task)
|
ModuleGenerator::finishTask(IonCompileTask* task)
|
||||||
{
|
{
|
||||||
const FuncBytecode& func = task->func();
|
const FuncBytes& func = task->func();
|
||||||
FuncCompileResults& results = task->results();
|
FuncCompileResults& results = task->results();
|
||||||
|
|
||||||
// Before merging in the new function's code, if jumps/calls in a previous
|
// Before merging in the new function's code, if jumps/calls in a previous
|
||||||
@ -725,7 +725,7 @@ bool
|
|||||||
ModuleGenerator::addMemoryExport(UniqueChars fieldName)
|
ModuleGenerator::addMemoryExport(UniqueChars fieldName)
|
||||||
{
|
{
|
||||||
return exportMap_->fieldNames.append(Move(fieldName)) &&
|
return exportMap_->fieldNames.append(Move(fieldName)) &&
|
||||||
exportMap_->fieldsToExports.append(ExportMap::MemoryExport);
|
exportMap_->fieldsToExports.append(MemoryExport);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -782,15 +782,8 @@ ModuleGenerator::startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg)
|
|||||||
|
|
||||||
IonCompileTask* task = freeTasks_.popCopy();
|
IonCompileTask* task = freeTasks_.popCopy();
|
||||||
|
|
||||||
task->reset(&fg->bytecode_);
|
task->reset(&fg->bytes_);
|
||||||
if (fg->bytecode_) {
|
fg->bytes_.clear();
|
||||||
fg->bytecode_->clear();
|
|
||||||
} else {
|
|
||||||
fg->bytecode_ = MakeUnique<Bytecode>();
|
|
||||||
if (!fg->bytecode_)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fg->lineOrBytecode_ = lineOrBytecode;
|
fg->lineOrBytecode_ = lineOrBytecode;
|
||||||
fg->m_ = this;
|
fg->m_ = this;
|
||||||
fg->task_ = task;
|
fg->task_ = task;
|
||||||
@ -803,13 +796,12 @@ ModuleGenerator::finishFuncDef(uint32_t funcIndex, unsigned generateTime, Functi
|
|||||||
{
|
{
|
||||||
MOZ_ASSERT(activeFunc_ == fg);
|
MOZ_ASSERT(activeFunc_ == fg);
|
||||||
|
|
||||||
UniqueFuncBytecode func =
|
auto func = js::MakeUnique<FuncBytes>(Move(fg->bytes_),
|
||||||
js::MakeUnique<FuncBytecode>(funcIndex,
|
funcIndex,
|
||||||
funcSig(funcIndex),
|
funcSig(funcIndex),
|
||||||
Move(fg->bytecode_),
|
fg->lineOrBytecode_,
|
||||||
fg->lineOrBytecode_,
|
Move(fg->callSiteLineNums_),
|
||||||
Move(fg->callSiteLineNums_),
|
generateTime);
|
||||||
generateTime);
|
|
||||||
if (!func)
|
if (!func)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ typedef Vector<SlowFunction> SlowFunctionVector;
|
|||||||
// is encapsulated by ModuleGenerator/ModuleGeneratorThreadView classes which
|
// is encapsulated by ModuleGenerator/ModuleGeneratorThreadView classes which
|
||||||
// present a race-free interface to the code in each thread assuming any given
|
// 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 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.
|
// Once created, the Vectors are never resized.
|
||||||
|
|
||||||
struct TableModuleGeneratorData
|
struct TableModuleGeneratorData
|
||||||
@ -202,8 +202,8 @@ class MOZ_STACK_CLASS ModuleGenerator
|
|||||||
Vector<IonCompileTask*> freeTasks_;
|
Vector<IonCompileTask*> freeTasks_;
|
||||||
|
|
||||||
// Assertions
|
// Assertions
|
||||||
DebugOnly<FunctionGenerator*> activeFunc_;
|
FunctionGenerator* activeFunc_;
|
||||||
DebugOnly<bool> finishedFuncs_;
|
bool finishedFuncs_;
|
||||||
|
|
||||||
bool finishOutstandingTask();
|
bool finishOutstandingTask();
|
||||||
bool funcIsDefined(uint32_t funcIndex) const;
|
bool funcIsDefined(uint32_t funcIndex) const;
|
||||||
@ -288,13 +288,13 @@ class MOZ_STACK_CLASS FunctionGenerator
|
|||||||
{
|
{
|
||||||
friend class ModuleGenerator;
|
friend class ModuleGenerator;
|
||||||
|
|
||||||
ModuleGenerator* m_;
|
ModuleGenerator* m_;
|
||||||
IonCompileTask* task_;
|
IonCompileTask* task_;
|
||||||
|
|
||||||
// Data created during function generation, then handed over to the
|
// Data created during function generation, then handed over to the
|
||||||
// FuncBytecode in ModuleGenerator::finishFunc().
|
// FuncBytes in ModuleGenerator::finishFunc().
|
||||||
UniqueBytecode bytecode_;
|
Bytes bytes_;
|
||||||
Uint32Vector callSiteLineNums_;
|
Uint32Vector callSiteLineNums_;
|
||||||
|
|
||||||
uint32_t lineOrBytecode_;
|
uint32_t lineOrBytecode_;
|
||||||
|
|
||||||
@ -303,8 +303,8 @@ class MOZ_STACK_CLASS FunctionGenerator
|
|||||||
: m_(nullptr), task_(nullptr), lineOrBytecode_(0)
|
: m_(nullptr), task_(nullptr), lineOrBytecode_(0)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Bytecode& bytecode() const {
|
Bytes& bytes() {
|
||||||
return *bytecode_;
|
return bytes_;
|
||||||
}
|
}
|
||||||
bool addCallSiteLineNum(uint32_t lineno) {
|
bool addCallSiteLineNum(uint32_t lineno) {
|
||||||
return callSiteLineNums_.append(lineno);
|
return callSiteLineNums_.append(lineno);
|
||||||
|
@ -40,7 +40,7 @@ class FunctionCompiler
|
|||||||
|
|
||||||
ModuleGeneratorThreadView& mg_;
|
ModuleGeneratorThreadView& mg_;
|
||||||
Decoder& decoder_;
|
Decoder& decoder_;
|
||||||
const FuncBytecode& func_;
|
const FuncBytes& func_;
|
||||||
const ValTypeVector& locals_;
|
const ValTypeVector& locals_;
|
||||||
size_t lastReadCallSite_;
|
size_t lastReadCallSite_;
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ class FunctionCompiler
|
|||||||
public:
|
public:
|
||||||
FunctionCompiler(ModuleGeneratorThreadView& mg,
|
FunctionCompiler(ModuleGeneratorThreadView& mg,
|
||||||
Decoder& decoder,
|
Decoder& decoder,
|
||||||
const FuncBytecode& func,
|
const FuncBytes& func,
|
||||||
const ValTypeVector& locals,
|
const ValTypeVector& locals,
|
||||||
MIRGenerator& mirGen,
|
MIRGenerator& mirGen,
|
||||||
FuncCompileResults& compileResults)
|
FuncCompileResults& compileResults)
|
||||||
@ -153,7 +153,7 @@ class FunctionCompiler
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
MOZ_ASSERT(inDeadCode());
|
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_);
|
MOZ_ASSERT(func_.callSiteLineNums().length() == lastReadCallSite_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3048,22 +3048,18 @@ wasm::IonCompileFunction(IonCompileTask* task)
|
|||||||
{
|
{
|
||||||
int64_t before = PRMJ_Now();
|
int64_t before = PRMJ_Now();
|
||||||
|
|
||||||
const FuncBytecode& func = task->func();
|
const FuncBytes& func = task->func();
|
||||||
FuncCompileResults& results = task->results();
|
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;
|
ValTypeVector locals;
|
||||||
if (!locals.appendAll(func.sig().args()))
|
if (!locals.appendAll(func.sig().args()))
|
||||||
return false;
|
return false;
|
||||||
|
if (!DecodeLocalEntries(d, &locals))
|
||||||
uint32_t numVars = d.uncheckedReadVarU32();
|
return false;
|
||||||
for (uint32_t i = 0; i < numVars; i++) {
|
|
||||||
if (!locals.append(d.uncheckedReadValType()))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up for Ion compilation.
|
// Set up for Ion compilation.
|
||||||
|
|
||||||
|
@ -31,52 +31,48 @@ typedef Vector<jit::MIRType, 8, SystemAllocPolicy> MIRTypeVector;
|
|||||||
typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
|
typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
|
||||||
typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
|
typedef jit::ABIArgIter<ValTypeVector> ABIArgValTypeIter;
|
||||||
|
|
||||||
// The FuncBytecode class contains the intermediate representation of a
|
// The FuncBytes class represents a single, concurrently-compilable function.
|
||||||
// parsed/decoded and validated asm.js/WebAssembly function. The FuncBytecode
|
// A FuncBytes object is composed of the wasm function body bytes along with the
|
||||||
// lives only until it is fully compiled.
|
// ambient metadata describing the function necessary to compile it.
|
||||||
|
|
||||||
class FuncBytecode
|
class FuncBytes
|
||||||
{
|
{
|
||||||
// Function metadata
|
Bytes bytes_;
|
||||||
|
uint32_t index_;
|
||||||
const DeclaredSig& sig_;
|
const DeclaredSig& sig_;
|
||||||
uint32_t lineOrBytecode_;
|
uint32_t lineOrBytecode_;
|
||||||
Uint32Vector callSiteLineNums_;
|
Uint32Vector callSiteLineNums_;
|
||||||
|
unsigned generateTime_;
|
||||||
// Compilation bookkeeping
|
|
||||||
uint32_t index_;
|
|
||||||
unsigned generateTime_;
|
|
||||||
|
|
||||||
UniqueBytecode bytecode_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FuncBytecode(uint32_t index,
|
FuncBytes(Bytes&& bytes,
|
||||||
const DeclaredSig& sig,
|
uint32_t index,
|
||||||
UniqueBytecode bytecode,
|
const DeclaredSig& sig,
|
||||||
uint32_t lineOrBytecode,
|
uint32_t lineOrBytecode,
|
||||||
Uint32Vector&& callSiteLineNums,
|
Uint32Vector&& callSiteLineNums,
|
||||||
unsigned generateTime)
|
unsigned generateTime)
|
||||||
: sig_(sig),
|
: bytes_(Move(bytes)),
|
||||||
|
index_(index),
|
||||||
|
sig_(sig),
|
||||||
lineOrBytecode_(lineOrBytecode),
|
lineOrBytecode_(lineOrBytecode),
|
||||||
callSiteLineNums_(Move(callSiteLineNums)),
|
callSiteLineNums_(Move(callSiteLineNums)),
|
||||||
index_(index),
|
generateTime_(generateTime)
|
||||||
generateTime_(generateTime),
|
|
||||||
bytecode_(Move(bytecode))
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
UniqueBytecode recycleBytecode() { return Move(bytecode_); }
|
Bytes& bytes() { return bytes_; }
|
||||||
|
const Bytes& bytes() const { return bytes_; }
|
||||||
uint32_t lineOrBytecode() const { return lineOrBytecode_; }
|
|
||||||
const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
|
|
||||||
uint32_t index() const { return index_; }
|
uint32_t index() const { return index_; }
|
||||||
const DeclaredSig& sig() const { return sig_; }
|
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_; }
|
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
|
class FuncCompileResults
|
||||||
{
|
{
|
||||||
jit::TempAllocator alloc_;
|
jit::TempAllocator alloc_;
|
||||||
@ -108,13 +104,14 @@ class FuncCompileResults
|
|||||||
// the FuncCompileResults, and finally sent back to the validation thread. To
|
// the FuncCompileResults, and finally sent back to the validation thread. To
|
||||||
// save time allocating and freeing memory, IonCompileTasks are reset() and
|
// save time allocating and freeing memory, IonCompileTasks are reset() and
|
||||||
// reused.
|
// reused.
|
||||||
|
|
||||||
class IonCompileTask
|
class IonCompileTask
|
||||||
{
|
{
|
||||||
JSRuntime* const runtime_;
|
JSRuntime* const runtime_;
|
||||||
ModuleGeneratorThreadView& mg_;
|
ModuleGeneratorThreadView& mg_;
|
||||||
LifoAlloc lifo_;
|
LifoAlloc lifo_;
|
||||||
UniqueFuncBytecode func_;
|
UniqueFuncBytes func_;
|
||||||
mozilla::Maybe<FuncCompileResults> results_;
|
Maybe<FuncCompileResults> results_;
|
||||||
|
|
||||||
IonCompileTask(const IonCompileTask&) = delete;
|
IonCompileTask(const IonCompileTask&) = delete;
|
||||||
IonCompileTask& operator=(const IonCompileTask&) = delete;
|
IonCompileTask& operator=(const IonCompileTask&) = delete;
|
||||||
@ -132,21 +129,21 @@ class IonCompileTask
|
|||||||
ModuleGeneratorThreadView& mg() const {
|
ModuleGeneratorThreadView& mg() const {
|
||||||
return mg_;
|
return mg_;
|
||||||
}
|
}
|
||||||
void init(UniqueFuncBytecode func) {
|
void init(UniqueFuncBytes func) {
|
||||||
MOZ_ASSERT(!func_);
|
MOZ_ASSERT(!func_);
|
||||||
func_ = mozilla::Move(func);
|
func_ = Move(func);
|
||||||
results_.emplace(lifo_);
|
results_.emplace(lifo_);
|
||||||
}
|
}
|
||||||
const FuncBytecode& func() const {
|
const FuncBytes& func() const {
|
||||||
MOZ_ASSERT(func_);
|
MOZ_ASSERT(func_);
|
||||||
return *func_;
|
return *func_;
|
||||||
}
|
}
|
||||||
FuncCompileResults& results() {
|
FuncCompileResults& results() {
|
||||||
return *results_;
|
return *results_;
|
||||||
}
|
}
|
||||||
void reset(UniqueBytecode* recycled) {
|
void reset(Bytes* recycled) {
|
||||||
if (func_)
|
if (func_)
|
||||||
*recycled = func_->recycleBytecode();
|
*recycled = Move(func_->bytes());
|
||||||
func_.reset(nullptr);
|
func_.reset(nullptr);
|
||||||
results_.reset();
|
results_.reset();
|
||||||
lifo_.releaseAll();
|
lifo_.releaseAll();
|
||||||
|
@ -54,8 +54,6 @@ using mozilla::PodZero;
|
|||||||
using mozilla::Swap;
|
using mozilla::Swap;
|
||||||
using JS::GenericNaN;
|
using JS::GenericNaN;
|
||||||
|
|
||||||
const uint32_t ExportMap::MemoryExport;
|
|
||||||
|
|
||||||
UniqueCodePtr
|
UniqueCodePtr
|
||||||
wasm::AllocateCode(ExclusiveContext* cx, size_t bytes)
|
wasm::AllocateCode(ExclusiveContext* cx, size_t bytes)
|
||||||
{
|
{
|
||||||
@ -1130,7 +1128,7 @@ CreateExportObject(JSContext* cx,
|
|||||||
if (!*fieldName) {
|
if (!*fieldName) {
|
||||||
MOZ_ASSERT(!exportObj);
|
MOZ_ASSERT(!exportObj);
|
||||||
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
|
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
|
||||||
if (exportIndex == ExportMap::MemoryExport) {
|
if (exportIndex == MemoryExport) {
|
||||||
MOZ_ASSERT(heap);
|
MOZ_ASSERT(heap);
|
||||||
exportObj.set(heap);
|
exportObj.set(heap);
|
||||||
} else {
|
} else {
|
||||||
@ -1167,7 +1165,7 @@ CreateExportObject(JSContext* cx,
|
|||||||
RootedId id(cx, AtomToId(atom));
|
RootedId id(cx, AtomToId(atom));
|
||||||
RootedValue val(cx);
|
RootedValue val(cx);
|
||||||
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
|
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
|
||||||
if (exportIndex == ExportMap::MemoryExport)
|
if (exportIndex == MemoryExport)
|
||||||
val = ObjectValue(*heap);
|
val = ObjectValue(*heap);
|
||||||
else
|
else
|
||||||
val = vals[exportIndex];
|
val = vals[exportIndex];
|
||||||
|
@ -336,10 +336,10 @@ typedef Vector<CacheableChars, 0, SystemAllocPolicy> CacheableCharsVector;
|
|||||||
// Lastly, the 'exportFuncIndices' vector provides, for each exported function,
|
// Lastly, the 'exportFuncIndices' vector provides, for each exported function,
|
||||||
// the internal index of the function.
|
// the internal index of the function.
|
||||||
|
|
||||||
|
static const uint32_t MemoryExport = UINT32_MAX;
|
||||||
|
|
||||||
struct ExportMap
|
struct ExportMap
|
||||||
{
|
{
|
||||||
static const uint32_t MemoryExport = UINT32_MAX;
|
|
||||||
|
|
||||||
CacheableCharsVector fieldNames;
|
CacheableCharsVector fieldNames;
|
||||||
Uint32Vector fieldsToExports;
|
Uint32Vector fieldsToExports;
|
||||||
Uint32Vector exportFuncIndices;
|
Uint32Vector exportFuncIndices;
|
||||||
|
@ -784,6 +784,7 @@ class WasmToken
|
|||||||
UnsignedInteger,
|
UnsignedInteger,
|
||||||
SignedInteger,
|
SignedInteger,
|
||||||
Memory,
|
Memory,
|
||||||
|
NegativeZero,
|
||||||
Load,
|
Load,
|
||||||
Local,
|
Local,
|
||||||
Loop,
|
Loop,
|
||||||
@ -1271,6 +1272,8 @@ WasmTokenStream::literal(const char16_t* begin)
|
|||||||
|
|
||||||
if (*begin == '-') {
|
if (*begin == '-') {
|
||||||
uint64_t value = u.value();
|
uint64_t value = u.value();
|
||||||
|
if (value == 0)
|
||||||
|
return WasmToken(WasmToken::NegativeZero, begin, cur_);
|
||||||
if (value > uint64_t(INT64_MIN))
|
if (value > uint64_t(INT64_MIN))
|
||||||
return LexHexFloatLiteral(begin, end_, &cur_);
|
return LexHexFloatLiteral(begin, end_, &cur_);
|
||||||
|
|
||||||
@ -1295,6 +1298,8 @@ WasmTokenStream::literal(const char16_t* begin)
|
|||||||
|
|
||||||
if (*begin == '-') {
|
if (*begin == '-') {
|
||||||
uint64_t value = u.value();
|
uint64_t value = u.value();
|
||||||
|
if (value == 0)
|
||||||
|
return WasmToken(WasmToken::NegativeZero, begin, cur_);
|
||||||
if (value > uint64_t(INT64_MIN))
|
if (value > uint64_t(INT64_MIN))
|
||||||
return LexDecFloatLiteral(begin, end_, &cur_);
|
return LexDecFloatLiteral(begin, end_, &cur_);
|
||||||
|
|
||||||
@ -2270,6 +2275,9 @@ ParseFloatLiteral(WasmParseContext& c, WasmToken token, Float* result)
|
|||||||
case WasmToken::SignedInteger:
|
case WasmToken::SignedInteger:
|
||||||
*result = token.sint();
|
*result = token.sint();
|
||||||
return true;
|
return true;
|
||||||
|
case WasmToken::NegativeZero:
|
||||||
|
*result = -0.0;
|
||||||
|
return true;
|
||||||
case WasmToken::Float:
|
case WasmToken::Float:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -2345,6 +2353,8 @@ ParseConst(WasmParseContext& c, WasmToken constToken)
|
|||||||
break;
|
break;
|
||||||
return new(c.lifo) WasmAstConst(Val(uint32_t(sint.value())));
|
return new(c.lifo) WasmAstConst(Val(uint32_t(sint.value())));
|
||||||
}
|
}
|
||||||
|
case WasmToken::NegativeZero:
|
||||||
|
return new(c.lifo) WasmAstConst(Val(uint32_t(0)));
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -2358,6 +2368,8 @@ ParseConst(WasmParseContext& c, WasmToken constToken)
|
|||||||
return new(c.lifo) WasmAstConst(Val(val.uint()));
|
return new(c.lifo) WasmAstConst(Val(val.uint()));
|
||||||
case WasmToken::SignedInteger:
|
case WasmToken::SignedInteger:
|
||||||
return new(c.lifo) WasmAstConst(Val(uint64_t(val.sint())));
|
return new(c.lifo) WasmAstConst(Val(uint64_t(val.sint())));
|
||||||
|
case WasmToken::NegativeZero:
|
||||||
|
return new(c.lifo) WasmAstConst(Val(uint32_t(0)));
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -3770,11 +3782,11 @@ EncodeFunctionSignatures(Encoder& e, WasmAstModule& module)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
EncodeCString(Encoder& e, WasmName wasmName)
|
EncodeBytes(Encoder& e, WasmName wasmName)
|
||||||
{
|
{
|
||||||
TwoByteChars range(wasmName.begin(), wasmName.length());
|
TwoByteChars range(wasmName.begin(), wasmName.length());
|
||||||
UniqueChars utf8(JS::CharsToNewUTF8CharsZ(nullptr, range).c_str());
|
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
|
static bool
|
||||||
@ -3783,10 +3795,10 @@ EncodeImport(Encoder& e, WasmAstImport& imp)
|
|||||||
if (!e.writeVarU32(imp.sigIndex()))
|
if (!e.writeVarU32(imp.sigIndex()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!EncodeCString(e, imp.module()))
|
if (!EncodeBytes(e, imp.module()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!EncodeCString(e, imp.func()))
|
if (!EncodeBytes(e, imp.func()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -3854,7 +3866,7 @@ EncodeFunctionExport(Encoder& e, WasmAstExport& exp)
|
|||||||
if (!e.writeVarU32(exp.func().index()))
|
if (!e.writeVarU32(exp.func().index()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!EncodeCString(e, exp.name()))
|
if (!EncodeBytes(e, exp.name()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -3925,13 +3937,11 @@ EncodeFunctionBody(Encoder& e, WasmAstFunc& func)
|
|||||||
|
|
||||||
size_t beforeBody = e.currentOffset();
|
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;
|
return false;
|
||||||
|
|
||||||
for (ValType type : func.vars()) {
|
|
||||||
if (!e.writeValType(type))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (WasmAstExpr* expr : func.body()) {
|
for (WasmAstExpr* expr : func.body()) {
|
||||||
if (!EncodeExpr(e, *expr))
|
if (!EncodeExpr(e, *expr))
|
||||||
@ -3981,10 +3991,7 @@ EncodeDataSegment(Encoder& e, WasmAstSegment& segment)
|
|||||||
bytes.infallibleAppend(byte);
|
bytes.infallibleAppend(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!e.writeVarU32(bytes.length()))
|
if (!e.writeBytes(bytes.begin(), bytes.length()))
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!e.writeRawData(bytes.begin(), bytes.length()))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -4014,60 +4021,56 @@ EncodeDataSegments(Encoder& e, WasmAstModule& module)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static UniqueBytecode
|
static bool
|
||||||
EncodeModule(WasmAstModule& module)
|
EncodeModule(WasmAstModule& module, Bytes* bytes)
|
||||||
{
|
{
|
||||||
UniqueBytecode bytecode = MakeUnique<Bytecode>();
|
Encoder e(*bytes);
|
||||||
if (!bytecode)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
Encoder e(*bytecode);
|
|
||||||
|
|
||||||
if (!e.writeFixedU32(MagicNumber))
|
if (!e.writeFixedU32(MagicNumber))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!e.writeFixedU32(EncodingVersion))
|
if (!e.writeFixedU32(EncodingVersion))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeSignatures(e, module))
|
if (!EncodeSignatures(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeImportTable(e, module))
|
if (!EncodeImportTable(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeFunctionSignatures(e, module))
|
if (!EncodeFunctionSignatures(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeFunctionTable(e, module))
|
if (!EncodeFunctionTable(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeMemory(e, module))
|
if (!EncodeMemory(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeExportTable(e, module))
|
if (!EncodeExportTable(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeFunctionBodies(e, module))
|
if (!EncodeFunctionBodies(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!EncodeDataSegments(e, module))
|
if (!EncodeDataSegments(e, module))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
return Move(bytecode);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
UniqueBytecode
|
bool
|
||||||
wasm::TextToBinary(const char16_t* text, UniqueChars* error)
|
wasm::TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error)
|
||||||
{
|
{
|
||||||
LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
|
LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
|
||||||
WasmAstModule* module = ParseModule(text, lifo, error);
|
WasmAstModule* module = ParseModule(text, lifo, error);
|
||||||
if (!module)
|
if (!module)
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
if (!ResolveModule(lifo, module, error))
|
if (!ResolveModule(lifo, module, error))
|
||||||
return nullptr;
|
return false;
|
||||||
|
|
||||||
return EncodeModule(*module);
|
return EncodeModule(*module, bytes);
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@ namespace js {
|
|||||||
namespace wasm {
|
namespace wasm {
|
||||||
|
|
||||||
// Translate the textual representation of a wasm module (given by a
|
// 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'.
|
// other than out-of-memory an error message string will be stored in 'error'.
|
||||||
|
|
||||||
extern UniqueBytecode
|
extern bool
|
||||||
TextToBinary(const char16_t* text, UniqueChars* error);
|
TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error);
|
||||||
|
|
||||||
} // namespace wasm
|
} // namespace wasm
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "mozilla/EnumeratedArray.h"
|
#include "mozilla/EnumeratedArray.h"
|
||||||
#include "mozilla/HashFunctions.h"
|
#include "mozilla/HashFunctions.h"
|
||||||
|
#include "mozilla/Maybe.h"
|
||||||
#include "mozilla/Move.h"
|
#include "mozilla/Move.h"
|
||||||
|
|
||||||
#include "NamespaceImports.h"
|
#include "NamespaceImports.h"
|
||||||
@ -39,11 +40,11 @@ class PropertyName;
|
|||||||
namespace wasm {
|
namespace wasm {
|
||||||
|
|
||||||
using mozilla::EnumeratedArray;
|
using mozilla::EnumeratedArray;
|
||||||
|
using mozilla::Maybe;
|
||||||
using mozilla::Move;
|
using mozilla::Move;
|
||||||
using mozilla::MallocSizeOf;
|
using mozilla::MallocSizeOf;
|
||||||
|
|
||||||
typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
|
typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
|
||||||
typedef Vector<ValType, 8, SystemAllocPolicy> ValTypeVector;
|
|
||||||
|
|
||||||
// ValType/ExprType utilities
|
// ValType/ExprType utilities
|
||||||
|
|
||||||
@ -594,13 +595,14 @@ static const unsigned NaN64GlobalDataOffset = HeapGlobalDataOffset + sizeof
|
|||||||
static const unsigned NaN32GlobalDataOffset = NaN64GlobalDataOffset + sizeof(double);
|
static const unsigned NaN32GlobalDataOffset = NaN64GlobalDataOffset + sizeof(double);
|
||||||
static const unsigned InitialGlobalDataBytes = NaN32GlobalDataOffset + sizeof(float);
|
static const unsigned InitialGlobalDataBytes = NaN32GlobalDataOffset + sizeof(float);
|
||||||
|
|
||||||
static const unsigned MaxSigs = 4 * 1024;
|
static const unsigned MaxSigs = 4 * 1024;
|
||||||
static const unsigned MaxFuncs = 512 * 1024;
|
static const unsigned MaxFuncs = 512 * 1024;
|
||||||
static const unsigned MaxImports = 4 * 1024;
|
static const unsigned MaxLocals = 64 * 1024;
|
||||||
static const unsigned MaxExports = 4 * 1024;
|
static const unsigned MaxImports = 64 * 1024;
|
||||||
static const unsigned MaxTableElems = 128 * 1024;
|
static const unsigned MaxExports = 64 * 1024;
|
||||||
static const unsigned MaxArgsPerFunc = 4 * 1024;
|
static const unsigned MaxTableElems = 128 * 1024;
|
||||||
static const unsigned MaxBrTableElems = 4 * 1024;
|
static const unsigned MaxArgsPerFunc = 4 * 1024;
|
||||||
|
static const unsigned MaxBrTableElems = 4 * 1024 * 1024;
|
||||||
|
|
||||||
} // namespace wasm
|
} // namespace wasm
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
@ -678,7 +678,109 @@ js::obj_getOwnPropertyDescriptor(JSContext* cx, unsigned argc, Value* vp)
|
|||||||
FromPropertyDescriptor(cx, desc, args.rval());
|
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
|
static bool
|
||||||
obj_keys(JSContext* cx, unsigned argc, Value* vp)
|
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);
|
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 */
|
/* ES6 draft 15.2.3.16 */
|
||||||
static bool
|
static bool
|
||||||
obj_is(JSContext* cx, unsigned argc, Value* vp)
|
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),
|
JS_FN(js_toSource_str, obj_toSource, 0,0),
|
||||||
#endif
|
#endif
|
||||||
JS_FN(js_toString_str, obj_toString, 0,0),
|
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),
|
JS_FN(js_valueOf_str, obj_valueOf, 0,0),
|
||||||
#if JS_HAS_OBJ_WATCHPOINT
|
#if JS_HAS_OBJ_WATCHPOINT
|
||||||
JS_FN(js_watch_str, obj_watch, 2,0),
|
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_isPrototypeOf_str, obj_isPrototypeOf, 1,0),
|
||||||
JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0),
|
JS_FN(js_propertyIsEnumerable_str, obj_propertyIsEnumerable, 1,0),
|
||||||
#if JS_OLD_GETTER_SETTER_METHODS
|
#if JS_OLD_GETTER_SETTER_METHODS
|
||||||
JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter", 2,JSPROP_DEFINE_LATE),
|
JS_SELF_HOSTED_FN(js_defineGetter_str, "ObjectDefineGetter", 2,0),
|
||||||
JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter", 2,JSPROP_DEFINE_LATE),
|
JS_SELF_HOSTED_FN(js_defineSetter_str, "ObjectDefineSetter", 2,0),
|
||||||
JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter", 1,JSPROP_DEFINE_LATE),
|
JS_SELF_HOSTED_FN(js_lookupGetter_str, "ObjectLookupGetter", 1,0),
|
||||||
JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter", 1,JSPROP_DEFINE_LATE),
|
JS_SELF_HOSTED_FN(js_lookupSetter_str, "ObjectLookupSetter", 1,0),
|
||||||
#endif
|
#endif
|
||||||
JS_FS_END
|
JS_FS_END
|
||||||
};
|
};
|
||||||
@ -1002,22 +1122,20 @@ static const JSPropertySpec object_properties[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const JSFunctionSpec object_static_methods[] = {
|
static const JSFunctionSpec object_static_methods[] = {
|
||||||
JS_SELF_HOSTED_FN("assign", "ObjectStaticAssign", 2, JSPROP_DEFINE_LATE),
|
JS_SELF_HOSTED_FN("assign", "ObjectStaticAssign", 2, 0),
|
||||||
JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, JSPROP_DEFINE_LATE),
|
JS_SELF_HOSTED_FN("getPrototypeOf", "ObjectGetPrototypeOf", 1, 0),
|
||||||
JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0),
|
JS_FN("setPrototypeOf", obj_setPrototypeOf, 2, 0),
|
||||||
JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2, 0),
|
JS_FN("getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor,2, 0),
|
||||||
JS_FN("keys", obj_keys, 1, 0),
|
JS_FN("keys", obj_keys, 1, 0),
|
||||||
#ifndef RELEASE_BUILD
|
JS_FN("values", obj_values, 1, 0),
|
||||||
JS_SELF_HOSTED_FN("values", "ObjectValues", 1, JSPROP_DEFINE_LATE),
|
JS_FN("entries", obj_entries, 1, 0),
|
||||||
JS_SELF_HOSTED_FN("entries", "ObjectEntries", 1, JSPROP_DEFINE_LATE),
|
|
||||||
#endif
|
|
||||||
JS_FN("is", obj_is, 2, 0),
|
JS_FN("is", obj_is, 2, 0),
|
||||||
JS_FN("defineProperty", obj_defineProperty, 3, 0),
|
JS_FN("defineProperty", obj_defineProperty, 3, 0),
|
||||||
JS_FN("defineProperties", obj_defineProperties, 2, 0),
|
JS_FN("defineProperties", obj_defineProperties, 2, 0),
|
||||||
JS_INLINABLE_FN("create", obj_create, 2, 0, ObjectCreate),
|
JS_INLINABLE_FN("create", obj_create, 2, 0, ObjectCreate),
|
||||||
JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1, 0),
|
JS_FN("getOwnPropertyNames", obj_getOwnPropertyNames, 1, 0),
|
||||||
JS_FN("getOwnPropertySymbols", obj_getOwnPropertySymbols, 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("preventExtensions", obj_preventExtensions, 1, 0),
|
||||||
JS_FN("freeze", obj_freeze, 1, 0),
|
JS_FN("freeze", obj_freeze, 1, 0),
|
||||||
JS_FN("isFrozen", obj_isFrozen, 1, 0),
|
JS_FN("isFrozen", obj_isFrozen, 1, 0),
|
||||||
@ -1088,22 +1206,6 @@ FinishObjectClassInit(JSContext* cx, JS::HandleObject ctor, JS::HandleObject pro
|
|||||||
if (!holder)
|
if (!holder)
|
||||||
return false;
|
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]].
|
* The global object should have |Object.prototype| as its [[Prototype]].
|
||||||
* Eventually we'd like to have standard classes be there from the start,
|
* 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);
|
object = std_Reflect_getPrototypeOf(object);
|
||||||
} while (object !== null);
|
} 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()))
|
if (!twoByteChars.initTwoByte(cx, args[0].toString()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
wasm::Bytes bytes;
|
||||||
UniqueChars error;
|
UniqueChars error;
|
||||||
wasm::UniqueBytecode bytes = wasm::TextToBinary(twoByteChars.twoByteChars(), &error);
|
if (!wasm::TextToBinary(twoByteChars.twoByteChars(), &bytes, &error)) {
|
||||||
if (!bytes) {
|
|
||||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
|
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_TEXT_FAIL,
|
||||||
error.get() ? error.get() : "out of memory");
|
error.get() ? error.get() : "out of memory");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RootedObject obj(cx, JS_NewUint8Array(cx, bytes->length()));
|
RootedObject obj(cx, JS_NewUint8Array(cx, bytes.length()));
|
||||||
if (!obj)
|
if (!obj)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes->begin(), bytes->length());
|
memcpy(obj->as<TypedArrayObject>().viewDataUnshared(), bytes.begin(), bytes.length());
|
||||||
|
|
||||||
args.rval().setObject(*obj);
|
args.rval().setObject(*obj);
|
||||||
return true;
|
return true;
|
||||||
|
@ -314,8 +314,8 @@ class StoreBuffer
|
|||||||
// overlap.
|
// overlap.
|
||||||
void merge(const SlotsEdge& other) {
|
void merge(const SlotsEdge& other) {
|
||||||
MOZ_ASSERT(overlaps(other));
|
MOZ_ASSERT(overlaps(other));
|
||||||
auto end = std::max(start_ + count_, other.start_ + other.count_);
|
auto end = Max(start_ + count_, other.start_ + other.count_);
|
||||||
start_ = std::min(start_, other.start_);
|
start_ = Min(start_, other.start_);
|
||||||
count_ = end - start_;
|
count_ = end - start_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,9 +41,11 @@ testConst('i32', '0xffffffff', -1);
|
|||||||
//testConst('i64', '0xffffffffffffffff', -1); // TODO: NYI
|
//testConst('i64', '0xffffffffffffffff', -1); // TODO: NYI
|
||||||
|
|
||||||
testConst('f32', '0.0', 0.0);
|
testConst('f32', '0.0', 0.0);
|
||||||
|
testConst('f32', '-0', -0.0);
|
||||||
testConst('f32', '-0.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.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', '-0x0.0p0', -0.0);
|
testConst('f32', '-0x0.0p0', -0.0);
|
||||||
testConst('f32', 'infinity', Infinity);
|
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.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.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', '-0x0.0p0', -0.0);
|
testConst('f64', '-0x0.0p0', -0.0);
|
||||||
testConst('f64', 'infinity', Infinity);
|
testConst('f64', 'infinity', Infinity);
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
load(libdir + "wasm.js");
|
load(libdir + "wasm.js");
|
||||||
|
|
||||||
if (!wasmIsSupported())
|
|
||||||
quit();
|
|
||||||
|
|
||||||
function testLoad(type, ext, base, offset, align, expect) {
|
function testLoad(type, ext, base, offset, align, expect) {
|
||||||
assertEq(wasmEvalText(
|
assertEq(wasmEvalText(
|
||||||
'(module' +
|
'(module' +
|
||||||
|
@ -79,16 +79,20 @@ function cstring(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function string(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) {
|
function moduleWithSections(sectionArray) {
|
||||||
var bytes = moduleHeaderThen();
|
var bytes = moduleHeaderThen();
|
||||||
for (let section of sectionArray) {
|
for (let section of sectionArray) {
|
||||||
var nameLength = varU32(section.name.length);
|
var sectionName = string(section.name);
|
||||||
bytes.push(...varU32(nameLength.length + section.name.length + section.body.length));
|
bytes.push(...varU32(sectionName.length + section.body.length));
|
||||||
bytes.push(...nameLength);
|
bytes.push(...sectionName);
|
||||||
bytes.push(...string(section.name));
|
|
||||||
bytes.push(...section.body);
|
bytes.push(...section.body);
|
||||||
}
|
}
|
||||||
return toU8(bytes);
|
return toU8(bytes);
|
||||||
@ -133,8 +137,8 @@ function importSection(imports) {
|
|||||||
body.push(...varU32(imports.length));
|
body.push(...varU32(imports.length));
|
||||||
for (let imp of imports) {
|
for (let imp of imports) {
|
||||||
body.push(...varU32(imp.sigIndex));
|
body.push(...varU32(imp.sigIndex));
|
||||||
body.push(...cstring(imp.module));
|
body.push(...string(imp.module));
|
||||||
body.push(...cstring(imp.func));
|
body.push(...string(imp.func));
|
||||||
}
|
}
|
||||||
return { name: importId, body };
|
return { name: importId, body };
|
||||||
}
|
}
|
||||||
@ -163,8 +167,8 @@ wasmEval(moduleWithSections([sigSection([v2vSig])]));
|
|||||||
wasmEval(moduleWithSections([sigSection([i2vSig])]));
|
wasmEval(moduleWithSections([sigSection([i2vSig])]));
|
||||||
wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])]));
|
wasmEval(moduleWithSections([sigSection([v2vSig, i2vSig])]));
|
||||||
|
|
||||||
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /bad expression type/);
|
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[], ret:100}])])), TypeError, /expression type/);
|
||||||
assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([{args:[100], ret:VoidCode}])])), TypeError, /bad value 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([]), declSection([0])])), TypeError, /signature index out of range/);
|
||||||
assertThrowsInstanceOf(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([1])])), 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())
|
if (!base()->isConstant() || !store->base()->isConstant())
|
||||||
return true;
|
return true;
|
||||||
const MConstant* otherBase = store->base()->toConstant();
|
const MConstant* otherBase = store->base()->toConstant();
|
||||||
return base()->toConstant()->equals(otherBase);
|
return base()->toConstant()->equals(otherBase) &&
|
||||||
|
offset() == store->offset();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -4566,7 +4567,9 @@ MAsmJSLoadHeap::congruentTo(const MDefinition* ins) const
|
|||||||
if (!ins->isAsmJSLoadHeap())
|
if (!ins->isAsmJSLoadHeap())
|
||||||
return false;
|
return false;
|
||||||
const MAsmJSLoadHeap* load = ins->toAsmJSLoadHeap();
|
const MAsmJSLoadHeap* load = ins->toAsmJSLoadHeap();
|
||||||
return load->accessType() == accessType() && congruentIfOperandsEqual(load);
|
return load->accessType() == accessType() &&
|
||||||
|
load->offset() == offset() &&
|
||||||
|
congruentIfOperandsEqual(load);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -43,8 +43,8 @@ BEGIN_TEST(testWasmLEB128_encoding)
|
|||||||
using namespace js;
|
using namespace js;
|
||||||
using namespace wasm;
|
using namespace wasm;
|
||||||
|
|
||||||
Bytecode bc;
|
Bytes bytes;
|
||||||
Encoder encoder(bc);
|
Encoder encoder(bytes);
|
||||||
|
|
||||||
bool passed;
|
bool passed;
|
||||||
if (!WriteValidBytes(encoder, &passed))
|
if (!WriteValidBytes(encoder, &passed))
|
||||||
@ -52,18 +52,18 @@ BEGIN_TEST(testWasmLEB128_encoding)
|
|||||||
CHECK(passed);
|
CHECK(passed);
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
CHECK(bc[i++] == 0x0);
|
CHECK(bytes[i++] == 0x0);
|
||||||
CHECK(bc[i++] == 0x1);
|
CHECK(bytes[i++] == 0x1);
|
||||||
CHECK(bc[i++] == 0x42);
|
CHECK(bytes[i++] == 0x42);
|
||||||
|
|
||||||
CHECK(bc[i++] == 0x80);
|
CHECK(bytes[i++] == 0x80);
|
||||||
CHECK(bc[i++] == 0x01);
|
CHECK(bytes[i++] == 0x01);
|
||||||
|
|
||||||
CHECK(bc[i++] == 0x80);
|
CHECK(bytes[i++] == 0x80);
|
||||||
CHECK(bc[i++] == 0x03);
|
CHECK(bytes[i++] == 0x03);
|
||||||
|
|
||||||
if (i + 1 < bc.length())
|
if (i + 1 < bytes.length())
|
||||||
CHECK(bc[i++] == 0x00);
|
CHECK(bytes[i++] == 0x00);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
END_TEST(testWasmLEB128_encoding)
|
END_TEST(testWasmLEB128_encoding)
|
||||||
@ -73,19 +73,19 @@ BEGIN_TEST(testWasmLEB128_valid_decoding)
|
|||||||
using namespace js;
|
using namespace js;
|
||||||
using namespace wasm;
|
using namespace wasm;
|
||||||
|
|
||||||
Bytecode bc;
|
Bytes bytes;
|
||||||
if (!bc.append(0x0) || !bc.append(0x1) || !bc.append(0x42))
|
if (!bytes.append(0x0) || !bytes.append(0x1) || !bytes.append(0x42))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!bc.append(0x80) || !bc.append(0x01))
|
if (!bytes.append(0x80) || !bytes.append(0x01))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!bc.append(0x80) || !bc.append(0x03))
|
if (!bytes.append(0x80) || !bytes.append(0x03))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
{
|
{
|
||||||
// Fallible decoding
|
// Fallible decoding
|
||||||
Decoder decoder(bc);
|
Decoder decoder(bytes);
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
|
|
||||||
CHECK(decoder.readVarU32(&value) && value == 0x0);
|
CHECK(decoder.readVarU32(&value) && value == 0x0);
|
||||||
@ -99,7 +99,7 @@ BEGIN_TEST(testWasmLEB128_valid_decoding)
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Infallible decoding
|
// Infallible decoding
|
||||||
Decoder decoder(bc);
|
Decoder decoder(bytes);
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
|
|
||||||
value = decoder.uncheckedReadVarU32();
|
value = decoder.uncheckedReadVarU32();
|
||||||
@ -124,20 +124,20 @@ BEGIN_TEST(testWasmLEB128_invalid_decoding)
|
|||||||
using namespace js;
|
using namespace js;
|
||||||
using namespace wasm;
|
using namespace wasm;
|
||||||
|
|
||||||
Bytecode bc;
|
Bytes bytes;
|
||||||
// Fill bits as per 28 encoded bits
|
// 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;
|
return false;
|
||||||
|
|
||||||
// Test last valid values
|
// Test last valid values
|
||||||
if (!bc.append(0x00))
|
if (!bytes.append(0x00))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < 0x0F; i++) {
|
for (uint8_t i = 0; i < 0x0F; i++) {
|
||||||
bc[4] = i;
|
bytes[4] = i;
|
||||||
|
|
||||||
{
|
{
|
||||||
Decoder decoder(bc);
|
Decoder decoder(bytes);
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
CHECK(decoder.readVarU32(&value));
|
CHECK(decoder.readVarU32(&value));
|
||||||
CHECK(value == uint32_t(i << 28));
|
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();
|
uint32_t value = decoder.uncheckedReadVarU32();
|
||||||
CHECK(value == uint32_t(i << 28));
|
CHECK(value == uint32_t(i << 28));
|
||||||
CHECK(decoder.done());
|
CHECK(decoder.done());
|
||||||
@ -154,9 +154,9 @@ BEGIN_TEST(testWasmLEB128_invalid_decoding)
|
|||||||
|
|
||||||
// Test all invalid values of the same size
|
// Test all invalid values of the same size
|
||||||
for (uint8_t i = 0x10; i < 0xF0; i++) {
|
for (uint8_t i = 0x10; i < 0xF0; i++) {
|
||||||
bc[4] = i;
|
bytes[4] = i;
|
||||||
|
|
||||||
Decoder decoder(bc);
|
Decoder decoder(bytes);
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
CHECK(!decoder.readVarU32(&value));
|
CHECK(!decoder.readVarU32(&value));
|
||||||
}
|
}
|
||||||
|
@ -3669,15 +3669,14 @@ JS_IsConstructor(JSFunction* fun)
|
|||||||
}
|
}
|
||||||
|
|
||||||
JS_PUBLIC_API(bool)
|
JS_PUBLIC_API(bool)
|
||||||
JS_DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
|
JS_DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs)
|
||||||
PropertyDefinitionBehavior behavior)
|
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
|
MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
|
||||||
AssertHeapIsIdle(cx);
|
AssertHeapIsIdle(cx);
|
||||||
CHECK_REQUEST(cx);
|
CHECK_REQUEST(cx);
|
||||||
assertSameCompartment(cx, obj);
|
assertSameCompartment(cx, obj);
|
||||||
|
|
||||||
return DefineFunctions(cx, obj, fs, NotIntrinsic, behavior);
|
return DefineFunctions(cx, obj, fs, NotIntrinsic);
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_PUBLIC_API(JSFunction*)
|
JS_PUBLIC_API(JSFunction*)
|
||||||
|
@ -834,10 +834,7 @@ class MOZ_STACK_CLASS SourceBufferHolder final
|
|||||||
object that delegates to a prototype
|
object that delegates to a prototype
|
||||||
containing this property */
|
containing this property */
|
||||||
#define JSPROP_INTERNAL_USE_BIT 0x80 /* internal JS engine use only */
|
#define JSPROP_INTERNAL_USE_BIT 0x80 /* internal JS engine use only */
|
||||||
#define JSPROP_DEFINE_LATE 0x100 /* Don't define property when initially creating
|
// 0x100 /* Unused */
|
||||||
the constructor. Some objects like Function/Object
|
|
||||||
have self-hosted functions that can only be defined
|
|
||||||
after the initialization is already finished. */
|
|
||||||
#define JSFUN_STUB_GSOPS 0x200 /* use JS_PropertyStub getter/setter
|
#define JSFUN_STUB_GSOPS 0x200 /* use JS_PropertyStub getter/setter
|
||||||
instead of defaulting to class gsops
|
instead of defaulting to class gsops
|
||||||
for property holding function */
|
for property holding function */
|
||||||
@ -3593,20 +3590,8 @@ JS_IsNativeFunction(JSObject* funobj, JSNative call);
|
|||||||
extern JS_PUBLIC_API(bool)
|
extern JS_PUBLIC_API(bool)
|
||||||
JS_IsConstructor(JSFunction* fun);
|
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)
|
extern JS_PUBLIC_API(bool)
|
||||||
JS_DefineFunctions(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* fs,
|
JS_DefineFunctions(JSContext* cx, JS::Handle<JSObject*> obj, const JSFunctionSpec* fs);
|
||||||
PropertyDefinitionBehavior behavior = DefineAllProperties);
|
|
||||||
|
|
||||||
extern JS_PUBLIC_API(JSFunction*)
|
extern JS_PUBLIC_API(JSFunction*)
|
||||||
JS_DefineFunction(JSContext* cx, JS::Handle<JSObject*> obj, const char* name, JSNative call,
|
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);
|
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
|
#ifdef DEBUG
|
||||||
bool
|
bool
|
||||||
js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
|
js::ArrayInfo(JSContext* cx, unsigned argc, Value* vp)
|
||||||
|
@ -119,6 +119,9 @@ extern JSObject*
|
|||||||
NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
|
NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length,
|
||||||
HandleObject proto = nullptr);
|
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
|
* 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
|
* |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_apply_str, fun_apply, 2,0),
|
||||||
JS_FN(js_call_str, fun_call, 1,0),
|
JS_FN(js_call_str, fun_call, 1,0),
|
||||||
JS_FN("isGenerator", fun_isGenerator,0,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
|
JS_FS_END
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,15 +84,7 @@ typedef HashSet<jsid, IdHashPolicy> IdSet;
|
|||||||
static inline bool
|
static inline bool
|
||||||
NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval)
|
NewKeyValuePair(JSContext* cx, jsid id, const Value& val, MutableHandleValue rval)
|
||||||
{
|
{
|
||||||
JS::AutoValueArray<2> vec(cx);
|
return NewValuePair(cx, IdToValue(id), val, rval);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
|
@ -2907,24 +2907,10 @@ DefineFunctionFromSpec(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
js::DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
|
js::DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
|
||||||
DefineAsIntrinsic intrinsic, PropertyDefinitionBehavior behavior)
|
DefineAsIntrinsic intrinsic)
|
||||||
{
|
{
|
||||||
for (; fs->name; fs++) {
|
for (; fs->name; fs++) {
|
||||||
unsigned flags = fs->flags;
|
if (!DefineFunctionFromSpec(cx, obj, fs, fs->flags, intrinsic))
|
||||||
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))
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -992,8 +992,7 @@ enum DefineAsIntrinsic {
|
|||||||
|
|
||||||
extern bool
|
extern bool
|
||||||
DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
|
DefineFunctions(JSContext* cx, HandleObject obj, const JSFunctionSpec* fs,
|
||||||
DefineAsIntrinsic intrinsic,
|
DefineAsIntrinsic intrinsic);
|
||||||
PropertyDefinitionBehavior behavior = DefineAllProperties);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set a watchpoint: a synchronous callback when the given property of the
|
* Set a watchpoint: a synchronous callback when the given property of the
|
||||||
|
@ -147,6 +147,7 @@ EXPORTS.js += [
|
|||||||
UNIFIED_SOURCES += [
|
UNIFIED_SOURCES += [
|
||||||
'asmjs/AsmJS.cpp',
|
'asmjs/AsmJS.cpp',
|
||||||
'asmjs/Wasm.cpp',
|
'asmjs/Wasm.cpp',
|
||||||
|
'asmjs/WasmBinary.cpp',
|
||||||
'asmjs/WasmFrameIterator.cpp',
|
'asmjs/WasmFrameIterator.cpp',
|
||||||
'asmjs/WasmGenerator.cpp',
|
'asmjs/WasmGenerator.cpp',
|
||||||
'asmjs/WasmIonCompile.cpp',
|
'asmjs/WasmIonCompile.cpp',
|
||||||
|
@ -220,10 +220,12 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle<GlobalObject*> global, JS
|
|||||||
global->setConstructorPropertySlot(key, ObjectValue(*ctor));
|
global->setConstructorPropertySlot(key, ObjectValue(*ctor));
|
||||||
|
|
||||||
// Define any specified functions and properties, unless we're a dependent
|
// Define any specified functions and properties, unless we're a dependent
|
||||||
// standard class (in which case they live on the prototype).
|
// standard class (in which case they live on the prototype), or we're
|
||||||
if (!StandardClassIsDependent(key)) {
|
// 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 (const JSFunctionSpec* funs = clasp->spec.prototypeFunctions()) {
|
||||||
if (!JS_DefineFunctions(cx, proto, funs, DontDefineLateProperties))
|
if (!JS_DefineFunctions(cx, proto, funs))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (const JSPropertySpec* props = clasp->spec.prototypeProperties()) {
|
if (const JSPropertySpec* props = clasp->spec.prototypeProperties()) {
|
||||||
@ -231,7 +233,7 @@ GlobalObject::resolveConstructor(JSContext* cx, Handle<GlobalObject*> global, JS
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (const JSFunctionSpec* funs = clasp->spec.constructorFunctions()) {
|
if (const JSFunctionSpec* funs = clasp->spec.constructorFunctions()) {
|
||||||
if (!JS_DefineFunctions(cx, ctor, funs, DontDefineLateProperties))
|
if (!JS_DefineFunctions(cx, ctor, funs))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (const JSPropertySpec* props = clasp->spec.constructorProperties()) {
|
if (const JSPropertySpec* props = clasp->spec.constructorProperties()) {
|
||||||
|
@ -137,6 +137,59 @@ intrinsic_IsConstructor(JSContext* cx, unsigned argc, Value* vp)
|
|||||||
return true;
|
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>
|
template<typename T>
|
||||||
static bool
|
static bool
|
||||||
intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
|
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,
|
JS_INLINABLE_FN("UnsafeGetBooleanFromReservedSlot", intrinsic_UnsafeGetBooleanFromReservedSlot,2,0,
|
||||||
IntrinsicUnsafeGetBooleanFromReservedSlot),
|
IntrinsicUnsafeGetBooleanFromReservedSlot),
|
||||||
|
|
||||||
|
JS_FN("UnsafeCallWrappedFunction", intrinsic_UnsafeCallWrappedFunction,2,0),
|
||||||
|
|
||||||
JS_FN("IsPackedArray", intrinsic_IsPackedArray, 1,0),
|
JS_FN("IsPackedArray", intrinsic_IsPackedArray, 1,0),
|
||||||
|
|
||||||
JS_FN("GetIteratorPrototype", intrinsic_GetIteratorPrototype, 0,0),
|
JS_FN("GetIteratorPrototype", intrinsic_GetIteratorPrototype, 0,0),
|
||||||
|
@ -9305,40 +9305,52 @@ PresShell::Observe(nsISupports* aSubject,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!nsCRT::strcmp(aTopic, "agent-sheet-added") && mStyleSet) {
|
if (!nsCRT::strcmp(aTopic, "agent-sheet-added")) {
|
||||||
AddAgentSheet(aSubject);
|
if (mStyleSet) {
|
||||||
|
AddAgentSheet(aSubject);
|
||||||
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsCRT::strcmp(aTopic, "user-sheet-added") && mStyleSet) {
|
if (!nsCRT::strcmp(aTopic, "user-sheet-added")) {
|
||||||
AddUserSheet(aSubject);
|
if (mStyleSet) {
|
||||||
|
AddUserSheet(aSubject);
|
||||||
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsCRT::strcmp(aTopic, "author-sheet-added") && mStyleSet) {
|
if (!nsCRT::strcmp(aTopic, "author-sheet-added")) {
|
||||||
AddAuthorSheet(aSubject);
|
if (mStyleSet) {
|
||||||
|
AddAuthorSheet(aSubject);
|
||||||
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsCRT::strcmp(aTopic, "agent-sheet-removed") && mStyleSet) {
|
if (!nsCRT::strcmp(aTopic, "agent-sheet-removed")) {
|
||||||
RemoveSheet(SheetType::Agent, aSubject);
|
if (mStyleSet) {
|
||||||
|
RemoveSheet(SheetType::Agent, aSubject);
|
||||||
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsCRT::strcmp(aTopic, "user-sheet-removed") && mStyleSet) {
|
if (!nsCRT::strcmp(aTopic, "user-sheet-removed")) {
|
||||||
RemoveSheet(SheetType::User, aSubject);
|
if (mStyleSet) {
|
||||||
|
RemoveSheet(SheetType::User, aSubject);
|
||||||
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsCRT::strcmp(aTopic, "author-sheet-removed") && mStyleSet) {
|
if (!nsCRT::strcmp(aTopic, "author-sheet-removed")) {
|
||||||
RemoveSheet(SheetType::Doc, aSubject);
|
if (mStyleSet) {
|
||||||
|
RemoveSheet(SheetType::Doc, aSubject);
|
||||||
|
}
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsCRT::strcmp(aTopic, "memory-pressure") &&
|
if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
|
||||||
!AssumeAllImagesVisible() &&
|
if (!AssumeAllImagesVisible() && mPresContext->IsRootContentDocument()) {
|
||||||
mPresContext->IsRootContentDocument()) {
|
DoUpdateImageVisibility(/* aRemoveOnly = */ true);
|
||||||
DoUpdateImageVisibility(/* aRemoveOnly = */ true);
|
}
|
||||||
return NS_OK;
|
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;
|
Mode mMode;
|
||||||
NumOption<size_t> mSampleBelowSize;
|
NumOption<size_t> mSampleBelowSize;
|
||||||
NumOption<uint32_t> mMaxFrames;
|
|
||||||
bool mShowDumpStats;
|
bool mShowDumpStats;
|
||||||
|
|
||||||
void BadArg(const char* aArg);
|
void BadArg(const char* aArg);
|
||||||
@ -404,7 +403,6 @@ public:
|
|||||||
const char* DMDEnvVar() const { return mDMDEnvVar; }
|
const char* DMDEnvVar() const { return mDMDEnvVar; }
|
||||||
|
|
||||||
size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
|
size_t SampleBelowSize() const { return mSampleBelowSize.mActual; }
|
||||||
size_t MaxFrames() const { return mMaxFrames.mActual; }
|
|
||||||
size_t ShowDumpStats() const { return mShowDumpStats; }
|
size_t ShowDumpStats() const { return mShowDumpStats; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -695,9 +693,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t mLength; // The number of PCs.
|
uint32_t mLength; // The number of PCs.
|
||||||
const void* mPcs[MaxFrames]; // The PCs themselves. If --max-frames is less
|
const void* mPcs[MaxFrames]; // The PCs themselves.
|
||||||
// than 24, this array is bigger than
|
|
||||||
// necessary, but that case is unusual.
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StackTrace() : mLength(0) {}
|
StackTrace() : mLength(0) {}
|
||||||
@ -779,7 +775,7 @@ StackTrace::Get(Thread* aT)
|
|||||||
AutoUnlockState unlock;
|
AutoUnlockState unlock;
|
||||||
uint32_t skipFrames = 2;
|
uint32_t skipFrames = 2;
|
||||||
if (MozStackWalk(StackWalkCallback, skipFrames,
|
if (MozStackWalk(StackWalkCallback, skipFrames,
|
||||||
gOptions->MaxFrames(), &tmp, 0, nullptr)) {
|
MaxFrames, &tmp, 0, nullptr)) {
|
||||||
// Handle the common case first. All is ok. Nothing to do.
|
// Handle the common case first. All is ok. Nothing to do.
|
||||||
} else {
|
} else {
|
||||||
tmp.mLength = 0;
|
tmp.mLength = 0;
|
||||||
@ -1430,7 +1426,6 @@ Options::Options(const char* aDMDEnvVar)
|
|||||||
: nullptr)
|
: nullptr)
|
||||||
, mMode(DarkMatter)
|
, mMode(DarkMatter)
|
||||||
, mSampleBelowSize(4093, 100 * 100 * 1000)
|
, mSampleBelowSize(4093, 100 * 100 * 1000)
|
||||||
, mMaxFrames(StackTrace::MaxFrames, StackTrace::MaxFrames)
|
|
||||||
, mShowDumpStats(false)
|
, mShowDumpStats(false)
|
||||||
{
|
{
|
||||||
// It's no longer necessary to set the DMD env var to "1" if you want default
|
// 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)) {
|
&myLong)) {
|
||||||
mSampleBelowSize.mActual = 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)) {
|
} else if (GetBool(arg, "--show-dump-stats", &myBool)) {
|
||||||
mShowDumpStats = myBool;
|
mShowDumpStats = myBool;
|
||||||
|
|
||||||
@ -1502,22 +1494,7 @@ Options::BadArg(const char* aArg)
|
|||||||
{
|
{
|
||||||
StatusMsg("\n");
|
StatusMsg("\n");
|
||||||
StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
|
StatusMsg("Bad entry in the $DMD environment variable: '%s'.\n", aArg);
|
||||||
StatusMsg("\n");
|
StatusMsg("See the output of |mach help run| for the allowed options.\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");
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import org.mozilla.gecko.dlc.DownloadContentService;
|
|||||||
import org.mozilla.gecko.home.HomePanelsManager;
|
import org.mozilla.gecko.home.HomePanelsManager;
|
||||||
import org.mozilla.gecko.lwt.LightweightTheme;
|
import org.mozilla.gecko.lwt.LightweightTheme;
|
||||||
import org.mozilla.gecko.mdns.MulticastDNSManager;
|
import org.mozilla.gecko.mdns.MulticastDNSManager;
|
||||||
import org.mozilla.gecko.push.PushService;
|
|
||||||
import org.mozilla.gecko.util.Clipboard;
|
import org.mozilla.gecko.util.Clipboard;
|
||||||
import org.mozilla.gecko.util.HardwareUtils;
|
import org.mozilla.gecko.util.HardwareUtils;
|
||||||
import org.mozilla.gecko.util.ThreadUtils;
|
import org.mozilla.gecko.util.ThreadUtils;
|
||||||
@ -166,27 +165,6 @@ public class GeckoApplication extends Application
|
|||||||
|
|
||||||
super.onCreate();
|
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) {
|
if (AppConstants.MOZ_ANDROID_DOWNLOAD_CONTENT_SERVICE) {
|
||||||
DownloadContentService.startStudy(this);
|
DownloadContentService.startStudy(this);
|
||||||
}
|
}
|
||||||
|
@ -5162,6 +5162,16 @@ pref("reader.has_used_toolbar", false);
|
|||||||
// Whether to use a vertical or horizontal toolbar.
|
// Whether to use a vertical or horizontal toolbar.
|
||||||
pref("reader.toolbar.vertical", true);
|
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)
|
#if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
|
||||||
// Whether to allow, on a Linux system that doesn't support the necessary sandboxing
|
// 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
|
// 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\'.')
|
help='Profiling mode. The default is \'dark-matter\'.')
|
||||||
@CommandArgument('--sample-below', default=None, type=str, group='DMD',
|
@CommandArgument('--sample-below', default=None, type=str, group='DMD',
|
||||||
help='Sample blocks smaller than this. Use 1 for no sampling. The default is 4093.')
|
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',
|
@CommandArgument('--show-dump-stats', action='store_true', group='DMD',
|
||||||
help='Show stats when doing dumps.')
|
help='Show stats when doing dumps.')
|
||||||
def run(self, params, remote, background, noprofile, debug, debugger,
|
def run(self, params, remote, background, noprofile, debug, debugger,
|
||||||
debugparams, slowscript, dmd, mode, sample_below, max_frames,
|
debugparams, slowscript, dmd, mode, sample_below, show_dump_stats):
|
||||||
show_dump_stats):
|
|
||||||
|
|
||||||
if conditions.is_android(self):
|
if conditions.is_android(self):
|
||||||
# Running Firefox for Android is completely different
|
# Running Firefox for Android is completely different
|
||||||
@ -1207,8 +1204,6 @@ class RunProgram(MachCommandBase):
|
|||||||
dmd_params.append('--mode=' + mode)
|
dmd_params.append('--mode=' + mode)
|
||||||
if sample_below:
|
if sample_below:
|
||||||
dmd_params.append('--sample-below=' + sample_below)
|
dmd_params.append('--sample-below=' + sample_below)
|
||||||
if max_frames:
|
|
||||||
dmd_params.append('--max-frames=' + max_frames)
|
|
||||||
if show_dump_stats:
|
if show_dump_stats:
|
||||||
dmd_params.append('--show-dump-stats=yes')
|
dmd_params.append('--show-dump-stats=yes')
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ function WebRequestEventManager(context, eventName) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let optional = ["requestHeaders", "responseHeaders", "statusCode", "redirectUrl"];
|
let optional = ["requestHeaders", "responseHeaders", "statusCode", "statusLine", "redirectUrl"];
|
||||||
for (let opt of optional) {
|
for (let opt of optional) {
|
||||||
if (opt in data) {
|
if (opt in data) {
|
||||||
data2[opt] = data[opt];
|
data2[opt] = data[opt];
|
||||||
|
@ -96,6 +96,20 @@ function backgroundScript() {
|
|||||||
return url.startsWith(BASE) || /^data:.*\bwebRequestTest\b/.test(url);
|
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) {
|
function checkType(details) {
|
||||||
let expected_type = "???";
|
let expected_type = "???";
|
||||||
if (details.url.indexOf("style") != -1) {
|
if (details.url.indexOf("style") != -1) {
|
||||||
@ -302,6 +316,7 @@ function backgroundScript() {
|
|||||||
|
|
||||||
browser.test.assertEq(details.tabId, savedTabId, "correct tab ID");
|
browser.test.assertEq(details.tabId, savedTabId, "correct tab ID");
|
||||||
checkType(details);
|
checkType(details);
|
||||||
|
checkStatus(details);
|
||||||
|
|
||||||
let id = frameIDs.get(details.url);
|
let id = frameIDs.get(details.url);
|
||||||
browser.test.assertEq(id, details.frameId, "frame ID same in onBeforeRedirect as onBeforeRequest");
|
browser.test.assertEq(id, details.frameId, "frame ID same in onBeforeRedirect as onBeforeRequest");
|
||||||
@ -344,6 +359,7 @@ function backgroundScript() {
|
|||||||
}
|
}
|
||||||
completedUrls[kind].add(details.url);
|
completedUrls[kind].add(details.url);
|
||||||
}
|
}
|
||||||
|
checkStatus(details);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHeadersReceived(details) {
|
function onHeadersReceived(details) {
|
||||||
|
@ -34,6 +34,7 @@ DIRS += [
|
|||||||
'gfx',
|
'gfx',
|
||||||
'jsdownloads',
|
'jsdownloads',
|
||||||
'lz4',
|
'lz4',
|
||||||
|
'narrate',
|
||||||
'mediasniffer',
|
'mediasniffer',
|
||||||
'microformats',
|
'microformats',
|
||||||
'osfile',
|
'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, "Rect", "resource://gre/modules/Geometry.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
|
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.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");
|
var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
|
||||||
|
|
||||||
@ -102,6 +103,10 @@ var AboutReader = function(mm, win, articlePromise) {
|
|||||||
|
|
||||||
this._setupFontSizeButtons();
|
this._setupFontSizeButtons();
|
||||||
|
|
||||||
|
if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
|
||||||
|
new NarrateControls(mm, win);
|
||||||
|
}
|
||||||
|
|
||||||
this._loadArticle();
|
this._loadArticle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +173,7 @@ AboutReader.prototype = {
|
|||||||
// Triggered by Android user pressing BACK while the banner font-dropdown is open.
|
// Triggered by Android user pressing BACK while the banner font-dropdown is open.
|
||||||
case "Reader:CloseDropdown": {
|
case "Reader:CloseDropdown": {
|
||||||
// Just close it.
|
// Just close it.
|
||||||
this._closeDropdown();
|
this._closeDropdowns();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,19 +214,27 @@ AboutReader.prototype = {
|
|||||||
switch (aEvent.type) {
|
switch (aEvent.type) {
|
||||||
case "click":
|
case "click":
|
||||||
let target = aEvent.target;
|
let target = aEvent.target;
|
||||||
while (target && target.id != "reader-popup")
|
if (target.classList.contains('dropdown-toggle')) {
|
||||||
target = target.parentNode;
|
this._toggleDropdownClicked(aEvent);
|
||||||
if (!target)
|
} else if (!target.closest('.dropdown-popup')) {
|
||||||
this._closeDropdown();
|
this._closeDropdowns();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "scroll":
|
case "scroll":
|
||||||
this._closeDropdown();
|
this._closeDropdowns();
|
||||||
let isScrollingUp = this._scrollOffset > aEvent.pageY;
|
let isScrollingUp = this._scrollOffset > aEvent.pageY;
|
||||||
this._setSystemUIVisibility(isScrollingUp);
|
this._setSystemUIVisibility(isScrollingUp);
|
||||||
this._scrollOffset = aEvent.pageY;
|
this._scrollOffset = aEvent.pageY;
|
||||||
break;
|
break;
|
||||||
case "resize":
|
case "resize":
|
||||||
this._updateImageMargins();
|
this._updateImageMargins();
|
||||||
|
if (this._isToolbarVertical) {
|
||||||
|
this._win.setTimeout(() => {
|
||||||
|
for (let dropdown of this._doc.querySelectorAll('.dropdown.open')) {
|
||||||
|
this._updatePopupPosition(dropdown);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "devicelight":
|
case "devicelight":
|
||||||
@ -234,7 +247,7 @@ AboutReader.prototype = {
|
|||||||
|
|
||||||
case "unload":
|
case "unload":
|
||||||
// Close the Banners Font-dropdown, cleanup Android BackPressListener.
|
// Close the Banners Font-dropdown, cleanup Android BackPressListener.
|
||||||
this._closeDropdown();
|
this._closeDropdowns();
|
||||||
|
|
||||||
this._mm.removeMessageListener("Reader:CloseDropdown", this);
|
this._mm.removeMessageListener("Reader:CloseDropdown", this);
|
||||||
this._mm.removeMessageListener("Reader:AddButton", this);
|
this._mm.removeMessageListener("Reader:AddButton", this);
|
||||||
@ -603,7 +616,7 @@ AboutReader.prototype = {
|
|||||||
this._requestFavicon();
|
this._requestFavicon();
|
||||||
this._doc.body.classList.add("loaded");
|
this._doc.body.classList.add("loaded");
|
||||||
|
|
||||||
Services.obs.notifyObservers(null, "AboutReader:Ready", "");
|
Services.obs.notifyObservers(this._win, "AboutReader:Ready", "");
|
||||||
},
|
},
|
||||||
|
|
||||||
_hideContent: function() {
|
_hideContent: function() {
|
||||||
@ -718,75 +731,66 @@ AboutReader.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_setupStyleDropdown: function() {
|
_setupStyleDropdown: function() {
|
||||||
let doc = this._doc;
|
let dropdownToggle = this._doc.querySelector("#style-dropdown .dropdown-toggle");
|
||||||
let win = this._win;
|
dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
|
||||||
|
},
|
||||||
|
|
||||||
let dropdown = doc.getElementById("style-dropdown");
|
_updatePopupPosition: function(dropdown) {
|
||||||
let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
|
let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
|
||||||
let dropdownPopup = dropdown.querySelector(".dropdown-popup");
|
let dropdownPopup = dropdown.querySelector(".dropdown-popup");
|
||||||
|
|
||||||
// Helper function used to position the popup on desktop,
|
let toggleHeight = dropdownToggle.offsetHeight;
|
||||||
// where there is a vertical toolbar.
|
let toggleTop = dropdownToggle.offsetTop;
|
||||||
function updatePopupPosition() {
|
let popupTop = toggleTop - toggleHeight / 2;
|
||||||
let toggleHeight = dropdownToggle.offsetHeight;
|
|
||||||
let toggleTop = dropdownToggle.offsetTop;
|
|
||||||
let popupTop = toggleTop - toggleHeight / 2;
|
|
||||||
dropdownPopup.style.top = popupTop + "px";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._isToolbarVertical) {
|
dropdownPopup.style.top = popupTop + "px";
|
||||||
win.addEventListener("resize", event => {
|
},
|
||||||
if (!event.isTrusted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Wait for reflow before calculating the new position of the popup.
|
_toggleDropdownClicked: function(event) {
|
||||||
win.setTimeout(updatePopupPosition, 0);
|
let dropdown = event.target.closest('.dropdown');
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
|
if (!dropdown)
|
||||||
dropdownToggle.addEventListener("click", event => {
|
return;
|
||||||
if (!event.isTrusted)
|
|
||||||
return;
|
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (dropdown.classList.contains("open")) {
|
if (dropdown.classList.contains("open")) {
|
||||||
this._closeDropdown();
|
this._closeDropdowns();
|
||||||
} else {
|
} else {
|
||||||
this._openDropdown();
|
this._openDropdown(dropdown);
|
||||||
if (this._isToolbarVertical) {
|
if (this._isToolbarVertical) {
|
||||||
updatePopupPosition();
|
this._updatePopupPosition(dropdown);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, true);
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the ReaderView banner font-dropdown is closed, open it.
|
* If the ReaderView banner font-dropdown is closed, open it.
|
||||||
*/
|
*/
|
||||||
_openDropdown: function() {
|
_openDropdown: function(dropdown) {
|
||||||
let dropdown = this._doc.getElementById("style-dropdown");
|
|
||||||
if (dropdown.classList.contains("open")) {
|
if (dropdown.classList.contains("open")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._closeDropdowns();
|
||||||
|
|
||||||
// Trigger BackPressListener initialization in Android.
|
// Trigger BackPressListener initialization in Android.
|
||||||
dropdown.classList.add("open");
|
dropdown.classList.add("open");
|
||||||
this._mm.sendAsyncMessage("Reader:DropdownOpened", this.viewId);
|
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() {
|
_closeDropdowns: function() {
|
||||||
let dropdown = this._doc.getElementById("style-dropdown");
|
let openDropdowns = this._doc.querySelectorAll(".dropdown.open:not(.keep-open)");
|
||||||
if (!dropdown.classList.contains("open")) {
|
for (let dropdown of openDropdowns) {
|
||||||
return;
|
dropdown.classList.remove("open");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger BackPressListener cleanup in Android.
|
// Trigger BackPressListener cleanup in Android.
|
||||||
dropdown.classList.remove("open");
|
if (openDropdowns.length) {
|
||||||
this._mm.sendAsyncMessage("Reader:DropdownClosed", this.viewId);
|
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/keys.properties (%chrome/global/keys.properties)
|
||||||
locale/@AB_CD@/global/languageNames.properties (%chrome/global/languageNames.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/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/notification.dtd (%chrome/global/notification.dtd)
|
||||||
locale/@AB_CD@/global/preferences.dtd (%chrome/global/preferences.dtd)
|
locale/@AB_CD@/global/preferences.dtd (%chrome/global/preferences.dtd)
|
||||||
locale/@AB_CD@/global/printdialog.dtd (%chrome/global/printdialog.dtd)
|
locale/@AB_CD@/global/printdialog.dtd (%chrome/global/printdialog.dtd)
|
||||||
|
@ -87,6 +87,20 @@ function parseExtra(extra, allowed) {
|
|||||||
return result;
|
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 HttpObserverManager;
|
||||||
|
|
||||||
var ContentPolicyManager = {
|
var ContentPolicyManager = {
|
||||||
@ -404,7 +418,7 @@ HttpObserverManager = {
|
|||||||
let responseHeaderNames;
|
let responseHeaderNames;
|
||||||
|
|
||||||
let includeStatus = kind === "headersReceived" ||
|
let includeStatus = kind === "headersReceived" ||
|
||||||
kind === "onBeforeRedirect" ||
|
kind === "onRedirect" ||
|
||||||
kind === "onStart" ||
|
kind === "onStart" ||
|
||||||
kind === "onStop";
|
kind === "onStop";
|
||||||
|
|
||||||
@ -443,7 +457,7 @@ HttpObserverManager = {
|
|||||||
responseHeaderNames = data.responseHeaders.map(h => h.name);
|
responseHeaderNames = data.responseHeaders.map(h => h.name);
|
||||||
}
|
}
|
||||||
if (includeStatus) {
|
if (includeStatus) {
|
||||||
data.statusCode = channel.responseStatus;
|
mergeStatus(data, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = null;
|
let result = null;
|
||||||
|
@ -55,7 +55,9 @@ ol,
|
|||||||
li,
|
li,
|
||||||
figure,
|
figure,
|
||||||
.wp-caption {
|
.wp-caption {
|
||||||
margin: 0 0 30px 0;
|
margin: -10px -10px 20px -10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p > img:only-child,
|
p > img:only-child,
|
||||||
|
@ -120,7 +120,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*======= Font style popup =======*/
|
/*======= Popup =======*/
|
||||||
|
|
||||||
.dropdown-popup {
|
.dropdown-popup {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
@ -136,6 +136,10 @@
|
|||||||
box-shadow: 0 1px 12px #666;
|
box-shadow: 0 1px 12px #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.keep-open .dropdown-popup {
|
||||||
|
z-index: initial;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-popup > hr {
|
.dropdown-popup > hr {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -154,6 +158,8 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*======= Font style popup =======*/
|
||||||
|
|
||||||
#font-type-buttons,
|
#font-type-buttons,
|
||||||
#font-size-buttons,
|
#font-size-buttons,
|
||||||
#color-scheme-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/loading-inverted@2x.png (../../shared/icons/loading-inverted@2x.png)
|
||||||
skin/classic/global/icons/warning.svg (../../shared/incontent-icons/warning.svg)
|
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/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@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.png (../../shared/menu-check.png)
|
||||||
skin/classic/global/menu/shared-menu-check-active.svg (../../shared/menu-check-active.svg)
|
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);
|
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"
|
* This predicate can be used to determine if the platform is a "low-memory"
|
||||||
* platform. Callers may use this to dynamically tune their behaviour
|
* 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);
|
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
|
NS_IMETHODIMP
|
||||||
nsMemoryImpl::IsLowMemoryPlatform(bool* aResult)
|
nsMemoryImpl::IsLowMemoryPlatform(bool* aResult)
|
||||||
{
|
{
|
||||||
|