diff --git a/browser/components/enterprisepolicies/tests/browser/browser.ini b/browser/components/enterprisepolicies/tests/browser/browser.ini index 5c8680792ffa..010f4a4f5650 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser.ini +++ b/browser/components/enterprisepolicies/tests/browser/browser.ini @@ -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] diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.js index 7d1f5d6f3c68..58e261644df3 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.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"; diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_popups_cookies_addons_flash.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_popups_cookies_addons_flash.js index a2aeaad200ae..a7951d286243 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policies_popups_cookies_addons_flash.js +++ b/browser/components/enterprisepolicies/tests/browser/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"; diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js index 75327e370a21..7f5b32ab1bfe 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_setAndLockPref_API.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"; @@ -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": { diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_policies.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_policies.js index 947f4db77237..f75f16e817aa 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_policies.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_policies.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"; @@ -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": { diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js b/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js index 74cdec0b7554..ec7d7f64eec8 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.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"; diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_block_set_desktop_background.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_set_desktop_background.js index 6f2e2b8a3fc6..72a4365a832e 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_block_set_desktop_background.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_block_set_desktop_background.js @@ -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() { diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_default_browser_check.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_default_browser_check.js index ce00bac19bf7..afa1ce49dae8 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_default_browser_check.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_default_browser_check.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"; 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"); diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_display_bookmarks.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_bookmarks.js index f902dbadbbf7..88badb8a1d62 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_display_bookmarks.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_bookmarks.js @@ -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() { diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_display_menu.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_menu.js index 3adb09c349ab..b0bc2787984a 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_display_menu.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_display_menu.js @@ -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() { diff --git a/browser/components/enterprisepolicies/tests/browser/config_block_set_desktop_background.json b/browser/components/enterprisepolicies/tests/browser/config_block_set_desktop_background.json deleted file mode 100644 index 2823f00b9865..000000000000 --- a/browser/components/enterprisepolicies/tests/browser/config_block_set_desktop_background.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "policies": { - "block_set_desktop_background": true - } -} diff --git a/browser/components/enterprisepolicies/tests/browser/config_display_bookmarks.json b/browser/components/enterprisepolicies/tests/browser/config_display_bookmarks.json deleted file mode 100644 index ac745ffac2df..000000000000 --- a/browser/components/enterprisepolicies/tests/browser/config_display_bookmarks.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "policies": { - "display_bookmarks_toolbar": true - } -} diff --git a/browser/components/enterprisepolicies/tests/browser/config_display_menu.json b/browser/components/enterprisepolicies/tests/browser/config_display_menu.json deleted file mode 100644 index cae2ff67e472..000000000000 --- a/browser/components/enterprisepolicies/tests/browser/config_display_menu.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "policies": { - "display_menu_bar": true - } -} diff --git a/browser/components/enterprisepolicies/tests/browser/config_dont_check_default_browser.json b/browser/components/enterprisepolicies/tests/browser/config_dont_check_default_browser.json deleted file mode 100644 index ef049be71609..000000000000 --- a/browser/components/enterprisepolicies/tests/browser/config_dont_check_default_browser.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "policies": { - "dont_check_default_browser": true - } -} diff --git a/browser/components/enterprisepolicies/tests/browser/config_setAndLockPref.json b/browser/components/enterprisepolicies/tests/browser/config_setAndLockPref.json deleted file mode 100644 index a9cb089abb79..000000000000 --- a/browser/components/enterprisepolicies/tests/browser/config_setAndLockPref.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "policies": { - "bool_policy": true, - "int_policy": 42, - "string_policy": "policies test 2" - } -} diff --git a/browser/components/enterprisepolicies/tests/browser/config_simple_policies.json b/browser/components/enterprisepolicies/tests/browser/config_simple_policies.json deleted file mode 100644 index 0fc759605f79..000000000000 --- a/browser/components/enterprisepolicies/tests/browser/config_simple_policies.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "policies": { - "simple_policy0": true, - "simple_policy1": true, - "simple_policy2": true, - "simple_policy3": false - } -} diff --git a/browser/components/enterprisepolicies/tests/browser/head.js b/browser/components/enterprisepolicies/tests/browser/head.js index 79900924d938..f30242f485f9 100644 --- a/browser/components/enterprisepolicies/tests/browser/head.js +++ b/browser/components/enterprisepolicies/tests/browser/head.js @@ -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; diff --git a/devtools/client/themes/rules.css b/devtools/client/themes/rules.css index f4eabc39e0e6..5d6fc004c454 100644 --- a/devtools/client/themes/rules.css +++ b/devtools/client/themes/rules.css @@ -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 > * { diff --git a/dom/media/MediaData.cpp b/dom/media/MediaData.cpp index ed3e0b6ec946..445475163687 100644 --- a/dom/media/MediaData.cpp +++ b/dom/media/MediaData.cpp @@ -548,4 +548,10 @@ MediaRawDataWriter::Size() return mTarget->Size(); } +void +MediaRawDataWriter::PopFront(size_t aSize) +{ + mTarget->mBuffer.PopFront(aSize); +} + } // namespace mozilla diff --git a/dom/media/MediaData.h b/dom/media/MediaData.h index 12c9642fe9a5..2b5e8ff8fc8d 100644 --- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -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; diff --git a/dom/media/platforms/agnostic/bytestreams/Adts.cpp b/dom/media/platforms/agnostic/bytestreams/Adts.cpp index 11384f969235..6580d2d666df 100644 --- a/dom/media/platforms/agnostic/bytestreams/Adts.cpp +++ b/dom/media/platforms/agnostic/bytestreams/Adts.cpp @@ -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 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; +} } diff --git a/dom/media/platforms/agnostic/bytestreams/Adts.h b/dom/media/platforms/agnostic/bytestreams/Adts.h index b2a03522aba8..df50fa166629 100644 --- a/dom/media/platforms/agnostic/bytestreams/Adts.h +++ b/dom/media/platforms/agnostic/bytestreams/Adts.h @@ -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); }; } diff --git a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp index 67a328b21818..49591db571c8 100644 --- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp +++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp @@ -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 namespace mozilla { @@ -27,20 +30,52 @@ extern already_AddRefed 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(static_cast(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 { public: - EMEDecryptor(MediaDataDecoder* aDecoder, CDMProxy* aProxy, - TaskQueue* aDecodeTaskQueue, TrackInfo::TrackType aType, - MediaEventProducer* aOnWaitingForKey) + EMEDecryptor(MediaDataDecoder* aDecoder, + CDMProxy* aProxy, + TaskQueue* aDecodeTaskQueue, + TrackInfo::TrackType aType, + MediaEventProducer* aOnWaitingForKey, + UniquePtr 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 mDrainPromise; MozPromiseHolder mFlushPromise; MozPromiseRequestHolder mDecodeRequest; - + UniquePtr mADTSSampleConverter; bool mIsShutdown; }; @@ -384,14 +438,25 @@ EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams) return m->CreateAudioDecoder(aParams); } + UniquePtr converter = nullptr; + if (MP4Decoder::IsAAC(aParams.mConfig.mMimeType)) { + // The CDM expects encrypted AAC to be in ADTS format. + // See bug 1433344. + converter = MakeUnique(aParams.AudioConfig()); + } + RefPtr decoder(mPDM->CreateDecoder(aParams)); if (!decoder) { return nullptr; } - RefPtr emeDecoder(new EMEDecryptor( - decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue(), - aParams.mType, aParams.mOnWaitingForKeyEvent)); + RefPtr emeDecoder( + new EMEDecryptor(decoder, + mProxy, + AbstractThread::GetCurrent()->AsTaskQueue(), + aParams.mType, + aParams.mOnWaitingForKeyEvent, + Move(converter))); return emeDecoder.forget(); } diff --git a/dom/plugins/ipc/FunctionBroker.h b/dom/plugins/ipc/FunctionBroker.h index 376591c94f52..695258464477 100644 --- a/dom/plugins/ipc/FunctionBroker.h +++ b/dom/plugins/ipc/FunctionBroker.h @@ -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()); }; diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 5a3d72d4e6f2..d8e32dd318c8 100644 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -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 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). diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h index 8a14da44fa63..ec3c937e20c3 100644 --- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -605,6 +605,8 @@ private: TakeFullMinidumpCallback mTakeFullMinidumpCallback; TerminateChildProcessCallback mTerminateChildProcessCallback; + + bool mIsCleaningFromTimeout; }; } // namespace plugins diff --git a/modules/libpref/Preferences.cpp b/modules/libpref/Preferences.cpp index 9de57c0caca6..9ccdf8607e8b 100644 --- a/modules/libpref/Preferences.cpp +++ b/modules/libpref/Preferences.cpp @@ -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* 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); + const nsCString& aBuf) + { + sNumPrefs = 0; + bool ok = prefs_parser_parse( + aPath, aBuf.get(), aBuf.Length(), HandlePref, HandleError); + if (!ok) { + return false; + } - bool GrowBuf(); + uint32_t loadTime_us = (TimeStamp::Now() - aStartTime).ToMicroseconds(); - void HandleValue(const char* aPrefName, - PrefType aType, - PrefValue aValue, - bool aIsDefault, - bool aIsSticky); + // 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); - void ReportProblem(const char* aMessage, int aLine, bool aError); + return true; + } private: - // Pref parser states. - enum class State + static void HandlePref(const char* aPrefName, + PrefType aType, + PrefValueKind aKind, + PrefValue aValue, + bool aIsSticky) { - eInit, - eMatchString, - eUntilName, - eQuotedString, - eUntilComma, - eUntilValue, - eIntValue, - eCommentMaybeStart, - eCommentBlock, - eCommentBlockMaybeEnd, - eEscapeSequence, - eHexEscape, - eUTF16LowSurrogate, - eUntilOpenParen, - eUntilCloseParen, - eUntilSemicolon, - eUntilEOL - }; + sNumPrefs++; + pref_SetPref( + aPrefName, aType, aKind, aValue, aIsSticky, /* fromFile */ true); + } - static const int kUTF16EscapeNumDigits = 4; - static const int kHexEscapeNumDigits = 2; - static const int KBitsPerHexDigit = 4; + static void HandleError(const char* aMsg) + { + nsresult rv; + nsCOMPtr console = + do_GetService("@mozilla.org/consoleservice;1", &rv); + if (NS_SUCCEEDED(rv)) { + console->LogStringMessage(NS_ConvertUTF8toUTF16(aMsg).get()); + } else { + printf_stderr("%s\n", aMsg); + } + NS_WARNING(aMsg); + } - 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 mVtype; // pref value type - bool mIsDefault; // true if (default) pref - bool mIsSticky; // true if (sticky) pref + // 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; }; -// 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() +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) { - 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) { - return false; - } - - mLbCur = mLb + curPos; - mLbEnd = mLb + bufLen; - mVb = mLb + valPos; - - return true; } +static char* gTestParseErrorMsg; + +static void +TestParseErrorHandleError(const char* aMsg) +{ + // aMsg's lifetime is shorter than we need, so duplicate it. + gTestParseErrorMsg = moz_xstrdup(aMsg); +} + +// Keep this in sync with the declaration in test/gtest/Parser.cpp. void -Parser::HandleValue(const char* aPrefName, - PrefType aType, - PrefValue aValue, - bool aIsDefault, - bool aIsSticky) +TestParseError(const char* aText, nsCString& aErrorMsg) { - PrefValueKind kind = - aIsDefault ? PrefValueKind::Default : PrefValueKind::User; - pref_SetPref(aPrefName, aType, kind, aValue, aIsSticky, /* fromFile */ true); -} + prefs_parser_parse("test", + aText, + strlen(aText), + TestParseErrorHandlePref, + TestParseErrorHandleError); -// Report an error or a warning. If not specified, just dump to stderr. -void -Parser::ReportProblem(const char* aMessage, int aLine, bool aError) -{ - nsPrintfCString message("** Preference parsing %s (line %d) = %s **\n", - (aError ? "error" : "warning"), - aLine, - aMessage); - nsresult rv; - nsCOMPtr console = - do_GetService("@mozilla.org/consoleservice;1", &rv); - if (NS_SUCCEEDED(rv)) { - console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get()); - } else { - printf_stderr("%s", message.get()); - } -} - -// 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 = -// HT = -// LF = -// VT = -// FF = -// CR = -// comment-block = -// comment-line = -// bcomment-line = -// -bool -Parser::Parse(const nsCString& aName, - const TimeStamp& aStartTime, - const char* aBuf, - size_t aBufLen) -{ - // 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; - - // 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]; - } - - // Push the non-hex character back for re-parsing. (++aBuf at the top - // of the loop keeps this safe.) - --aBuf; - state = State::eQuotedString; - continue; - } - - // 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; } diff --git a/modules/libpref/Preferences.h b/modules/libpref/Preferences.h index 7d939e5b7682..59a69bfdf30d 100644 --- a/modules/libpref/Preferences.h +++ b/modules/libpref/Preferences.h @@ -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 diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 8489b8b712d0..a988a29397c9 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -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", ""); diff --git a/modules/libpref/parser/Cargo.toml b/modules/libpref/parser/Cargo.toml new file mode 100644 index 000000000000..e7d4ee61e34a --- /dev/null +++ b/modules/libpref/parser/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "prefs_parser" +version = "0.0.1" +authors = ["Nicholas Nethercote "] + +[dependencies] diff --git a/modules/libpref/parser/src/lib.rs b/modules/libpref/parser/src/lib.rs new file mode 100644 index 000000000000..58dd549e6917 --- /dev/null +++ b/modules/libpref/parser/src/lib.rs @@ -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. +//! +//! = * +//! = "(" "," ")" ";" +//! = "user_pref" | "pref" | "sticky_pref" +//! = +//! = | "true" | "false" | +//! = ? +//! = "+" | "-" +//! = [0-9]+ (and cannot be followed by [A-Za-z_]) +//! = +//! 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 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; + } + + // + 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"); + } + + // + 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"); + } + + // + 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 { + 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) -> 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 { + 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) -> 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; + } +} diff --git a/modules/libpref/test/gtest/CallbackAndVarCacheOrder.cpp b/modules/libpref/test/gtest/CallbackAndVarCacheOrder.cpp index 518df407b1ae..40eeb349cfe0 100644 --- a/modules/libpref/test/gtest/CallbackAndVarCacheOrder.cpp +++ b/modules/libpref/test/gtest/CallbackAndVarCacheOrder.cpp @@ -173,7 +173,7 @@ TEST(CallbackAndVarCacheOrder, AtomicBoolRelaxed) { RunTest>( "test_pref.atomic_bool.1", "test_pref.atomic_bool.2", false, true); - } +} TEST(CallbackAndVarCacheOrder, AtomicBoolReleaseAcquire) { diff --git a/modules/libpref/test/gtest/Parser.cpp b/modules/libpref/test/gtest/Parser.cpp new file mode 100644 index 000000000000..2e22fbc8964d --- /dev/null +++ b/modules/libpref/test/gtest/Parser.cpp @@ -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 +} diff --git a/modules/libpref/test/gtest/moz.build b/modules/libpref/test/gtest/moz.build index 8fe93195931d..cd6a7dffda56 100644 --- a/modules/libpref/test/gtest/moz.build +++ b/modules/libpref/test/gtest/moz.build @@ -12,6 +12,7 @@ LOCAL_INCLUDES += [ UNIFIED_SOURCES = [ 'CallbackAndVarCacheOrder.cpp', + 'Parser.cpp', ] if CONFIG['CC_TYPE'] in ('clang', 'gcc'): diff --git a/modules/libpref/test/unit/data/testParser.js b/modules/libpref/test/unit/data/testParser.js new file mode 100644 index 000000000000..932e195e7cdf --- /dev/null +++ b/modules/libpref/test/unit/data/testParser.js @@ -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"); + diff --git a/modules/libpref/test/unit/test_parser.js b/modules/libpref/test/unit/test_parser.js new file mode 100644 index 000000000000..7bfe396ca77a --- /dev/null +++ b/modules/libpref/test/unit/test_parser.js @@ -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"); +} diff --git a/modules/libpref/test/unit/xpcshell.ini b/modules/libpref/test/unit/xpcshell.ini index 446e97d445c8..6318da1771eb 100644 --- a/modules/libpref/test/unit/xpcshell.ini +++ b/modules/libpref/test/unit/xpcshell.ini @@ -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 diff --git a/taskcluster/ci/test/reftest.yml b/taskcluster/ci/test/reftest.yml index 94b30bc24e04..8ddd983d88aa 100644 --- a/taskcluster/ci/test/reftest.yml +++ b/taskcluster/ci/test/reftest.yml @@ -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: diff --git a/testing/mozharness/mozharness/mozilla/testing/codecoverage.py b/testing/mozharness/mozharness/mozilla/testing/codecoverage.py index 01700e2b5ac5..a7c611f1434b 100644 --- a/testing/mozharness/mozharness/mozilla/testing/codecoverage.py +++ b/testing/mozharness/mozharness/mozilla/testing/codecoverage.py @@ -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') diff --git a/testing/talos/talos/config.py b/testing/talos/talos/config.py index 417bdbc63328..1737d2b0e21e 100644 --- a/testing/talos/talos/config.py +++ b/testing/talos/talos/config.py @@ -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 diff --git a/toolkit/library/gtest/rust/Cargo.lock b/toolkit/library/gtest/rust/Cargo.lock index 9586923ece62..38219b4d1400 100644 --- a/toolkit/library/gtest/rust/Cargo.lock +++ b/toolkit/library/gtest/rust/Cargo.lock @@ -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" diff --git a/toolkit/library/rust/Cargo.lock b/toolkit/library/rust/Cargo.lock index 9b2076891be2..c3a96ccec3c1 100644 --- a/toolkit/library/rust/Cargo.lock +++ b/toolkit/library/rust/Cargo.lock @@ -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" diff --git a/toolkit/library/rust/shared/Cargo.toml b/toolkit/library/rust/shared/Cargo.toml index 9fac5fe654b5..6037bcf9404e 100644 --- a/toolkit/library/rust/shared/Cargo.toml +++ b/toolkit/library/rust/shared/Cargo.toml @@ -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"] } diff --git a/toolkit/library/rust/shared/lib.rs b/toolkit/library/rust/shared/lib.rs index 535269c8e4fb..5152f6f74462 100644 --- a/toolkit/library/rust/shared/lib.rs +++ b/toolkit/library/rust/shared/lib.rs @@ -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")]