зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
Коммит
1698ad0344
|
@ -3,14 +3,8 @@ prefs =
|
|||
browser.policies.enabled=true
|
||||
support-files =
|
||||
head.js
|
||||
config_dont_check_default_browser.json
|
||||
config_popups_cookies_addons_flash.json
|
||||
config_setAndLockPref.json
|
||||
config_simple_policies.json
|
||||
config_broken_json.json
|
||||
config_display_menu.json
|
||||
config_display_bookmarks.json
|
||||
config_block_set_desktop_background.json
|
||||
|
||||
[browser_policies_broken_json.js]
|
||||
[browser_policies_popups_cookies_addons_flash.js]
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
@ -95,8 +94,16 @@ add_task(async function test_API_through_policies() {
|
|||
};
|
||||
|
||||
await setupPolicyEngineWithJson(
|
||||
"config_setAndLockPref.json",
|
||||
/* custom schema */
|
||||
// policies.json
|
||||
{
|
||||
"policies": {
|
||||
"bool_policy": true,
|
||||
"int_policy": 42,
|
||||
"string_policy": "policies test 2"
|
||||
}
|
||||
},
|
||||
|
||||
// custom schema
|
||||
{
|
||||
properties: {
|
||||
"bool_policy": {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
@ -52,8 +51,17 @@ add_task(async function test_simple_policies() {
|
|||
};
|
||||
|
||||
await setupPolicyEngineWithJson(
|
||||
"config_simple_policies.json",
|
||||
/* custom schema */
|
||||
// policies.json
|
||||
{
|
||||
"policies": {
|
||||
"simple_policy0": true,
|
||||
"simple_policy1": true,
|
||||
"simple_policy2": true,
|
||||
"simple_policy3": false
|
||||
}
|
||||
},
|
||||
|
||||
// custom schema
|
||||
{
|
||||
properties: {
|
||||
"simple_policy0": {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
"use strict";
|
||||
|
||||
add_task(async function setup() {
|
||||
await setupPolicyEngineWithJson("config_block_set_desktop_background.json");
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"block_set_desktop_background": true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_check_set_desktop_background() {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
const { ShellService } = ChromeUtils.import("resource:///modules/ShellService.jsm", {});
|
||||
|
@ -13,7 +12,11 @@ add_task(async function test_default_browser_check() {
|
|||
ShellService.shouldCheckDefaultBrowser = true;
|
||||
is(ShellService.shouldCheckDefaultBrowser, true, "Sanity check");
|
||||
|
||||
await setupPolicyEngineWithJson("config_dont_check_default_browser.json");
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"dont_check_default_browser": true
|
||||
}
|
||||
});
|
||||
|
||||
is(ShellService.shouldCheckDefaultBrowser, false, "Policy changed it to not check");
|
||||
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
"use strict";
|
||||
|
||||
add_task(async function setup() {
|
||||
await setupPolicyEngineWithJson("config_display_bookmarks.json");
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"display_bookmarks_toolbar": true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_menu_shown() {
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
"use strict";
|
||||
|
||||
add_task(async function setup() {
|
||||
await setupPolicyEngineWithJson("config_display_menu.json");
|
||||
await setupPolicyEngineWithJson({
|
||||
"policies": {
|
||||
"display_menu_bar": true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_menu_shown() {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"policies": {
|
||||
"block_set_desktop_background": true
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"policies": {
|
||||
"display_bookmarks_toolbar": true
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"policies": {
|
||||
"display_menu_bar": true
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"policies": {
|
||||
"dont_check_default_browser": true
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"policies": {
|
||||
"bool_policy": true,
|
||||
"int_policy": 42,
|
||||
"string_policy": "policies test 2"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"policies": {
|
||||
"simple_policy0": true,
|
||||
"simple_policy1": true,
|
||||
"simple_policy2": true,
|
||||
"simple_policy3": false
|
||||
}
|
||||
}
|
|
@ -4,8 +4,23 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
async function setupPolicyEngineWithJson(jsonName, customSchema) {
|
||||
let filePath = getTestFilePath(jsonName ? jsonName : "non-existing-file.json");
|
||||
ChromeUtils.defineModuleGetter(this, "FileTestUtils",
|
||||
"resource://testing-common/FileTestUtils.jsm");
|
||||
|
||||
async function setupPolicyEngineWithJson(json, customSchema) {
|
||||
let filePath;
|
||||
if (typeof(json) == "object") {
|
||||
filePath = FileTestUtils.getTempFile("policies.json").path;
|
||||
|
||||
// This file gets automatically deleted by FileTestUtils
|
||||
// at the end of the test run.
|
||||
await OS.File.writeAtomic(filePath, JSON.stringify(json), {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
} else {
|
||||
filePath = getTestFilePath(json ? json : "non-existing-file.json");
|
||||
}
|
||||
|
||||
Services.prefs.setStringPref("browser.policies.alternatePath", filePath);
|
||||
|
||||
let resolve = null;
|
||||
|
|
|
@ -159,7 +159,7 @@
|
|||
|
||||
.ruleview-namecontainer {
|
||||
cursor: text;
|
||||
padding-left: 3px;
|
||||
margin-left: -25px;
|
||||
}
|
||||
|
||||
.ruleview-propertyvaluecontainer {
|
||||
|
@ -251,11 +251,6 @@
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
:root[platform="win"] .ruleview-header,
|
||||
:root[platform="linux"] .ruleview-header {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.ruleview-expandable-header {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -264,7 +259,6 @@
|
|||
background-color: var(--theme-toolbar-background-hover);
|
||||
}
|
||||
|
||||
|
||||
.ruleview-rule-pseudo-element {
|
||||
padding-left:20px;
|
||||
border-left: solid 10px;
|
||||
|
@ -384,6 +378,12 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.ruleview-enableproperty {
|
||||
position: relative;
|
||||
float: left;
|
||||
left: -28px;
|
||||
}
|
||||
|
||||
.ruleview-rule:not(:hover) .ruleview-enableproperty {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
@ -403,7 +403,7 @@
|
|||
|
||||
.ruleview-newproperty {
|
||||
/* (enable checkbox width: 12px) + (expander width: 15px) */
|
||||
margin-inline-start: 27px;
|
||||
margin-inline-start: -10px;
|
||||
}
|
||||
|
||||
.ruleview-namecontainer,
|
||||
|
@ -418,12 +418,8 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.ruleview-computed {
|
||||
margin-inline-start: 26px;
|
||||
}
|
||||
|
||||
.ruleview-overridden-items {
|
||||
margin: 0px 0px 0px 2px;
|
||||
margin-inline-start: -25px;
|
||||
list-style: none;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
@ -563,6 +559,7 @@
|
|||
.ruleview-property {
|
||||
border-left: 3px solid transparent;
|
||||
clear: right;
|
||||
padding-left: 28px;
|
||||
}
|
||||
|
||||
.ruleview-propertycontainer > * {
|
||||
|
|
|
@ -548,4 +548,10 @@ MediaRawDataWriter::Size()
|
|||
return mTarget->Size();
|
||||
}
|
||||
|
||||
void
|
||||
MediaRawDataWriter::PopFront(size_t aSize)
|
||||
{
|
||||
mTarget->mBuffer.PopFront(aSize);
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -210,6 +210,13 @@ public:
|
|||
return AlignmentOffset() * 2;
|
||||
}
|
||||
|
||||
void PopFront(size_t aSize)
|
||||
{
|
||||
MOZ_ASSERT(mLength >= aSize);
|
||||
PodMove(mData, mData + aSize, mLength - aSize);
|
||||
mLength -= aSize;
|
||||
}
|
||||
|
||||
private:
|
||||
static size_t AlignmentOffset()
|
||||
{
|
||||
|
@ -637,6 +644,8 @@ public:
|
|||
bool Replace(const uint8_t* aData, size_t aSize);
|
||||
// Clear the memory buffer. Will set target mData and mSize to 0.
|
||||
void Clear();
|
||||
// Remove aSize bytes from the front of the sample.
|
||||
void PopFront(size_t aSize);
|
||||
|
||||
private:
|
||||
friend class MediaRawData;
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
namespace mozilla
|
||||
{
|
||||
|
||||
static const int kADTSHeaderSize = 7;
|
||||
|
||||
int8_t
|
||||
Adts::GetFrequencyIndex(uint32_t aSamplesPerSecond)
|
||||
{
|
||||
|
@ -31,11 +33,11 @@ Adts::GetFrequencyIndex(uint32_t aSamplesPerSecond)
|
|||
}
|
||||
|
||||
bool
|
||||
Adts::ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex,
|
||||
int8_t aProfile, MediaRawData* aSample)
|
||||
Adts::ConvertSample(uint16_t aChannelCount,
|
||||
int8_t aFrequencyIndex,
|
||||
int8_t aProfile,
|
||||
MediaRawData* aSample)
|
||||
{
|
||||
static const int kADTSHeaderSize = 7;
|
||||
|
||||
size_t newSize = aSample->Size() + kADTSHeaderSize;
|
||||
|
||||
// ADTS header uses 13 bits for packet size.
|
||||
|
@ -70,4 +72,32 @@ Adts::ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex,
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Adts::RevertSample(MediaRawData* aSample)
|
||||
{
|
||||
if (aSample->Size() < kADTSHeaderSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
const uint8_t* header = aSample->Data();
|
||||
if (header[0] != 0xff || header[1] != 0xf1 || header[6] != 0xfc) {
|
||||
// Not ADTS.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoPtr<MediaRawDataWriter> writer(aSample->CreateWriter());
|
||||
writer->PopFront(kADTSHeaderSize);
|
||||
|
||||
if (aSample->mCrypto.mValid) {
|
||||
if (aSample->mCrypto.mPlainSizes.Length() > 0 &&
|
||||
writer->mCrypto.mPlainSizes[0] >= kADTSHeaderSize) {
|
||||
writer->mCrypto.mPlainSizes[0] -= kADTSHeaderSize;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ public:
|
|||
static int8_t GetFrequencyIndex(uint32_t aSamplesPerSecond);
|
||||
static bool ConvertSample(uint16_t aChannelCount, int8_t aFrequencyIndex,
|
||||
int8_t aProfile, mozilla::MediaRawData* aSample);
|
||||
static bool RevertSample(MediaRawData* aSample);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "EMEDecoderModule.h"
|
||||
#include "Adts.h"
|
||||
#include "GMPDecoderModule.h"
|
||||
#include "GMPService.h"
|
||||
#include "MediaInfo.h"
|
||||
|
@ -13,12 +14,14 @@
|
|||
#include "mozIGeckoMediaPluginService.h"
|
||||
#include "mozilla/CDMProxy.h"
|
||||
#include "mozilla/EMEUtils.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsClassHashtable.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "DecryptThroughputLimit.h"
|
||||
#include "ChromiumCDMVideoDecoder.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -27,20 +30,52 @@ extern already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule();
|
|||
|
||||
DDLoggedTypeDeclNameAndBase(EMEDecryptor, MediaDataDecoder);
|
||||
|
||||
class ADTSSampleConverter
|
||||
{
|
||||
public:
|
||||
explicit ADTSSampleConverter(const AudioInfo& aInfo)
|
||||
: mNumChannels(aInfo.mChannels)
|
||||
// Note: we clamp profile to 4 so that HE-AACv2 (profile 5) can pass
|
||||
// through the conversion to ADTS and back again.
|
||||
, mProfile(
|
||||
std::min<uint8_t>(static_cast<uint8_t>(0xff & aInfo.mProfile), 4))
|
||||
, mFrequencyIndex(Adts::GetFrequencyIndex(aInfo.mRate))
|
||||
{
|
||||
}
|
||||
bool Convert(MediaRawData* aSample) const
|
||||
{
|
||||
return Adts::ConvertSample(
|
||||
mNumChannels, mFrequencyIndex, mProfile, aSample);
|
||||
}
|
||||
bool Revert(MediaRawData* aSample) const
|
||||
{
|
||||
return Adts::RevertSample(aSample);
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t mNumChannels;
|
||||
const uint8_t mProfile;
|
||||
const uint8_t mFrequencyIndex;
|
||||
};
|
||||
|
||||
class EMEDecryptor
|
||||
: public MediaDataDecoder
|
||||
, public DecoderDoctorLifeLogger<EMEDecryptor>
|
||||
{
|
||||
public:
|
||||
EMEDecryptor(MediaDataDecoder* aDecoder, CDMProxy* aProxy,
|
||||
TaskQueue* aDecodeTaskQueue, TrackInfo::TrackType aType,
|
||||
MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey)
|
||||
EMEDecryptor(MediaDataDecoder* aDecoder,
|
||||
CDMProxy* aProxy,
|
||||
TaskQueue* aDecodeTaskQueue,
|
||||
TrackInfo::TrackType aType,
|
||||
MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey,
|
||||
UniquePtr<ADTSSampleConverter> aConverter = nullptr)
|
||||
: mDecoder(aDecoder)
|
||||
, mTaskQueue(aDecodeTaskQueue)
|
||||
, mProxy(aProxy)
|
||||
, mSamplesWaitingForKey(
|
||||
new SamplesWaitingForKey(mProxy, aType, aOnWaitingForKey))
|
||||
, mThroughputLimiter(aDecodeTaskQueue)
|
||||
, mADTSSampleConverter(Move(aConverter))
|
||||
, mIsShutdown(false)
|
||||
{
|
||||
DDLINKCHILD("decoder", mDecoder.get());
|
||||
|
@ -98,6 +133,15 @@ public:
|
|||
return;
|
||||
}
|
||||
|
||||
if (mADTSSampleConverter && !mADTSSampleConverter->Convert(aSample)) {
|
||||
mDecodePromise.RejectIfExists(
|
||||
MediaResult(
|
||||
NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
RESULT_DETAIL("Failed to convert encrypted AAC sample to ADTS")),
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
mDecrypts.Put(aSample, new DecryptPromiseRequestHolder());
|
||||
mProxy->Decrypt(aSample)
|
||||
->Then(mTaskQueue, __func__, this,
|
||||
|
@ -121,6 +165,16 @@ public:
|
|||
return;
|
||||
}
|
||||
|
||||
if (mADTSSampleConverter &&
|
||||
!mADTSSampleConverter->Revert(aDecrypted.mSample)) {
|
||||
mDecodePromise.RejectIfExists(
|
||||
MediaResult(
|
||||
NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
RESULT_DETAIL("Failed to revert decrypted ADTS sample to AAC")),
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsShutdown) {
|
||||
NS_WARNING("EME decrypted sample arrived after shutdown");
|
||||
return;
|
||||
|
@ -229,7 +283,7 @@ private:
|
|||
MozPromiseHolder<DecodePromise> mDrainPromise;
|
||||
MozPromiseHolder<FlushPromise> mFlushPromise;
|
||||
MozPromiseRequestHolder<DecodePromise> mDecodeRequest;
|
||||
|
||||
UniquePtr<ADTSSampleConverter> mADTSSampleConverter;
|
||||
bool mIsShutdown;
|
||||
};
|
||||
|
||||
|
@ -384,14 +438,25 @@ EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
|
|||
return m->CreateAudioDecoder(aParams);
|
||||
}
|
||||
|
||||
UniquePtr<ADTSSampleConverter> converter = nullptr;
|
||||
if (MP4Decoder::IsAAC(aParams.mConfig.mMimeType)) {
|
||||
// The CDM expects encrypted AAC to be in ADTS format.
|
||||
// See bug 1433344.
|
||||
converter = MakeUnique<ADTSSampleConverter>(aParams.AudioConfig());
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
|
||||
if (!decoder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(
|
||||
decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue(),
|
||||
aParams.mType, aParams.mOnWaitingForKeyEvent));
|
||||
RefPtr<MediaDataDecoder> emeDecoder(
|
||||
new EMEDecryptor(decoder,
|
||||
mProxy,
|
||||
AbstractThread::GetCurrent()->AsTaskQueue(),
|
||||
aParams.mType,
|
||||
aParams.mOnWaitingForKeyEvent,
|
||||
Move(converter)));
|
||||
return emeDecoder.forget();
|
||||
}
|
||||
|
||||
|
|
|
@ -1269,6 +1269,12 @@ protected:
|
|||
// Note: p is also non-null... its just hard to assert that.
|
||||
MOZ_ASSERT(bmhi && monitor && ok && winErr && r);
|
||||
*ok = bmhi->BrokerCallClient(*winErr, *r, *p...);
|
||||
{
|
||||
// By grabbing (and freeing) the lock, we make sure that Wait() has been
|
||||
// called in PostToDispatchThread. We need that since we wake it with
|
||||
// Notify().
|
||||
MonitorAutoLock lock(*monitor);
|
||||
}
|
||||
*ok &= NS_SUCCEEDED(monitor->Notify());
|
||||
};
|
||||
|
||||
|
|
|
@ -647,6 +647,7 @@ PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath,
|
|||
, mFlashProcess2(0)
|
||||
, mFinishInitTask(nullptr)
|
||||
#endif
|
||||
, mIsCleaningFromTimeout(false)
|
||||
{
|
||||
NS_ASSERTION(mSubprocess, "Out of memory!");
|
||||
mSandboxLevel = aSandboxLevel;
|
||||
|
@ -804,6 +805,15 @@ PluginModuleChromeParent::CleanupFromTimeout(const bool aFromHangUI)
|
|||
return;
|
||||
}
|
||||
|
||||
// Avoid recursively calling this method. MessageChannel::Close() can
|
||||
// cause this task to be re-launched.
|
||||
if (mIsCleaningFromTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutoRestore<bool> resetCleaningFlag(mIsCleaningFromTimeout);
|
||||
mIsCleaningFromTimeout = true;
|
||||
|
||||
/* If the plugin container was terminated by the Plugin Hang UI,
|
||||
then either the I/O thread detects a channel error, or the
|
||||
main thread must set the error (whomever gets there first).
|
||||
|
|
|
@ -605,6 +605,8 @@ private:
|
|||
TakeFullMinidumpCallback mTakeFullMinidumpCallback;
|
||||
|
||||
TerminateChildProcessCallback mTerminateChildProcessCallback;
|
||||
|
||||
bool mIsCleaningFromTimeout;
|
||||
};
|
||||
|
||||
} // namespace plugins
|
||||
|
|
|
@ -122,6 +122,7 @@ static const uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024;
|
|||
// Actually, 4kb should be enough for everyone.
|
||||
static const uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024;
|
||||
|
||||
// Keep this in sync with PrefType in parser/src/lib.rs.
|
||||
enum class PrefType : uint8_t
|
||||
{
|
||||
None = 0, // only used when neither the default nor user value is set
|
||||
|
@ -130,6 +131,7 @@ enum class PrefType : uint8_t
|
|||
Bool = 3,
|
||||
};
|
||||
|
||||
// Keep this in sync with PrefValue in prefs_parser/src/lib.rs.
|
||||
union PrefValue {
|
||||
const char* mStringVal;
|
||||
int32_t mIntVal;
|
||||
|
@ -929,633 +931,126 @@ struct TelemetryLoadData
|
|||
|
||||
static nsDataHashtable<nsCStringHashKey, TelemetryLoadData>* gTelemetryLoadData;
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Keep this in sync with PrefFn in prefs_parser/src/lib.rs.
|
||||
typedef void (*PrefsParserPrefFn)(const char* aPrefName,
|
||||
PrefType aType,
|
||||
PrefValueKind aKind,
|
||||
PrefValue aValue,
|
||||
bool aIsSticky);
|
||||
|
||||
// Keep this in sync with ErrorFn in prefs_parser/src/lib.rs.
|
||||
//
|
||||
// `aMsg` is just a borrow of the string, and must be copied if it is used
|
||||
// outside the lifetime of the prefs_parser_parse() call.
|
||||
typedef void (*PrefsParserErrorFn)(const char* aMsg);
|
||||
|
||||
// Keep this in sync with prefs_parser_parse() in prefs_parser/src/lib.rs.
|
||||
bool
|
||||
prefs_parser_parse(const char* aPath,
|
||||
const char* aBuf,
|
||||
size_t aLen,
|
||||
PrefsParserPrefFn aPrefFn,
|
||||
PrefsParserErrorFn aErrorFn);
|
||||
}
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
Parser()
|
||||
: mState()
|
||||
, mNextState()
|
||||
, mStrMatch()
|
||||
, mStrIndex()
|
||||
, mUtf16()
|
||||
, mEscLen()
|
||||
, mEscTmp()
|
||||
, mQuoteChar()
|
||||
, mLb()
|
||||
, mLbCur()
|
||||
, mLbEnd()
|
||||
, mVb()
|
||||
, mVtype()
|
||||
, mIsDefault()
|
||||
, mIsSticky()
|
||||
{
|
||||
}
|
||||
|
||||
~Parser() { free(mLb); }
|
||||
Parser() = default;
|
||||
~Parser() = default;
|
||||
|
||||
bool Parse(const nsCString& aName,
|
||||
const char* aPath,
|
||||
const TimeStamp& aStartTime,
|
||||
const char* aBuf,
|
||||
size_t aBufLen);
|
||||
|
||||
bool GrowBuf();
|
||||
|
||||
void HandleValue(const char* aPrefName,
|
||||
PrefType aType,
|
||||
PrefValue aValue,
|
||||
bool aIsDefault,
|
||||
bool aIsSticky);
|
||||
|
||||
void ReportProblem(const char* aMessage, int aLine, bool aError);
|
||||
|
||||
private:
|
||||
// Pref parser states.
|
||||
enum class State
|
||||
const nsCString& aBuf)
|
||||
{
|
||||
eInit,
|
||||
eMatchString,
|
||||
eUntilName,
|
||||
eQuotedString,
|
||||
eUntilComma,
|
||||
eUntilValue,
|
||||
eIntValue,
|
||||
eCommentMaybeStart,
|
||||
eCommentBlock,
|
||||
eCommentBlockMaybeEnd,
|
||||
eEscapeSequence,
|
||||
eHexEscape,
|
||||
eUTF16LowSurrogate,
|
||||
eUntilOpenParen,
|
||||
eUntilCloseParen,
|
||||
eUntilSemicolon,
|
||||
eUntilEOL
|
||||
};
|
||||
|
||||
static const int kUTF16EscapeNumDigits = 4;
|
||||
static const int kHexEscapeNumDigits = 2;
|
||||
static const int KBitsPerHexDigit = 4;
|
||||
|
||||
static constexpr const char* kUserPref = "user_pref";
|
||||
static constexpr const char* kPref = "pref";
|
||||
static constexpr const char* kStickyPref = "sticky_pref";
|
||||
static constexpr const char* kTrue = "true";
|
||||
static constexpr const char* kFalse = "false";
|
||||
|
||||
State mState; // current parse state
|
||||
State mNextState; // sometimes used...
|
||||
const char* mStrMatch; // string to match
|
||||
int mStrIndex; // next char of smatch to check;
|
||||
// also, counter in \u parsing
|
||||
char16_t mUtf16[2]; // parsing UTF16 (\u) escape
|
||||
int mEscLen; // length in mEscTmp
|
||||
char mEscTmp[6]; // raw escape to put back if err
|
||||
char mQuoteChar; // char delimiter for quotations
|
||||
char* mLb; // line buffer (only allocation)
|
||||
char* mLbCur; // line buffer cursor
|
||||
char* mLbEnd; // line buffer end
|
||||
char* mVb; // value buffer (ptr into mLb)
|
||||
Maybe<PrefType> mVtype; // pref value type
|
||||
bool mIsDefault; // true if (default) pref
|
||||
bool mIsSticky; // true if (sticky) pref
|
||||
};
|
||||
|
||||
// This function will increase the size of the buffer owned by the given pref
|
||||
// parse state. We currently use a simple doubling algorithm, but the only hard
|
||||
// requirement is that it increase the buffer by at least the size of the
|
||||
// mEscTmp buffer used for escape processing (currently 6 bytes).
|
||||
//
|
||||
// The buffer is used to store partial pref lines. It is freed when the parse
|
||||
// state is destroyed.
|
||||
//
|
||||
// This function updates all pointers that reference an address within mLb
|
||||
// since realloc may relocate the buffer.
|
||||
//
|
||||
// Returns false on failure.
|
||||
bool
|
||||
Parser::GrowBuf()
|
||||
{
|
||||
int bufLen, curPos, valPos;
|
||||
|
||||
bufLen = mLbEnd - mLb;
|
||||
curPos = mLbCur - mLb;
|
||||
valPos = mVb - mLb;
|
||||
|
||||
if (bufLen == 0) {
|
||||
bufLen = 128; // default buffer size
|
||||
} else {
|
||||
bufLen <<= 1; // double buffer size
|
||||
}
|
||||
|
||||
mLb = (char*)realloc(mLb, bufLen);
|
||||
if (!mLb) {
|
||||
sNumPrefs = 0;
|
||||
bool ok = prefs_parser_parse(
|
||||
aPath, aBuf.get(), aBuf.Length(), HandlePref, HandleError);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mLbCur = mLb + curPos;
|
||||
mLbEnd = mLb + bufLen;
|
||||
mVb = mLb + valPos;
|
||||
uint32_t loadTime_us = (TimeStamp::Now() - aStartTime).ToMicroseconds();
|
||||
|
||||
// Most prefs files are read before telemetry initializes, so we have to
|
||||
// save these measurements now and send them to telemetry later.
|
||||
TelemetryLoadData loadData = { uint32_t(aBuf.Length()),
|
||||
sNumPrefs,
|
||||
loadTime_us };
|
||||
gTelemetryLoadData->Put(aName, loadData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
Parser::HandleValue(const char* aPrefName,
|
||||
private:
|
||||
static void HandlePref(const char* aPrefName,
|
||||
PrefType aType,
|
||||
PrefValueKind aKind,
|
||||
PrefValue aValue,
|
||||
bool aIsDefault,
|
||||
bool aIsSticky)
|
||||
{
|
||||
PrefValueKind kind =
|
||||
aIsDefault ? PrefValueKind::Default : PrefValueKind::User;
|
||||
pref_SetPref(aPrefName, aType, kind, aValue, aIsSticky, /* fromFile */ true);
|
||||
sNumPrefs++;
|
||||
pref_SetPref(
|
||||
aPrefName, aType, aKind, aValue, aIsSticky, /* fromFile */ true);
|
||||
}
|
||||
|
||||
// Report an error or a warning. If not specified, just dump to stderr.
|
||||
void
|
||||
Parser::ReportProblem(const char* aMessage, int aLine, bool aError)
|
||||
static void HandleError(const char* aMsg)
|
||||
{
|
||||
nsPrintfCString message("** Preference parsing %s (line %d) = %s **\n",
|
||||
(aError ? "error" : "warning"),
|
||||
aLine,
|
||||
aMessage);
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIConsoleService> console =
|
||||
do_GetService("@mozilla.org/consoleservice;1", &rv);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get());
|
||||
console->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get());
|
||||
} else {
|
||||
printf_stderr("%s", message.get());
|
||||
printf_stderr("%s\n", aMsg);
|
||||
}
|
||||
NS_WARNING(aMsg);
|
||||
}
|
||||
|
||||
// Parse a buffer containing some portion of a preference file. This function
|
||||
// may be called repeatedly as new data is made available. The PrefReader
|
||||
// callback function passed to Parser's constructor will be called as preference
|
||||
// name value pairs are extracted from the data. Returns false if buffer
|
||||
// contains malformed content.
|
||||
//
|
||||
// Pseudo-BNF
|
||||
// ----------
|
||||
// function = LJUNK function-name JUNK function-args
|
||||
// function-name = "user_pref" | "pref" | "sticky_pref"
|
||||
// function-args = "(" JUNK pref-name JUNK "," JUNK pref-value JUNK ")" JUNK ";"
|
||||
// pref-name = quoted-string
|
||||
// pref-value = quoted-string | "true" | "false" | integer-value
|
||||
// JUNK = *(WS | comment-block | comment-line)
|
||||
// LJUNK = *(WS | comment-block | comment-line | bcomment-line)
|
||||
// WS = SP | HT | LF | VT | FF | CR
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// VT = <US-ASCII HT, vertical-tab (11)>
|
||||
// FF = <US-ASCII FF, form-feed (12)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// comment-block = <C/C++ style comment block>
|
||||
// comment-line = <C++ style comment line>
|
||||
// bcomment-line = <bourne-shell style comment line>
|
||||
//
|
||||
bool
|
||||
Parser::Parse(const nsCString& aName,
|
||||
const TimeStamp& aStartTime,
|
||||
const char* aBuf,
|
||||
size_t aBufLen)
|
||||
// This is static so that HandlePref() can increment it easily. This is ok
|
||||
// because prefs files are read one at a time.
|
||||
static uint32_t sNumPrefs;
|
||||
};
|
||||
|
||||
uint32_t Parser::sNumPrefs = 0;
|
||||
|
||||
// The following code is test code for the gtest.
|
||||
|
||||
static void
|
||||
TestParseErrorHandlePref(const char* aPrefName,
|
||||
PrefType aType,
|
||||
PrefValueKind aKind,
|
||||
PrefValue aValue,
|
||||
bool aIsSticky)
|
||||
{
|
||||
// The line number is currently only used for the error/warning reporting.
|
||||
int lineNum = 0;
|
||||
|
||||
uint32_t numPrefs = 0;
|
||||
|
||||
State state = mState;
|
||||
for (const char* end = aBuf + aBufLen; aBuf != end; ++aBuf) {
|
||||
char c = *aBuf;
|
||||
if (c == '\r' || c == '\n' || c == 0x1A) {
|
||||
lineNum++;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
// initial state
|
||||
case State::eInit:
|
||||
if (mLbCur != mLb) { // reset state
|
||||
mLbCur = mLb;
|
||||
mVb = nullptr;
|
||||
mVtype = Nothing();
|
||||
mIsDefault = false;
|
||||
mIsSticky = false;
|
||||
}
|
||||
switch (c) {
|
||||
case '/': // begin comment block or line?
|
||||
state = State::eCommentMaybeStart;
|
||||
break;
|
||||
case '#': // accept shell style comments
|
||||
state = State::eUntilEOL;
|
||||
break;
|
||||
case 'u': // indicating user_pref
|
||||
case 's': // indicating sticky_pref
|
||||
case 'p': // indicating pref
|
||||
if (c == 'u') {
|
||||
mStrMatch = kUserPref;
|
||||
} else if (c == 's') {
|
||||
mStrMatch = kStickyPref;
|
||||
} else {
|
||||
mStrMatch = kPref;
|
||||
}
|
||||
mStrIndex = 1;
|
||||
mNextState = State::eUntilOpenParen;
|
||||
state = State::eMatchString;
|
||||
break;
|
||||
// else skip char
|
||||
}
|
||||
break;
|
||||
static char* gTestParseErrorMsg;
|
||||
|
||||
// string matching
|
||||
case State::eMatchString:
|
||||
if (c == mStrMatch[mStrIndex++]) {
|
||||
// If we've matched all characters, then move to next state.
|
||||
if (mStrMatch[mStrIndex] == '\0') {
|
||||
state = mNextState;
|
||||
mNextState = State::eInit; // reset next state
|
||||
}
|
||||
// else wait for next char
|
||||
} else {
|
||||
ReportProblem("non-matching string", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
// quoted string parsing
|
||||
case State::eQuotedString:
|
||||
// we assume that the initial quote has already been consumed
|
||||
if (mLbCur == mLbEnd && !GrowBuf()) {
|
||||
return false; // out of memory
|
||||
}
|
||||
if (c == '\\') {
|
||||
state = State::eEscapeSequence;
|
||||
} else if (c == mQuoteChar) {
|
||||
*mLbCur++ = '\0';
|
||||
state = mNextState;
|
||||
mNextState = State::eInit; // reset next state
|
||||
} else {
|
||||
*mLbCur++ = c;
|
||||
}
|
||||
break;
|
||||
|
||||
// name parsing
|
||||
case State::eUntilName:
|
||||
if (c == '\"' || c == '\'') {
|
||||
mIsDefault = (mStrMatch == kPref || mStrMatch == kStickyPref);
|
||||
mIsSticky = (mStrMatch == kStickyPref);
|
||||
mQuoteChar = c;
|
||||
mNextState = State::eUntilComma; // return here when done
|
||||
state = State::eQuotedString;
|
||||
} else if (c == '/') { // allow embedded comment
|
||||
mNextState = state; // return here when done with comment
|
||||
state = State::eCommentMaybeStart;
|
||||
} else if (!isspace(c)) {
|
||||
ReportProblem("need space, comment or quote", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
// parse until we find a comma separating name and value
|
||||
case State::eUntilComma:
|
||||
if (c == ',') {
|
||||
mVb = mLbCur;
|
||||
state = State::eUntilValue;
|
||||
} else if (c == '/') { // allow embedded comment
|
||||
mNextState = state; // return here when done with comment
|
||||
state = State::eCommentMaybeStart;
|
||||
} else if (!isspace(c)) {
|
||||
ReportProblem("need space, comment or comma", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
// value parsing
|
||||
case State::eUntilValue:
|
||||
// The pref value type is unknown. So, we scan for the first character
|
||||
// of the value, and determine the type from that.
|
||||
if (c == '\"' || c == '\'') {
|
||||
mVtype = Some(PrefType::String);
|
||||
mQuoteChar = c;
|
||||
mNextState = State::eUntilCloseParen;
|
||||
state = State::eQuotedString;
|
||||
} else if (c == 't' || c == 'f') {
|
||||
mVb = (char*)(c == 't' ? kTrue : kFalse);
|
||||
mVtype = Some(PrefType::Bool);
|
||||
mStrMatch = mVb;
|
||||
mStrIndex = 1;
|
||||
mNextState = State::eUntilCloseParen;
|
||||
state = State::eMatchString;
|
||||
} else if (isdigit(c) || (c == '-') || (c == '+')) {
|
||||
mVtype = Some(PrefType::Int);
|
||||
// write c to line buffer...
|
||||
if (mLbCur == mLbEnd && !GrowBuf()) {
|
||||
return false; // out of memory
|
||||
}
|
||||
*mLbCur++ = c;
|
||||
state = State::eIntValue;
|
||||
} else if (c == '/') { // allow embedded comment
|
||||
mNextState = state; // return here when done with comment
|
||||
state = State::eCommentMaybeStart;
|
||||
} else if (!isspace(c)) {
|
||||
ReportProblem("need value, comment or space", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::eIntValue:
|
||||
// grow line buffer if necessary...
|
||||
if (mLbCur == mLbEnd && !GrowBuf()) {
|
||||
return false; // out of memory
|
||||
}
|
||||
if (isdigit(c)) {
|
||||
*mLbCur++ = c;
|
||||
} else {
|
||||
*mLbCur++ = '\0'; // stomp null terminator; we are done.
|
||||
if (c == ')') {
|
||||
state = State::eUntilSemicolon;
|
||||
} else if (c == '/') { // allow embedded comment
|
||||
mNextState = State::eUntilCloseParen;
|
||||
state = State::eCommentMaybeStart;
|
||||
} else if (isspace(c)) {
|
||||
state = State::eUntilCloseParen;
|
||||
} else {
|
||||
ReportProblem("while parsing integer", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// comment parsing
|
||||
case State::eCommentMaybeStart:
|
||||
switch (c) {
|
||||
case '*': // comment block
|
||||
state = State::eCommentBlock;
|
||||
break;
|
||||
case '/': // comment line
|
||||
state = State::eUntilEOL;
|
||||
break;
|
||||
default:
|
||||
// pref file is malformed
|
||||
ReportProblem("while parsing comment", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::eCommentBlock:
|
||||
if (c == '*') {
|
||||
state = State::eCommentBlockMaybeEnd;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::eCommentBlockMaybeEnd:
|
||||
switch (c) {
|
||||
case '/':
|
||||
state = mNextState;
|
||||
mNextState = State::eInit;
|
||||
break;
|
||||
case '*': // stay in this state
|
||||
break;
|
||||
default:
|
||||
state = State::eCommentBlock;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
// string escape sequence parsing
|
||||
case State::eEscapeSequence:
|
||||
// It's not necessary to resize the buffer here since we should be
|
||||
// writing only one character and the resize check would have been done
|
||||
// for us in the previous state.
|
||||
switch (c) {
|
||||
case '\"':
|
||||
case '\'':
|
||||
case '\\':
|
||||
break;
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
case 'x': // hex escape -- always interpreted as Latin-1
|
||||
case 'u': // UTF16 escape
|
||||
mEscTmp[0] = c;
|
||||
mEscLen = 1;
|
||||
mUtf16[0] = mUtf16[1] = 0;
|
||||
mStrIndex =
|
||||
(c == 'x') ? kHexEscapeNumDigits : kUTF16EscapeNumDigits;
|
||||
state = State::eHexEscape;
|
||||
continue;
|
||||
default:
|
||||
ReportProblem(
|
||||
"preserving unexpected JS escape sequence", lineNum, false);
|
||||
NS_WARNING("preserving unexpected JS escape sequence");
|
||||
// Invalid escape sequence so we do have to write more than one
|
||||
// character. Grow line buffer if necessary...
|
||||
if ((mLbCur + 1) == mLbEnd && !GrowBuf()) {
|
||||
return false; // out of memory
|
||||
}
|
||||
*mLbCur++ = '\\'; // preserve the escape sequence
|
||||
break;
|
||||
}
|
||||
*mLbCur++ = c;
|
||||
state = State::eQuotedString;
|
||||
break;
|
||||
|
||||
// parsing a hex (\xHH) or mUtf16 escape (\uHHHH)
|
||||
case State::eHexEscape: {
|
||||
char udigit;
|
||||
if (c >= '0' && c <= '9') {
|
||||
udigit = (c - '0');
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
udigit = (c - 'A') + 10;
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
udigit = (c - 'a') + 10;
|
||||
} else {
|
||||
// bad escape sequence found, write out broken escape as-is
|
||||
ReportProblem(
|
||||
"preserving invalid or incomplete hex escape", lineNum, false);
|
||||
NS_WARNING("preserving invalid or incomplete hex escape");
|
||||
*mLbCur++ = '\\'; // original escape slash
|
||||
if ((mLbCur + mEscLen) >= mLbEnd && !GrowBuf()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < mEscLen; ++i) {
|
||||
*mLbCur++ = mEscTmp[i];
|
||||
static void
|
||||
TestParseErrorHandleError(const char* aMsg)
|
||||
{
|
||||
// aMsg's lifetime is shorter than we need, so duplicate it.
|
||||
gTestParseErrorMsg = moz_xstrdup(aMsg);
|
||||
}
|
||||
|
||||
// Push the non-hex character back for re-parsing. (++aBuf at the top
|
||||
// of the loop keeps this safe.)
|
||||
--aBuf;
|
||||
state = State::eQuotedString;
|
||||
continue;
|
||||
}
|
||||
// Keep this in sync with the declaration in test/gtest/Parser.cpp.
|
||||
void
|
||||
TestParseError(const char* aText, nsCString& aErrorMsg)
|
||||
{
|
||||
prefs_parser_parse("test",
|
||||
aText,
|
||||
strlen(aText),
|
||||
TestParseErrorHandlePref,
|
||||
TestParseErrorHandleError);
|
||||
|
||||
// have a digit
|
||||
mEscTmp[mEscLen++] = c; // preserve it
|
||||
mUtf16[1] <<= KBitsPerHexDigit;
|
||||
mUtf16[1] |= udigit;
|
||||
mStrIndex--;
|
||||
if (mStrIndex == 0) {
|
||||
// we have the full escape, convert to UTF8
|
||||
int utf16len = 0;
|
||||
if (mUtf16[0]) {
|
||||
// already have a high surrogate, this is a two char seq
|
||||
utf16len = 2;
|
||||
} else if (0xD800 == (0xFC00 & mUtf16[1])) {
|
||||
// a high surrogate, can't convert until we have the low
|
||||
mUtf16[0] = mUtf16[1];
|
||||
mUtf16[1] = 0;
|
||||
state = State::eUTF16LowSurrogate;
|
||||
break;
|
||||
} else {
|
||||
// a single mUtf16 character
|
||||
mUtf16[0] = mUtf16[1];
|
||||
utf16len = 1;
|
||||
}
|
||||
|
||||
// The actual conversion.
|
||||
// Make sure there's room, 6 bytes is max utf8 len (in theory; 4
|
||||
// bytes covers the actual mUtf16 range).
|
||||
if (mLbCur + 6 >= mLbEnd && !GrowBuf()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ConvertUTF16toUTF8 converter(mLbCur);
|
||||
converter.write(mUtf16, utf16len);
|
||||
mLbCur += converter.Size();
|
||||
state = State::eQuotedString;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// looking for beginning of mUtf16 low surrogate
|
||||
case State::eUTF16LowSurrogate:
|
||||
if (mStrIndex == 0 && c == '\\') {
|
||||
++mStrIndex;
|
||||
} else if (mStrIndex == 1 && c == 'u') {
|
||||
// escape sequence is correct, now parse hex
|
||||
mStrIndex = kUTF16EscapeNumDigits;
|
||||
mEscTmp[0] = 'u';
|
||||
mEscLen = 1;
|
||||
state = State::eHexEscape;
|
||||
} else {
|
||||
// Didn't find expected low surrogate. Ignore high surrogate (it
|
||||
// would just get converted to nothing anyway) and start over with
|
||||
// this character.
|
||||
--aBuf;
|
||||
if (mStrIndex == 1) {
|
||||
state = State::eEscapeSequence;
|
||||
} else {
|
||||
state = State::eQuotedString;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
// function open and close parsing
|
||||
case State::eUntilOpenParen:
|
||||
// tolerate only whitespace and embedded comments
|
||||
if (c == '(') {
|
||||
state = State::eUntilName;
|
||||
} else if (c == '/') {
|
||||
mNextState = state; // return here when done with comment
|
||||
state = State::eCommentMaybeStart;
|
||||
} else if (!isspace(c)) {
|
||||
ReportProblem(
|
||||
"need space, comment or open parentheses", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case State::eUntilCloseParen:
|
||||
// tolerate only whitespace and embedded comments
|
||||
if (c == ')') {
|
||||
state = State::eUntilSemicolon;
|
||||
} else if (c == '/') {
|
||||
mNextState = state; // return here when done with comment
|
||||
state = State::eCommentMaybeStart;
|
||||
} else if (!isspace(c)) {
|
||||
ReportProblem(
|
||||
"need space, comment or closing parentheses", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
// function terminator ';' parsing
|
||||
case State::eUntilSemicolon:
|
||||
// tolerate only whitespace and embedded comments
|
||||
if (c == ';') {
|
||||
|
||||
PrefValue value;
|
||||
|
||||
switch (*mVtype) {
|
||||
case PrefType::String:
|
||||
value.mStringVal = mVb;
|
||||
break;
|
||||
|
||||
case PrefType::Int:
|
||||
if ((mVb[0] == '-' || mVb[0] == '+') && mVb[1] == '\0') {
|
||||
ReportProblem("invalid integer value", 0, true);
|
||||
NS_WARNING("malformed integer value");
|
||||
return false;
|
||||
}
|
||||
value.mIntVal = atoi(mVb);
|
||||
break;
|
||||
|
||||
case PrefType::Bool:
|
||||
value.mBoolVal = (mVb == kTrue);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
// We've extracted a complete name/value pair.
|
||||
HandleValue(mLb, *mVtype, value, mIsDefault, mIsSticky);
|
||||
numPrefs++;
|
||||
|
||||
state = State::eInit;
|
||||
} else if (c == '/') {
|
||||
mNextState = state; // return here when done with comment
|
||||
state = State::eCommentMaybeStart;
|
||||
} else if (!isspace(c)) {
|
||||
ReportProblem("need space, comment or semicolon", lineNum, true);
|
||||
NS_WARNING("malformed pref file");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
// eol parsing
|
||||
case State::eUntilEOL:
|
||||
// Need to handle mac, unix, or dos line endings. State::eInit will
|
||||
// eat the next \n in case we have \r\n.
|
||||
if (c == '\r' || c == '\n' || c == 0x1A) {
|
||||
state = mNextState;
|
||||
mNextState = State::eInit; // reset next state
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
mState = state;
|
||||
|
||||
uint32_t loadTime_us = (TimeStamp::Now() - aStartTime).ToMicroseconds();
|
||||
|
||||
// Most prefs files are read before telemetry initializes, so we have to save
|
||||
// these measurements now and send them to telemetry later.
|
||||
TelemetryLoadData loadData = { uint32_t(aBufLen), numPrefs, loadTime_us };
|
||||
gTelemetryLoadData->Put(aName, loadData);
|
||||
|
||||
return true;
|
||||
// Copy the duplicated error message into the outparam, then free it.
|
||||
aErrorMsg.Assign(gTestParseErrorMsg);
|
||||
free(gTestParseErrorMsg);
|
||||
gTestParseErrorMsg = nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -4001,8 +3496,12 @@ openPrefFile(nsIFile* aFile)
|
|||
aFile->GetLeafName(filenameUtf16);
|
||||
NS_ConvertUTF16toUTF8 filename(filenameUtf16);
|
||||
|
||||
nsAutoString path;
|
||||
aFile->GetPath(path);
|
||||
|
||||
Parser parser;
|
||||
if (!parser.Parse(filename, startTime, data.get(), data.Length())) {
|
||||
if (!parser.Parse(
|
||||
filename, NS_ConvertUTF16toUTF8(path).get(), startTime, data)) {
|
||||
return NS_ERROR_FILE_CORRUPTED;
|
||||
}
|
||||
|
||||
|
@ -4135,9 +3634,8 @@ pref_ReadPrefFromJar(nsZipArchive* aJarReader, const char* aName)
|
|||
MOZ_TRY_VAR(manifest,
|
||||
URLPreloader::ReadZip(aJarReader, nsDependentCString(aName)));
|
||||
|
||||
nsDependentCString name(aName);
|
||||
Parser parser;
|
||||
if (!parser.Parse(name, startTime, manifest.get(), manifest.Length())) {
|
||||
if (!parser.Parse(nsDependentCString(aName), aName, startTime, manifest)) {
|
||||
return NS_ERROR_FILE_CORRUPTED;
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,8 @@ class PrefValue;
|
|||
|
||||
struct PrefsSizes;
|
||||
|
||||
enum class PrefValueKind : bool
|
||||
// Keep this in sync with PrefType in parser/src/lib.rs.
|
||||
enum class PrefValueKind : uint8_t
|
||||
{
|
||||
Default,
|
||||
User
|
||||
|
|
|
@ -446,7 +446,7 @@ pref("media.suspend-bkgnd-video.enabled", true);
|
|||
pref("media.suspend-bkgnd-video.delay-ms", 10000);
|
||||
// Resume video decoding when the cursor is hovering on a background tab to
|
||||
// reduce the resume latency and improve the user experience.
|
||||
pref("media.resume-bkgnd-video-on-tabhover", true);;
|
||||
pref("media.resume-bkgnd-video-on-tabhover", true);
|
||||
|
||||
// Whether to enable media seamless looping.
|
||||
pref("media.seamless-looping", true);
|
||||
|
@ -2432,7 +2432,7 @@ pref("intl.hyphenation-alias.nb-*", "nb");
|
|||
pref("intl.hyphenation-alias.nn-*", "nn");
|
||||
|
||||
// All prefs of default font should be "auto".
|
||||
pref("font.name.serif.ar", "");;
|
||||
pref("font.name.serif.ar", "");
|
||||
pref("font.name.sans-serif.ar", "");
|
||||
pref("font.name.monospace.ar", "");
|
||||
pref("font.name.cursive.ar", "");
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "prefs_parser"
|
||||
version = "0.0.1"
|
||||
authors = ["Nicholas Nethercote <nnethercote@mozilla.com>"]
|
||||
|
||||
[dependencies]
|
|
@ -0,0 +1,782 @@
|
|||
/* 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/. */
|
||||
|
||||
//! This crate implements a prefs file parser.
|
||||
//!
|
||||
//! Pref files have the following grammar.
|
||||
//!
|
||||
//! <pref-file> = <pref>*
|
||||
//! <pref> = <pref-spec> "(" <pref-name> "," <pref-value> ")" ";"
|
||||
//! <pref-spec> = "user_pref" | "pref" | "sticky_pref"
|
||||
//! <pref-name> = <string-literal>
|
||||
//! <pref-value> = <string-literal> | "true" | "false" | <int-value>
|
||||
//! <int-value> = <sign>? <int-literal>
|
||||
//! <sign> = "+" | "-"
|
||||
//! <int-literal> = [0-9]+ (and cannot be followed by [A-Za-z_])
|
||||
//! <string-literal> =
|
||||
//! A single or double-quoted string, with the following escape sequences
|
||||
//! allowed: \", \', \\, \n, \r, \xNN, \uNNNN, where \xNN gives a raw byte
|
||||
//! value that is copied directly into an 8-bit string value, and \uNNNN
|
||||
//! gives a UTF-16 code unit that is converted to UTF-8 before being copied
|
||||
//! into an 8-bit string value. \x00 and \u0000 are disallowed because they
|
||||
//! would cause C++ code handling such strings to misbehave.
|
||||
//!
|
||||
//! Comments can take three forms:
|
||||
//! - # Python-style comments
|
||||
//! - // C++ style comments
|
||||
//! - /* C style comments (non-nested) */
|
||||
//!
|
||||
//! Non-end-of-line whitespace chars are \t, \v, \f, and space.
|
||||
//!
|
||||
//! End-of-line sequences can take three forms, each of which is considered as a
|
||||
//! single EoL:
|
||||
//! - \n
|
||||
//! - \r (without subsequent \n)
|
||||
//! - \r\n
|
||||
//!
|
||||
//! The valid range for <int-value> is -2,147,483,648..2,147,483,647. Values
|
||||
//! outside that range will result in a parse error.
|
||||
//!
|
||||
//! A '\0' char is interpreted as the end of the file. The use of this character
|
||||
//! in a prefs file is not recommended. Within string literals \x00 or \u0000
|
||||
//! can be used instead.
|
||||
|
||||
// This parser uses several important optimizations.
|
||||
//
|
||||
// - Because "'\0' means EOF" is part of the grammar (see above) we can match
|
||||
// EOF as a normal char/token, which means we can avoid a typical "do we
|
||||
// still have chars remaining?" test in get_char(), which gives a speedup
|
||||
// because get_char() is a very hot function. (Actually, Rust would
|
||||
// bounds-check this function anyway, so we have get_char_unchecked() which
|
||||
// is used for the two hottest call sites.)
|
||||
//
|
||||
// This also means EOF is representable by a u8. If EOF was represented by an
|
||||
// out-of-band value such as -1 or 256, we'd have to return a larger type
|
||||
// such as u16 or i16 from get_char().
|
||||
//
|
||||
// - When starting a new token, it uses a lookup table with the first char,
|
||||
// which quickly identifies what kind of token it will be. Furthermore, if
|
||||
// that token is an unambiguous single-char token (e.g. '(', ')', '+', ',',
|
||||
// '-', ';'), the parser will return the appropriate token kind value at
|
||||
// minimal cost because the single-char tokens have a uniform representation.
|
||||
//
|
||||
// - It has a lookup table that identifies chars in string literals that need
|
||||
// special handling. This means non-special chars (the common case) can be
|
||||
// handled with a single test, rather than testing for the multiple special
|
||||
// cases.
|
||||
//
|
||||
// - It pre-scans string literals for special chars. If none are present, it
|
||||
// bulk copies the string literal into a Vec, which is faster than doing a
|
||||
// char-by-char copy.
|
||||
//
|
||||
// - It reuses Vecs to avoid creating a new one for each string literal.
|
||||
|
||||
use std::os::raw::{c_char, c_uchar};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// The public interface
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/// Keep this in sync with PrefType in Preferences.cpp.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum PrefType {
|
||||
None,
|
||||
String,
|
||||
Int,
|
||||
Bool,
|
||||
}
|
||||
|
||||
/// Keep this in sync with PrefValueKind in Preferences.h.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum PrefValueKind {
|
||||
Default,
|
||||
User
|
||||
}
|
||||
|
||||
/// Keep this in sync with PrefValue in Preferences.cpp.
|
||||
#[repr(C)]
|
||||
pub union PrefValue {
|
||||
string_val: *const c_char,
|
||||
int_val: i32,
|
||||
bool_val: bool,
|
||||
}
|
||||
|
||||
/// Keep this in sync with PrefsParserPrefFn in Preferences.cpp.
|
||||
type PrefFn = unsafe extern "C" fn(pref_name: *const c_char, pref_type: PrefType,
|
||||
pref_value_kind: PrefValueKind, pref_value: PrefValue,
|
||||
is_sticky: bool);
|
||||
|
||||
/// Keep this in sync with PrefsParserErrorFn in Preferences.cpp.
|
||||
type ErrorFn = unsafe extern "C" fn(msg: *const c_char);
|
||||
|
||||
/// Parse the contents of a prefs file.
|
||||
///
|
||||
/// `buf` is a null-terminated string. `len` is its length, excluding the
|
||||
/// null terminator.
|
||||
///
|
||||
/// Keep this in sync with the prefs_parser_parse() declaration in
|
||||
/// Preferences.cpp.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn prefs_parser_parse(path: *const c_char, buf: *const c_char, len: usize,
|
||||
pref_fn: PrefFn, error_fn: ErrorFn) -> bool {
|
||||
let path = unsafe { std::ffi::CStr::from_ptr(path).to_string_lossy().into_owned() };
|
||||
|
||||
// Make sure `buf` ends in a '\0', and include that in the length, because
|
||||
// it represents EOF.
|
||||
let buf = unsafe { std::slice::from_raw_parts(buf as *const c_uchar, len + 1) };
|
||||
assert!(buf.last() == Some(&EOF));
|
||||
|
||||
let mut parser = Parser::new(&path, &buf, pref_fn, error_fn);
|
||||
parser.parse()
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// The implementation
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Token {
|
||||
// Unambiguous single-char tokens.
|
||||
SingleChar(u8),
|
||||
|
||||
// Keywords
|
||||
Pref, // pref
|
||||
StickyPref, // sticky_pref
|
||||
UserPref, // user_pref
|
||||
True, // true
|
||||
False, // false
|
||||
|
||||
// String literal, e.g. '"string"'. The value is stored elsewhere.
|
||||
String,
|
||||
|
||||
// Unsigned integer literal, e.g. '123'. Although libpref uses i32 values,
|
||||
// any '-' and '+' before an integer literal are treated as separate
|
||||
// tokens, so these token values are always positive. Furthermore, we
|
||||
// tokenize int literals as u32 so that 2147483648 (which doesn't fit into
|
||||
// an i32) can be subsequently negated to -2147483648 (which does fit into
|
||||
// an i32) if a '-' token precedes it.
|
||||
Int(u32),
|
||||
|
||||
// Malformed token.
|
||||
Error(&'static str),
|
||||
}
|
||||
|
||||
// We categorize every char by what action should be taken when it appears at
|
||||
// the start of a new token.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum CharKind {
|
||||
// These are ordered by frequency. See the comment in GetToken().
|
||||
SingleChar, // Unambiguous single-char tokens: [()+,-]
|
||||
SpaceNL, // [\t\v\f \n]
|
||||
Keyword, // [A-Za-z_]
|
||||
Quote, // ["']
|
||||
Slash, // /
|
||||
Digit, // [0-9]
|
||||
Hash, // #
|
||||
CR, // \r
|
||||
Other // Everything else; invalid except within strings and comments.
|
||||
}
|
||||
|
||||
const C_SINGL: CharKind = CharKind::SingleChar;
|
||||
const C_SPCNL: CharKind = CharKind::SpaceNL;
|
||||
const C_KEYWD: CharKind = CharKind::Keyword;
|
||||
const C_QUOTE: CharKind = CharKind::Quote;
|
||||
const C_SLASH: CharKind = CharKind::Slash;
|
||||
const C_DIGIT: CharKind = CharKind::Digit;
|
||||
const C_HASH : CharKind = CharKind::Hash;
|
||||
const C_CR : CharKind = CharKind::CR;
|
||||
const C______: CharKind = CharKind::Other;
|
||||
|
||||
const CHAR_KINDS: [CharKind; 256] = [
|
||||
/* 0 1 2 3 4 5 6 7 8 9 */
|
||||
/* 0+ */ C_SINGL, C______, C______, C______, C______, C______, C______, C______, C______, C_SPCNL,
|
||||
/* 10+ */ C_SPCNL, C_SPCNL, C_SPCNL, C_CR , C______, C______, C______, C______, C______, C______,
|
||||
/* 20+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 30+ */ C______, C______, C_SPCNL, C______, C_QUOTE, C_HASH , C______, C______, C______, C_QUOTE,
|
||||
/* 40+ */ C_SINGL, C_SINGL, C______, C_SINGL, C_SINGL, C_SINGL, C______, C_SLASH, C_DIGIT, C_DIGIT,
|
||||
/* 50+ */ C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C______, C_SINGL,
|
||||
/* 60+ */ C______, C______, C______, C______, C______, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD,
|
||||
/* 70+ */ C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD,
|
||||
/* 80+ */ C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD,
|
||||
/* 90+ */ C_KEYWD, C______, C______, C______, C______, C_KEYWD, C______, C_KEYWD, C_KEYWD, C_KEYWD,
|
||||
/* 100+ */ C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD,
|
||||
/* 110+ */ C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD, C_KEYWD,
|
||||
/* 120+ */ C_KEYWD, C_KEYWD, C_KEYWD, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 130+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 140+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 150+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 160+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 170+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 180+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 190+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 200+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 210+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 220+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 230+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 240+ */ C______, C______, C______, C______, C______, C______, C______, C______, C______, C______,
|
||||
/* 250+ */ C______, C______, C______, C______, C______, C______
|
||||
];
|
||||
|
||||
const _______: bool = false;
|
||||
const SPECIAL_STRING_CHARS: [bool; 256] = [
|
||||
/* 0 1 2 3 4 5 6 7 8 9 */
|
||||
/* 0+ */ true, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 10+ */ true, _______, _______, true, _______, _______, _______, _______, _______, _______,
|
||||
/* 20+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 30+ */ _______, _______, _______, _______, true, _______, _______, _______, _______, true,
|
||||
/* 40+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 50+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 60+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 70+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 80+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 90+ */ _______, _______, true, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 100+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 110+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 120+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 130+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 140+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 150+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 160+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 170+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 180+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 190+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 200+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 210+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 220+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 230+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 240+ */ _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
|
||||
/* 250+ */ _______, _______, _______, _______, _______, _______
|
||||
];
|
||||
|
||||
struct KeywordInfo {
|
||||
string: &'static [u8],
|
||||
token: Token,
|
||||
}
|
||||
|
||||
const KEYWORD_INFOS: &[KeywordInfo; 5] = &[
|
||||
// These are ordered by frequency.
|
||||
KeywordInfo { string: b"pref", token: Token::Pref },
|
||||
KeywordInfo { string: b"true", token: Token::True },
|
||||
KeywordInfo { string: b"false", token: Token::False },
|
||||
KeywordInfo { string: b"user_pref", token: Token::UserPref },
|
||||
KeywordInfo { string: b"sticky_pref", token: Token::StickyPref },
|
||||
];
|
||||
|
||||
struct Parser<'t> {
|
||||
path: &'t str, // Path to the file being parsed. Used in error messages.
|
||||
buf: &'t [u8], // Text being parsed.
|
||||
i: usize, // Index of next char to be read.
|
||||
line_num: u32, // Current line number within the text.
|
||||
pref_fn: PrefFn, // Callback for processing each pref.
|
||||
error_fn: ErrorFn, // Callback for parse errors.
|
||||
}
|
||||
|
||||
// As described above, we use 0 to represent EOF.
|
||||
const EOF: u8 = b'\0';
|
||||
|
||||
impl<'t> Parser<'t> {
|
||||
fn new(path: &'t str, buf: &'t [u8], pref_fn: PrefFn, error_fn: ErrorFn) -> Parser<'t> {
|
||||
// Make sure these tables take up 1 byte per entry.
|
||||
assert!(std::mem::size_of_val(&CHAR_KINDS) == 256);
|
||||
assert!(std::mem::size_of_val(&SPECIAL_STRING_CHARS) == 256);
|
||||
|
||||
Parser {
|
||||
path: path,
|
||||
buf: buf,
|
||||
i: 0,
|
||||
line_num: 1,
|
||||
pref_fn: pref_fn,
|
||||
error_fn: error_fn,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(&mut self) -> bool {
|
||||
// These are reused, because allocating a new Vec for every string is slow.
|
||||
let mut name_str = Vec::with_capacity(128); // For pref names.
|
||||
let mut value_str = Vec::with_capacity(512); // For string pref values.
|
||||
let mut none_str = Vec::with_capacity(0); // For tokens that shouldn't be strings.
|
||||
|
||||
loop {
|
||||
// Note: if you add error recovery here, be aware that the
|
||||
// erroneous char may have been the text-ending EOF, in which case
|
||||
// self.i will point one past the end of the text. You should check
|
||||
// for that possibility before getting more chars.
|
||||
|
||||
// EOF?
|
||||
let token = self.get_token(&mut none_str);
|
||||
if token == Token::SingleChar(EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
// <pref-spec>
|
||||
let (pref_value_kind, is_sticky) = match token {
|
||||
Token::Pref => {
|
||||
(PrefValueKind::Default, false)
|
||||
}
|
||||
Token::StickyPref => {
|
||||
(PrefValueKind::Default, true)
|
||||
}
|
||||
Token::UserPref => {
|
||||
(PrefValueKind::User, false)
|
||||
}
|
||||
_ => return self.error(token,
|
||||
"expected pref specifier at start of pref definition")
|
||||
};
|
||||
|
||||
// "("
|
||||
let token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b'(') {
|
||||
return self.error(token, "expected '(' after pref specifier");
|
||||
}
|
||||
|
||||
// <pref-name>
|
||||
let token = self.get_token(&mut name_str);
|
||||
let pref_name = if token == Token::String {
|
||||
&name_str
|
||||
} else {
|
||||
return self.error(token, "expected pref name after '('");
|
||||
};
|
||||
|
||||
// ","
|
||||
let token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b',') {
|
||||
return self.error(token, "expected ',' after pref name");
|
||||
}
|
||||
|
||||
// <pref-value>
|
||||
let token = self.get_token(&mut value_str);
|
||||
let (pref_type, pref_value) = match token {
|
||||
Token::True => {
|
||||
(PrefType::Bool, PrefValue { bool_val: true })
|
||||
}
|
||||
Token::False => {
|
||||
(PrefType::Bool, PrefValue { bool_val: false })
|
||||
}
|
||||
Token::String => {
|
||||
(PrefType::String,
|
||||
PrefValue { string_val: value_str.as_ptr() as *const c_char })
|
||||
|
||||
}
|
||||
Token::Int(u) => {
|
||||
// Accept u <= 2147483647; anything larger will overflow i32.
|
||||
if u <= std::i32::MAX as u32 {
|
||||
(PrefType::Int, PrefValue { int_val: u as i32 })
|
||||
} else {
|
||||
return self.error(Token::Error("integer literal overflowed"), "");
|
||||
}
|
||||
|
||||
}
|
||||
Token::SingleChar(b'-') => {
|
||||
let token = self.get_token(&mut none_str);
|
||||
if let Token::Int(u) = token {
|
||||
// Accept u <= 2147483648; anything larger will overflow i32 once negated.
|
||||
if u <= std::i32::MAX as u32 {
|
||||
(PrefType::Int, PrefValue { int_val: -(u as i32) })
|
||||
} else if u == std::i32::MAX as u32 + 1 {
|
||||
(PrefType::Int, PrefValue { int_val: std::i32::MIN })
|
||||
} else {
|
||||
return self.error(Token::Error("integer literal overflowed"), "");
|
||||
}
|
||||
} else {
|
||||
return self.error(token, "expected integer literal after '-'");
|
||||
}
|
||||
|
||||
}
|
||||
Token::SingleChar(b'+') => {
|
||||
let token = self.get_token(&mut none_str);
|
||||
if let Token::Int(u) = token {
|
||||
// Accept u <= 2147483647; anything larger will overflow i32.
|
||||
if u <= std::i32::MAX as u32 {
|
||||
(PrefType::Int, PrefValue { int_val: u as i32 })
|
||||
} else {
|
||||
return self.error(Token::Error("integer literal overflowed"), "");
|
||||
}
|
||||
} else {
|
||||
return self.error(token, "expected integer literal after '+'");
|
||||
}
|
||||
|
||||
}
|
||||
_ => return self.error(token, "expected pref value after ','")
|
||||
};
|
||||
|
||||
// ")"
|
||||
let token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b')') {
|
||||
return self.error(token, "expected ')' after pref value");
|
||||
}
|
||||
|
||||
// ";"
|
||||
let token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b';') {
|
||||
return self.error(token, "expected ';' after ')'");
|
||||
}
|
||||
|
||||
unsafe { (self.pref_fn)(pref_name.as_ptr() as *const c_char, pref_type, pref_value_kind,
|
||||
pref_value, is_sticky) };
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn error(&self, token: Token, msg: &str) -> bool {
|
||||
// If `token` is a Token::Error, it's a lexing error and the error
|
||||
// message is within `token`. Otherwise, it's a parsing error and the
|
||||
// error message is in `msg`.
|
||||
let msg = if let Token::Error(token_msg) = token {
|
||||
token_msg
|
||||
} else {
|
||||
msg
|
||||
};
|
||||
let msg = format!("{}:{}: prefs parse error: {}", self.path, self.line_num, msg);
|
||||
let msg = std::ffi::CString::new(msg).unwrap();
|
||||
unsafe { (self.error_fn)(msg.as_ptr() as *const c_char) };
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_char(&mut self) -> u8 {
|
||||
let c = self.buf[self.i];
|
||||
self.i += 1;
|
||||
c
|
||||
}
|
||||
|
||||
// This function skips the bounds check. Using it at the hottest two call
|
||||
// sites gives a ~15% parsing speed boost.
|
||||
#[inline(always)]
|
||||
unsafe fn get_char_unchecked(&mut self) -> u8 {
|
||||
let c = *self.buf.get_unchecked(self.i);
|
||||
self.i += 1;
|
||||
c
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unget_char(&mut self) {
|
||||
debug_assert!(self.i > 0);
|
||||
self.i -= 1;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn match_char(&mut self, c: u8) -> bool {
|
||||
if self.buf[self.i] == c {
|
||||
self.i += 1;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn match_single_line_comment(&mut self) {
|
||||
loop {
|
||||
// To reach here, the previous char must have been '/', and
|
||||
// assertions elsewhere ensure that there must be at least one
|
||||
// subsequent char (the '\0' for EOF).
|
||||
let c = unsafe { self.get_char_unchecked() };
|
||||
|
||||
// All the special chars have value <= b'\r'.
|
||||
if c > b'\r' {
|
||||
continue;
|
||||
}
|
||||
match c {
|
||||
b'\n' => {
|
||||
self.line_num += 1;
|
||||
break;
|
||||
}
|
||||
b'\r' => {
|
||||
self.line_num += 1;
|
||||
self.match_char(b'\n');
|
||||
break;
|
||||
}
|
||||
EOF => {
|
||||
// We must unget the EOF otherwise we'll read past it the
|
||||
// next time around the main loop in get_token(), violating
|
||||
// self.buf's bounds.
|
||||
self.unget_char();
|
||||
break;
|
||||
}
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false if we hit EOF without closing the comment.
|
||||
fn match_multi_line_comment(&mut self) -> bool
|
||||
{
|
||||
loop {
|
||||
match self.get_char() {
|
||||
b'*' => {
|
||||
if self.match_char(b'/') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
b'\n' => {
|
||||
self.line_num += 1;
|
||||
}
|
||||
b'\r' => {
|
||||
self.line_num += 1;
|
||||
self.match_char(b'\n');
|
||||
}
|
||||
EOF => {
|
||||
return false
|
||||
}
|
||||
_ => continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn match_hex_digits(&mut self, ndigits: i32) -> Option<u16> {
|
||||
debug_assert!(ndigits == 2 || ndigits == 4);
|
||||
let mut value: u16 = 0;
|
||||
for _ in 0..ndigits {
|
||||
value = value << 4;
|
||||
match self.get_char() {
|
||||
c @ b'0'... b'9' => value += (c - b'0') as u16,
|
||||
c @ b'A'...b'F' => value += (c - b'A') as u16 + 10,
|
||||
c @ b'a'...b'f' => value += (c - b'a') as u16 + 10,
|
||||
_ => return None
|
||||
}
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn char_kind(c: u8) -> CharKind {
|
||||
// Use get_unchecked() because a u8 index cannot exceed this table's
|
||||
// bounds.
|
||||
unsafe { *CHAR_KINDS.get_unchecked(c as usize) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_special_string_char(c: u8) -> bool {
|
||||
// Use get_unchecked() because a u8 index cannot exceed this table's
|
||||
// bounds.
|
||||
unsafe { *SPECIAL_STRING_CHARS.get_unchecked(c as usize) }
|
||||
}
|
||||
|
||||
// If the obtained Token has a value, it is put within the Token, unless
|
||||
// it's a string, in which case it's put in `str_buf`. This avoids
|
||||
// allocating a new Vec for every string, which is slow.
|
||||
fn get_token(&mut self, str_buf: &mut Vec<u8>) -> Token {
|
||||
loop {
|
||||
// Note: the following tests are ordered by frequency when parsing
|
||||
// greprefs.js:
|
||||
// - SingleChar 36.7%
|
||||
// - SpaceNL 27.7% (14.9% for spaces, 12.8% for NL)
|
||||
// - Keyword 13.4%
|
||||
// - Quote 11.4%
|
||||
// - Slash 8.1%
|
||||
// - Digit 2.7%
|
||||
// - Hash, CR, Other 0.0%
|
||||
|
||||
let c = self.get_char();
|
||||
match Parser::char_kind(c) {
|
||||
CharKind::SingleChar => {
|
||||
return Token::SingleChar(c);
|
||||
}
|
||||
CharKind::SpaceNL => {
|
||||
// It's slightly faster to combine the handling of the
|
||||
// space chars with NL than to handle them separately; we
|
||||
// have an extra test for this case, but one fewer test for
|
||||
// all the subsequent CharKinds.
|
||||
if c == b'\n' {
|
||||
self.line_num += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
CharKind::Keyword => {
|
||||
let start = self.i - 1;
|
||||
loop {
|
||||
let c = self.get_char();
|
||||
if Parser::char_kind(c) != CharKind::Keyword {
|
||||
self.unget_char();
|
||||
break;
|
||||
}
|
||||
}
|
||||
for info in KEYWORD_INFOS.iter() {
|
||||
if &self.buf[start..self.i] == info.string {
|
||||
return info.token;
|
||||
}
|
||||
}
|
||||
return Token::Error("unknown keyword");
|
||||
}
|
||||
CharKind::Quote => {
|
||||
return self.get_string_token(c, str_buf);
|
||||
}
|
||||
CharKind::Slash => {
|
||||
match self.get_char() {
|
||||
b'/' => {
|
||||
self.match_single_line_comment();
|
||||
}
|
||||
b'*' => {
|
||||
if !self.match_multi_line_comment() {
|
||||
return Token::Error("unterminated /* comment");
|
||||
}
|
||||
}
|
||||
_ => return Token::Error("expected '/' or '*' after '/'")
|
||||
}
|
||||
continue;
|
||||
}
|
||||
CharKind::Digit => {
|
||||
let mut value = (c - b'0') as u32;
|
||||
loop {
|
||||
let c = self.get_char();
|
||||
match Parser::char_kind(c) {
|
||||
CharKind::Digit => {
|
||||
fn add_digit(v: u32, c: u8) -> Option<u32> {
|
||||
v.checked_mul(10)?.checked_add((c - b'0') as u32)
|
||||
}
|
||||
if let Some(v) = add_digit(value, c) {
|
||||
value = v;
|
||||
} else {
|
||||
return Token::Error("integer literal overflowed");
|
||||
}
|
||||
}
|
||||
CharKind::Keyword => {
|
||||
// Reject things like "123foo".
|
||||
return Token::Error(
|
||||
"unexpected character in integer literal");
|
||||
}
|
||||
_ => {
|
||||
self.unget_char();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Token::Int(value);
|
||||
}
|
||||
CharKind::Hash => {
|
||||
self.match_single_line_comment();
|
||||
continue;
|
||||
}
|
||||
CharKind::CR => {
|
||||
self.match_char(b'\n');
|
||||
self.line_num += 1;
|
||||
continue;
|
||||
}
|
||||
_ => return Token::Error("unexpected character")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always inline this because it has a single call site.
|
||||
#[inline(always)]
|
||||
fn get_string_token(&mut self, quote_char: u8, str_buf: &mut Vec<u8>) -> Token {
|
||||
// First scan through the string to see if it contains any chars that
|
||||
// need special handling.
|
||||
let start = self.i;
|
||||
let has_special_chars = loop {
|
||||
// To reach here, the previous char must have been a quote
|
||||
// (quote_char), and assertions elsewhere ensure that there must be
|
||||
// at least one subsequent char (the '\0' for EOF).
|
||||
let c = unsafe { self.get_char_unchecked() };
|
||||
if Parser::is_special_string_char(c) {
|
||||
break c != quote_char;
|
||||
}
|
||||
};
|
||||
|
||||
// Clear str_buf's contents without changing its capacity.
|
||||
str_buf.clear();
|
||||
|
||||
// If there are no special chars (the common case), we can bulk copy it
|
||||
// to str_buf. This is a lot faster than the char-by-char loop below.
|
||||
if !has_special_chars {
|
||||
str_buf.extend(&self.buf[start..self.i - 1]);
|
||||
str_buf.push(b'\0');
|
||||
return Token::String;
|
||||
}
|
||||
|
||||
// There were special chars. Re-scan the string, filling in str_buf one
|
||||
// char at a time.
|
||||
self.i = start;
|
||||
loop {
|
||||
let c = self.get_char();
|
||||
let c2 = if !Parser::is_special_string_char(c) {
|
||||
c
|
||||
|
||||
} else if c == quote_char {
|
||||
break;
|
||||
|
||||
} else if c == b'\\' {
|
||||
match self.get_char() {
|
||||
b'\"' => b'\"',
|
||||
b'\'' => b'\'',
|
||||
b'\\' => b'\\',
|
||||
b'n' => b'\n',
|
||||
b'r' => b'\r',
|
||||
b'x' => {
|
||||
if let Some(value) = self.match_hex_digits(2) {
|
||||
debug_assert!(value <= 0xff);
|
||||
if value != 0 {
|
||||
value as u8
|
||||
} else {
|
||||
return Token::Error("\\x00 is not allowed");
|
||||
}
|
||||
} else {
|
||||
return Token::Error("malformed \\x escape sequence");
|
||||
}
|
||||
}
|
||||
b'u' => {
|
||||
if let Some(value) = self.match_hex_digits(4) {
|
||||
let mut utf16 = vec![value];
|
||||
if 0xd800 == (0xfc00 & value) {
|
||||
// High surrogate value. Look for the low surrogate value.
|
||||
if self.match_char(b'\\') && self.match_char(b'u') {
|
||||
if let Some(lo) = self.match_hex_digits(4) {
|
||||
if 0xdc00 == (0xfc00 & lo) {
|
||||
// Found a valid low surrogate.
|
||||
utf16.push(lo);
|
||||
} else {
|
||||
return Token::Error(
|
||||
"invalid low surrogate value after high surrogate");
|
||||
}
|
||||
}
|
||||
}
|
||||
if utf16.len() != 2 {
|
||||
return Token::Error(
|
||||
"expected low surrogate after high surrogate");
|
||||
}
|
||||
} else if value == 0 {
|
||||
return Token::Error("\\u0000 is not allowed");
|
||||
}
|
||||
|
||||
// Insert the UTF-16 sequence as UTF-8.
|
||||
let utf8 = String::from_utf16(&utf16).unwrap();
|
||||
str_buf.extend(utf8.as_bytes());
|
||||
} else {
|
||||
return Token::Error("malformed \\u escape sequence");
|
||||
}
|
||||
continue; // We don't want to str_buf.push(c2) below.
|
||||
}
|
||||
_ => return Token::Error("unexpected escape sequence character after '\\'")
|
||||
}
|
||||
|
||||
} else if c == b'\n' {
|
||||
self.line_num += 1;
|
||||
c
|
||||
|
||||
} else if c == b'\r' {
|
||||
self.line_num += 1;
|
||||
if self.match_char(b'\n') {
|
||||
str_buf.push(b'\r');
|
||||
b'\n'
|
||||
} else {
|
||||
c
|
||||
}
|
||||
|
||||
} else if c == EOF {
|
||||
return Token::Error("unterminated string literal");
|
||||
|
||||
} else {
|
||||
// This case is only hit for the non-closing quote char.
|
||||
debug_assert!((c == b'\'' || c == b'\"') && c != quote_char);
|
||||
c
|
||||
};
|
||||
str_buf.push(c2);
|
||||
}
|
||||
str_buf.push(b'\0');
|
||||
return Token::String;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,544 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
// Keep this in sync with the declaration in Preferences.cpp.
|
||||
//
|
||||
// It's declared here to avoid polluting Preferences.h with test-only stuff.
|
||||
void
|
||||
TestParseError(const char* aText, nsCString& aErrorMsg);
|
||||
|
||||
TEST(PrefsParser, Errors)
|
||||
{
|
||||
nsAutoCStringN<128> actualErrorMsg;
|
||||
|
||||
// Use a macro rather than a function so that the line number reported by
|
||||
// gtest on failure is useful.
|
||||
#define P(text_, expectedErrorMsg_) \
|
||||
do { \
|
||||
TestParseError(text_, actualErrorMsg); \
|
||||
ASSERT_STREQ(expectedErrorMsg_, actualErrorMsg.get()); \
|
||||
} while (0)
|
||||
|
||||
// clang-format off
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Valid syntax, just as a sanity test. (More thorough testing of valid syntax
|
||||
// and semantics is done in modules/libpref/test/unit/test_parser.js.)
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
P(R"(
|
||||
pref("bool", true);
|
||||
sticky_pref("int", 123);
|
||||
user_pref("string", "value");
|
||||
)",
|
||||
""
|
||||
);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// All the lexing errors. (To be pedantic, some of the integer literal
|
||||
// overflows are triggered in the parser, but put them all here so they're all
|
||||
// in the one spot.)
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Integer overflow errors.
|
||||
|
||||
P(R"(
|
||||
pref("int.ok", 2147483647);
|
||||
pref("int.overflow", 2147483648);
|
||||
)",
|
||||
"test:3: prefs parse error: integer literal overflowed");
|
||||
|
||||
P(R"(
|
||||
pref("int.ok", +2147483647);
|
||||
pref("int.overflow", +2147483648);
|
||||
)",
|
||||
"test:3: prefs parse error: integer literal overflowed"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("int.ok", -2147483648);
|
||||
pref("int.overflow", -2147483649);
|
||||
)",
|
||||
"test:3: prefs parse error: integer literal overflowed"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("int.overflow", 4294967296);
|
||||
)",
|
||||
"test:2: prefs parse error: integer literal overflowed"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("int.overflow", +4294967296);
|
||||
)",
|
||||
"test:2: prefs parse error: integer literal overflowed"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("int.overflow", -4294967296);
|
||||
)",
|
||||
"test:2: prefs parse error: integer literal overflowed"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("int.overflow", 4294967297);
|
||||
)",
|
||||
"test:2: prefs parse error: integer literal overflowed"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("int.overflow", 1234567890987654321);
|
||||
)",
|
||||
"test:2: prefs parse error: integer literal overflowed"
|
||||
);
|
||||
|
||||
// Other integer errors.
|
||||
|
||||
P(R"(
|
||||
pref("int.unexpected", 100foo);
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected character in integer literal"
|
||||
);
|
||||
|
||||
// \x escape errors.
|
||||
|
||||
// \x00 is not allowed.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x00bar");
|
||||
)",
|
||||
"test:2: prefs parse error: \\x00 is not allowed"
|
||||
);
|
||||
|
||||
// End of string after \x.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
);
|
||||
|
||||
// Punctuation after \x.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x,bar");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
);
|
||||
|
||||
// Space after \x.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x 12");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
);
|
||||
|
||||
// Newline after \x.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x
|
||||
12");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
);
|
||||
|
||||
// EOF after \x.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
);
|
||||
|
||||
// Not enough hex digits.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x1");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
);
|
||||
|
||||
// Invalid hex digit.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x1G");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
);
|
||||
|
||||
// \u escape errors.
|
||||
|
||||
// \u0000 is not allowed.
|
||||
// (The string literal is broken in two so that MSVC doesn't complain about
|
||||
// an invalid universal-character-name.)
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\)" R"(u0000 bar");
|
||||
)",
|
||||
"test:2: prefs parse error: \\u0000 is not allowed"
|
||||
);
|
||||
|
||||
// End of string after \u.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// Punctuation after \u.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u,bar");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// Space after \u.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u 1234");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// Newline after \u.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u
|
||||
1234");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// EOF after \u.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// Not enough hex digits.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u1");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// Not enough hex digits.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u12");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// Not enough hex digits.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u123");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// Invalid hex digit.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u1G34");
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
);
|
||||
|
||||
// High surrogate not followed by low surrogate.
|
||||
// (The string literal is broken in two so that MSVC doesn't complain about
|
||||
// an invalid universal-character-name.)
|
||||
P(R"(
|
||||
pref("string.bad-u-surrogate", "foo\)" R"(ud83c,blah");
|
||||
)",
|
||||
"test:2: prefs parse error: expected low surrogate after high surrogate"
|
||||
);
|
||||
|
||||
// High surrogate followed by invalid low surrogate value.
|
||||
// (The string literal is broken in two so that MSVC doesn't complain about
|
||||
// an invalid universal-character-name.)
|
||||
P(R"(
|
||||
pref("string.bad-u-surrogate", "foo\)" R"(ud83c\u1234");
|
||||
)",
|
||||
"test:2: prefs parse error: invalid low surrogate value after high surrogate"
|
||||
);
|
||||
|
||||
// Bad escape characters.
|
||||
|
||||
// Unlike in JavaScript, \b isn't allowed.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\v");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Unlike in JavaScript, \f isn't allowed.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\f");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Unlike in JavaScript, \t isn't allowed.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\t");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Unlike in JavaScript, \v isn't allowed.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\v");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Non-special letter after \.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\Q");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Number after \.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\1");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Punctuation after \.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\,");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Space after \.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\ n");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Newline after \.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\
|
||||
n");
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// EOF after \.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
);
|
||||
|
||||
// Unterminated string literals.
|
||||
|
||||
// Simple case.
|
||||
P(R"(
|
||||
pref("string.unterminated-string", "foo
|
||||
)",
|
||||
"test:3: prefs parse error: unterminated string literal"
|
||||
);
|
||||
|
||||
// Mismatched quotes (1).
|
||||
P(R"(
|
||||
pref("string.unterminated-string", "foo');
|
||||
)",
|
||||
"test:3: prefs parse error: unterminated string literal"
|
||||
);
|
||||
|
||||
// Mismatched quotes (2).
|
||||
P(R"(
|
||||
pref("string.unterminated-string", 'foo");
|
||||
)",
|
||||
"test:3: prefs parse error: unterminated string literal"
|
||||
);
|
||||
|
||||
// Unknown keywords
|
||||
|
||||
P(R"(
|
||||
foo
|
||||
)",
|
||||
"test:2: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
preff("string.bad-keyword", true);
|
||||
)",
|
||||
"test:2: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
ticky_pref("string.bad-keyword", true);
|
||||
)",
|
||||
"test:2: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
User_pref("string.bad-keyword", true);
|
||||
)",
|
||||
"test:2: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("string.bad-keyword", TRUE);
|
||||
)",
|
||||
"test:2: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
// Unterminated C-style comment
|
||||
P(R"(
|
||||
/* comment
|
||||
)",
|
||||
"test:3: prefs parse error: unterminated /* comment"
|
||||
);
|
||||
|
||||
// Malformed comments.
|
||||
|
||||
P(R"(
|
||||
/ comment
|
||||
)",
|
||||
"test:2: prefs parse error: expected '/' or '*' after '/'"
|
||||
);
|
||||
|
||||
// Unexpected characters
|
||||
|
||||
P(R"(
|
||||
pref("unexpected.chars", &true);
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected character"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("unexpected.chars" : true);
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected character"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
@pref("unexpected.chars", true);
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected character"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref["unexpected.chars": true];
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected character"
|
||||
);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// All the parsing errors.
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
P(R"(
|
||||
"pref"("parse.error": true);
|
||||
)",
|
||||
"test:2: prefs parse error: expected pref specifier at start of pref definition"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref1("parse.error": true);
|
||||
)",
|
||||
"test:2: prefs parse error: expected '(' after pref specifier"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref(123: true);
|
||||
)",
|
||||
"test:2: prefs parse error: expected pref name after '('"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error" true);
|
||||
)",
|
||||
"test:2: prefs parse error: expected ',' after pref name"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", -true);
|
||||
)",
|
||||
"test:2: prefs parse error: expected integer literal after '-'"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", +"value");
|
||||
)",
|
||||
"test:2: prefs parse error: expected integer literal after '+'"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", pref);
|
||||
)",
|
||||
"test:2: prefs parse error: expected pref value after ','"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", true;
|
||||
)",
|
||||
"test:2: prefs parse error: expected ')' after pref value"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", true)
|
||||
pref("parse.error", true)
|
||||
)",
|
||||
"test:3: prefs parse error: expected ';' after ')'"
|
||||
);
|
||||
|
||||
// This is something we saw in practice with the old parser, which allowed
|
||||
// repeated semicolons.
|
||||
P(R"(
|
||||
pref("parse.error", true);;
|
||||
)",
|
||||
"test:2: prefs parse error: expected pref specifier at start of pref definition"
|
||||
);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Invalid syntax after various newline combinations, for the purpose of
|
||||
// testing that line numbers are correct.
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// In all of the following we have a \n, a \r, a \r\n, and then an error, so
|
||||
// the error is on line 4.
|
||||
|
||||
// XXX: these are temporarily commented out due to differing results on Windows
|
||||
#if 0
|
||||
|
||||
P(R"(
|
||||
|
||||
bad
|
||||
)",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
P(R"(#
|
||||
#
#
|
||||
bad
|
||||
)",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
P(R"(//
|
||||
//
//
|
||||
bad
|
||||
)",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
P(R"(/*
|
||||
|
||||
*/ bad
|
||||
)",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
// Note: the escape sequences do *not* affect the line number.
|
||||
P(R"(pref("foo\n
|
||||
\r
foo\r\n
|
||||
foo", bad
|
||||
)",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
);
|
||||
|
||||
#endif
|
||||
|
||||
// clang-format on
|
||||
}
|
|
@ -12,6 +12,7 @@ LOCAL_INCLUDES += [
|
|||
|
||||
UNIFIED_SOURCES = [
|
||||
'CallbackAndVarCacheOrder.cpp',
|
||||
'Parser.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
// Note: this file tests only valid syntax. See
|
||||
// modules/libpref/test/gtest/Parser.cpp for tests if invalid syntax.
|
||||
|
||||
#
|
||||
# comment
|
||||
# comment £
|
||||
//
|
||||
// comment
|
||||
// comment £
|
||||
/**/
|
||||
/* comment £ */
|
||||
/* comment
|
||||
* and
|
||||
some
|
||||
# more
|
||||
// comment */
|
||||
// comment /*
|
||||
# comment /*
|
||||
/* /* /* /* /* ...no nesting of C-style comments... */
|
||||
|
||||
// The following four lines have whitespace: \t, \v, \f, \r
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// This comment has the same whitespace:
|
||||
# This comment has other stuff: \n \r \t \v \f \r \a \b \? \' \" \\ \@
|
||||
/* This comment has more stuff: \x61 \u0061 \u1234 \uXYZ */
|
||||
|
||||
/*0*/ pref /*1*/ ( /*2*/ "comment1" /*3*/ , /*4*/ true /*5*/ ) /*6*/ ; /*7*/
|
||||
|
||||
pref # foo
|
||||
( // foo
|
||||
"comment2" /*
|
||||
foo
|
||||
*/,/*foo*/
|
||||
true#foo
|
||||
)//
|
||||
; /*7*/
|
||||
|
||||
pref
|
||||
(
|
||||
"spaced-out"
|
||||
,
|
||||
true
|
||||
)
|
||||
;
|
||||
|
||||
pref("pref", true);
|
||||
sticky_pref("sticky_pref", true);
|
||||
user_pref("user_pref", true);
|
||||
|
||||
pref("bool.true", true);
|
||||
pref("bool.false", false);
|
||||
|
||||
pref("int.0", 0);
|
||||
pref("int.1", 1);
|
||||
pref("int.123", 123);
|
||||
pref("int.+234", +234);
|
||||
pref("int.+ 345", + 345);
|
||||
// Note that both the prefname and value have tabs in them
|
||||
pref("int.-0", -0);
|
||||
pref("int.-1", -1);
|
||||
pref("int.- /* hmm */ 456", - /* hmm */ 456);
|
||||
pref("int.-\n567", -
|
||||
567);
|
||||
pref("int.INT_MAX-1", 2147483646);
|
||||
pref("int.INT_MAX", 2147483647);
|
||||
pref("int.INT_MIN+2", -2147483646);
|
||||
pref("int.INT_MIN+1", -2147483647);
|
||||
pref("int.INT_MIN", -2147483648);
|
||||
//pref("int.overflow.max", 2147483648); // overflows to -2147483648
|
||||
//pref("int.overflow.min", -2147483649); // overflows to 2147483647
|
||||
//pref("int.overflow.other", 4000000000); // overflows to -294967296
|
||||
//pref("int.overflow.another", 5000000000000000); // overflows to 937459712
|
||||
|
||||
pref("string.empty", "");
|
||||
pref("string.abc", "abc");
|
||||
pref("string.long", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
pref('string.single-quotes', '"abc"');
|
||||
pref("string.double-quotes", "'abc'");
|
||||
pref("string.weird-chars", "
");
|
||||
pref("string.escapes", "\" \' \\ \n \r");
|
||||
pref("string.x-escapes1", "Mozilla0\x4d\x6F\x7a\x69\x6c\x6C\x610");
|
||||
pref("string.x-escapes2", "A\x41 A_umlaut\xc4 y_umlaut\xff");
|
||||
pref("string.u-escapes1", "A\u0041 A_umlaut\u00c4 y_umlaut\u00ff0");
|
||||
pref("string.u-escapes2", "S_acute\u015a y_grave\u1Ef3");
|
||||
// CYCLONE is 0x1f300, GRINNING FACE is 0x1f600. We have to represent them via
|
||||
// surrogate pairs.
|
||||
pref("string.u-surrogates",
|
||||
"cyclone\uD83C\uDF00 grinning_face\uD83D\uDE00");
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/ */
|
||||
|
||||
function run_test() {
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const PREF_NAME = "testPref";
|
||||
|
||||
var ps = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService);
|
||||
var defaultPrefs = ps.getDefaultBranch(null);
|
||||
var prefs = ps.getBranch(null);
|
||||
|
||||
ps.resetPrefs();
|
||||
ps.readUserPrefsFromFile(do_get_file('data/testParser.js'));
|
||||
|
||||
Assert.equal(ps.getBoolPref("comment1"), true);
|
||||
Assert.equal(ps.getBoolPref("comment2"), true);
|
||||
Assert.equal(ps.getBoolPref("spaced-out"), true);
|
||||
|
||||
Assert.equal(ps.getBoolPref("pref"), true);
|
||||
Assert.equal(ps.getBoolPref("sticky_pref"), true);
|
||||
Assert.equal(ps.getBoolPref("user_pref"), true);
|
||||
|
||||
Assert.equal(ps.getBoolPref("bool.true"), true);
|
||||
Assert.equal(ps.getBoolPref("bool.false"), false);
|
||||
|
||||
Assert.equal(ps.getIntPref("int.0"), 0);
|
||||
Assert.equal(ps.getIntPref("int.1"), 1);
|
||||
Assert.equal(ps.getIntPref("int.123"), 123);
|
||||
Assert.equal(ps.getIntPref("int.+234"), 234);
|
||||
Assert.equal(ps.getIntPref("int.+ 345"), 345);
|
||||
Assert.equal(ps.getIntPref("int.-0"), -0);
|
||||
Assert.equal(ps.getIntPref("int.-1"), -1);
|
||||
Assert.equal(ps.getIntPref("int.- /* hmm */ 456"), -456);
|
||||
Assert.equal(ps.getIntPref("int.-\n567"), -567);
|
||||
Assert.equal(ps.getIntPref("int.INT_MAX-1"), 2147483646);
|
||||
Assert.equal(ps.getIntPref("int.INT_MAX"), 2147483647);
|
||||
Assert.equal(ps.getIntPref("int.INT_MIN+2"), -2147483646);
|
||||
Assert.equal(ps.getIntPref("int.INT_MIN+1"), -2147483647);
|
||||
Assert.equal(ps.getIntPref("int.INT_MIN"), -2147483648);
|
||||
|
||||
Assert.equal(ps.getCharPref("string.empty"), "");
|
||||
Assert.equal(ps.getCharPref("string.abc"), "abc");
|
||||
Assert.equal(ps.getCharPref("string.long"), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
Assert.equal(ps.getCharPref('string.single-quotes'), '"abc"');
|
||||
Assert.equal(ps.getCharPref("string.double-quotes"), "'abc'");
|
||||
Assert.equal(ps.getCharPref("string.weird-chars"),
|
||||
"\x0d \x09 \x0b \x0c \x06 \x16");
|
||||
Assert.equal(ps.getCharPref("string.escapes"), "\" \' \\ \n \r");
|
||||
|
||||
// This one is ASCII, so we can use getCharPref() and getStringPref
|
||||
// interchangeably.
|
||||
Assert.equal(ps.getCharPref("string.x-escapes1"),
|
||||
"Mozilla0\x4d\x6F\x7a\x69\x6c\x6C\x610");
|
||||
Assert.equal(ps.getStringPref("string.x-escapes1"),
|
||||
"Mozilla0Mozilla0");
|
||||
|
||||
// This one has chars with value > 127, so it's not valid UTF8, so we can't
|
||||
// use getStringPref on it.
|
||||
Assert.equal(ps.getCharPref("string.x-escapes2"),
|
||||
"AA A_umlaut\xc4 y_umlaut\xff");
|
||||
|
||||
// The following strings use \uNNNN escapes, which are UTF16 code points.
|
||||
// libpref stores them internally as UTF8 byte sequences. In each case we get
|
||||
// the string in two ways:
|
||||
// - getStringPref() interprets it as UTF8, which is then converted to UTF16
|
||||
// in JS. I.e. code points that are multiple bytes in UTF8 become a single
|
||||
// 16-bit char in JS (except for the non-BMP chars, which become a 16-bit
|
||||
// surrogate pair).
|
||||
// - getCharPref() interprets it as Latin1, which is then converted to UTF16
|
||||
// in JS. I.e. code points that are multiple bytes in UTF8 become multiple
|
||||
// 16-bit chars in JS.
|
||||
|
||||
Assert.equal(ps.getStringPref("string.u-escapes1"),
|
||||
"A\u0041 A_umlaut\u00c4 y_umlaut\u00ff0");
|
||||
Assert.equal(ps.getCharPref("string.u-escapes1"),
|
||||
"A\x41 A_umlaut\xc3\x84 y_umlaut\xc3\xbf0");
|
||||
|
||||
Assert.equal(ps.getStringPref("string.u-escapes2"),
|
||||
"S_acute\u015a y_grave\u1Ef3");
|
||||
Assert.equal(ps.getCharPref("string.u-escapes2"),
|
||||
"S_acute\xc5\x9a y_grave\xe1\xbb\xb3");
|
||||
|
||||
Assert.equal(ps.getStringPref("string.u-surrogates"),
|
||||
"cyclone\uD83C\uDF00 grinning_face\uD83D\uDE00");
|
||||
Assert.equal(ps.getCharPref("string.u-surrogates"),
|
||||
"cyclone\xF0\x9F\x8C\x80 grinning_face\xF0\x9F\x98\x80");
|
||||
}
|
|
@ -16,3 +16,5 @@ support-files = data/testPrefSticky.js data/testPrefStickyUser.js
|
|||
[test_dirtyPrefs.js]
|
||||
[test_libPrefs.js]
|
||||
[test_bug1354613.js]
|
||||
[test_parser.js]
|
||||
support-files = data/testParser.js
|
||||
|
|
|
@ -134,7 +134,6 @@ reftest-gpu:
|
|||
run-on-projects:
|
||||
by-test-platform:
|
||||
windows10.*: []
|
||||
windows8-64.*: []
|
||||
default: built-projects
|
||||
instance-size: default
|
||||
virtualization: virtual-with-gpu
|
||||
|
@ -148,15 +147,13 @@ reftest-no-accel:
|
|||
suite: reftest/reftest-no-accel
|
||||
treeherder-symbol: R(Ru)
|
||||
virtualization: virtual-with-gpu
|
||||
worker-type:
|
||||
run-on-projects:
|
||||
by-test-platform:
|
||||
windows10-64.*: buildbot-bridge/buildbot-bridge
|
||||
default: null
|
||||
windows10.*: []
|
||||
default: built-projects
|
||||
chunks:
|
||||
by-test-platform:
|
||||
macosx.*: 1
|
||||
windows10-64.*/debug: 2
|
||||
windows10-64.*/opt: 1
|
||||
default: 8
|
||||
e10s:
|
||||
by-test-platform:
|
||||
|
|
|
@ -174,7 +174,6 @@ class CodeCoverageMixin(object):
|
|||
jsvm_files = [os.path.join(self.jsvm_dir, e) for e in os.listdir(self.jsvm_dir)]
|
||||
rewriter = LcovFileRewriter('chrome-map.json')
|
||||
rewriter.rewrite_files(jsvm_files, 'jsvm_lcov_output.info', '')
|
||||
assert os.path.getsize('jsvm_lcov_output.info') > 0
|
||||
|
||||
# Package JSVM coverage data.
|
||||
file_path_jsvm = os.path.join(dirs['abs_blob_upload_dir'], 'code-coverage-jsvm.zip')
|
||||
|
|
|
@ -17,7 +17,11 @@ class ConfigurationError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
FAR_IN_FUTURE = 7258114800
|
||||
# Set places maintenance far in the future (the maximum time possible in an
|
||||
# int32_t) to avoid it kicking in during tests. The maintenance can take a
|
||||
# relatively long time which may cause unnecessary intermittents and slow
|
||||
# things down. This, like many things, will stop working correctly in 2038.
|
||||
FAR_IN_FUTURE = 2147483647
|
||||
|
||||
DEFAULTS = dict(
|
||||
# args to pass to browser
|
||||
|
|
|
@ -596,6 +596,7 @@ dependencies = [
|
|||
"netwerk_helper 0.0.1",
|
||||
"nserror 0.1.0",
|
||||
"nsstring 0.1.0",
|
||||
"prefs_parser 0.0.1",
|
||||
"rust_url_capi 0.0.1",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"u2fhid 0.1.0",
|
||||
|
@ -1043,6 +1044,10 @@ name = "precomputed-hash"
|
|||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "prefs_parser"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "procedural-masquerade"
|
||||
version = "0.1.1"
|
||||
|
|
|
@ -594,6 +594,7 @@ dependencies = [
|
|||
"netwerk_helper 0.0.1",
|
||||
"nserror 0.1.0",
|
||||
"nsstring 0.1.0",
|
||||
"prefs_parser 0.0.1",
|
||||
"rust_url_capi 0.0.1",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"u2fhid 0.1.0",
|
||||
|
@ -1030,6 +1031,10 @@ name = "precomputed-hash"
|
|||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "prefs_parser"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "procedural-masquerade"
|
||||
version = "0.1.1"
|
||||
|
|
|
@ -12,6 +12,7 @@ nsstring = { path = "../../../../servo/support/gecko/nsstring" }
|
|||
nserror = { path = "../../../../xpcom/rust/nserror" }
|
||||
netwerk_helper = { path = "../../../../netwerk/base/rust-helper" }
|
||||
xpcom = { path = "../../../../xpcom/rust/xpcom" }
|
||||
prefs_parser = { path = "../../../../modules/libpref/parser" }
|
||||
rust_url_capi = { path = "../../../../netwerk/base/rust-url-capi" }
|
||||
webrender_bindings = { path = "../../../../gfx/webrender_bindings", optional = true }
|
||||
cubeb-pulse = { path = "../../../../media/libcubeb/cubeb-pulse-rs", optional = true, features=["pulse-dlopen"] }
|
||||
|
|
|
@ -9,8 +9,9 @@ extern crate mp4parse_capi;
|
|||
extern crate nsstring;
|
||||
extern crate nserror;
|
||||
extern crate xpcom;
|
||||
extern crate rust_url_capi;
|
||||
extern crate netwerk_helper;
|
||||
extern crate prefs_parser;
|
||||
extern crate rust_url_capi;
|
||||
#[cfg(feature = "quantum_render")]
|
||||
extern crate webrender_bindings;
|
||||
#[cfg(feature = "cubeb_pulse_rust")]
|
||||
|
|
Загрузка…
Ссылка в новой задаче