зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to mozilla-central. a=merge
This commit is contained in:
Коммит
082f8e6d89
|
@ -19,6 +19,8 @@ skip-if = (os == 'win' && ccov) # Bug 1423667
|
|||
skip-if = (os == 'win' && ccov) # Bug 1423667
|
||||
[browser_bookmark_folder_moveability.js]
|
||||
skip-if = (os == 'win' && ccov) # Bug 1423667
|
||||
[browser_bookmark_private_window.js]
|
||||
skip-if = (os == 'win' && ccov) # Bug 1423667
|
||||
[browser_bookmark_remove_tags.js]
|
||||
skip-if = (os == 'win' && ccov) # Bug 1423667
|
||||
[browser_bookmarklet_windowOpen.js]
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test that the a website can be bookmarked from a private window.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "about:buildconfig";
|
||||
|
||||
// Cleanup.
|
||||
registerCleanupFunction(async () => {
|
||||
await PlacesUtils.bookmarks.eraseEverything();
|
||||
});
|
||||
|
||||
add_task(async function test_add_bookmark_from_private_window() {
|
||||
let win = await BrowserTestUtils.openNewBrowserWindow({private: true});
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, TEST_URL);
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
// Open the bookmark panel.
|
||||
let bookmarkPanel = win.document.getElementById("editBookmarkPanel");
|
||||
let shownPromise = promisePopupShown(bookmarkPanel);
|
||||
let bookmarkStar = win.BookmarkingUI.star;
|
||||
bookmarkStar.click();
|
||||
await shownPromise;
|
||||
|
||||
// Check if the bookmark star changes its state after click.
|
||||
Assert.equal(bookmarkStar.getAttribute("starred"), "true", "Bookmark star changed its state correctly.");
|
||||
|
||||
// Close the bookmark panel.
|
||||
let hiddenPromise = promisePopupHidden(bookmarkPanel);
|
||||
let doneButton = win.document.getElementById("editBookmarkPanelDoneButton");
|
||||
doneButton.click();
|
||||
await hiddenPromise;
|
||||
|
||||
let bm = await PlacesUtils.bookmarks.fetch({url: TEST_URL});
|
||||
Assert.equal(bm.url, TEST_URL, "The bookmark was successfully saved in the database.");
|
||||
});
|
|
@ -23,7 +23,4 @@ const ThemeVariableMap = [
|
|||
["--lwt-toolbarbutton-icon-fill-attention", "icon_attention_color"],
|
||||
["--lwt-toolbarbutton-hover-background", "button_background_hover"],
|
||||
["--lwt-toolbarbutton-active-background", "button_background_active"],
|
||||
["--arrowpanel-background", "popup"],
|
||||
["--arrowpanel-color", "popup_text"],
|
||||
["--arrowpanel-border-color", "popup_border"],
|
||||
];
|
||||
|
|
|
@ -951,18 +951,6 @@ panelmultiview .toolbaritem-combined-buttons > spacer.after-label {
|
|||
/* Unset the min-height constraint, because that works better for a text-only button. */
|
||||
#appMenu-zoomReset-button {
|
||||
min-height: unset;
|
||||
border: 1px solid var(--panel-separator-color);
|
||||
border-radius: 10000px;
|
||||
padding: 1px 8px;
|
||||
background-color: var(--arrowpanel-dimmed);
|
||||
}
|
||||
|
||||
#appMenu-zoomReset-button@buttonStateHover@ {
|
||||
background-color: var(--arrowpanel-dimmed-further);
|
||||
}
|
||||
|
||||
#appMenu-zoomReset-button@buttonStateActive@ {
|
||||
background-color: var(--arrowpanel-dimmed-even-further);
|
||||
}
|
||||
|
||||
#appMenu-zoomReset-button > .toolbarbutton-text {
|
||||
|
@ -980,6 +968,14 @@ panelmultiview .toolbaritem-combined-buttons > spacer.after-label {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Using this selector, because this way the hover and active selectors will apply properly. */
|
||||
.PanelUI-subView .toolbaritem-combined-buttons > .subviewbutton:not(.subviewbutton-iconic) {
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #e1e1e1;
|
||||
border-radius: 10000px;
|
||||
padding: 1px 8px;
|
||||
}
|
||||
|
||||
.toolbaritem-combined-buttons > .subviewbutton:not(.subviewbutton-iconic) > .toolbarbutton-text {
|
||||
font-size: 1em;
|
||||
padding-inline-start: 0;
|
||||
|
|
|
@ -78,6 +78,7 @@ class nsGeolocationRequest final
|
|||
GeoPositionErrorCallback aErrorCallback,
|
||||
UniquePtr<PositionOptions>&& aOptions,
|
||||
uint8_t aProtocolType,
|
||||
nsIEventTarget* aMainThreadTarget,
|
||||
bool aWatchPositionRequest = false,
|
||||
bool aIsHandlingUserInput = false,
|
||||
int32_t aWatchId = 0);
|
||||
|
@ -136,6 +137,7 @@ class nsGeolocationRequest final
|
|||
bool mShutdown;
|
||||
nsCOMPtr<nsIContentPermissionRequester> mRequester;
|
||||
uint8_t mProtocolType;
|
||||
nsCOMPtr<nsIEventTarget> mMainThreadTarget;
|
||||
};
|
||||
|
||||
static UniquePtr<PositionOptions>
|
||||
|
@ -307,6 +309,7 @@ nsGeolocationRequest::nsGeolocationRequest(Geolocation* aLocator,
|
|||
GeoPositionErrorCallback aErrorCallback,
|
||||
UniquePtr<PositionOptions>&& aOptions,
|
||||
uint8_t aProtocolType,
|
||||
nsIEventTarget* aMainThreadTarget,
|
||||
bool aWatchPositionRequest,
|
||||
bool aIsHandlingUserInput,
|
||||
int32_t aWatchId)
|
||||
|
@ -318,7 +321,8 @@ nsGeolocationRequest::nsGeolocationRequest(Geolocation* aLocator,
|
|||
mLocator(aLocator),
|
||||
mWatchId(aWatchId),
|
||||
mShutdown(false),
|
||||
mProtocolType(aProtocolType)
|
||||
mProtocolType(aProtocolType),
|
||||
mMainThreadTarget(aMainThreadTarget)
|
||||
{
|
||||
if (nsCOMPtr<nsPIDOMWindowInner> win =
|
||||
do_QueryReferent(mLocator->GetOwner())) {
|
||||
|
@ -621,7 +625,7 @@ NS_IMETHODIMP
|
|||
nsGeolocationRequest::Update(nsIDOMGeoPosition* aPosition)
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> ev = new RequestSendLocationEvent(aPosition, this);
|
||||
NS_DispatchToMainThread(ev);
|
||||
mMainThreadTarget->Dispatch(ev.forget());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1247,6 +1251,15 @@ Geolocation::GetCurrentPosition(PositionCallback& aCallback,
|
|||
}
|
||||
}
|
||||
|
||||
static nsIEventTarget* MainThreadTarget(Geolocation* geo)
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryReferent(geo->GetOwner());
|
||||
if (!window) {
|
||||
return GetMainThreadEventTarget();
|
||||
}
|
||||
return nsGlobalWindowInner::Cast(window)->EventTargetFor(mozilla::TaskCategory::Other);
|
||||
}
|
||||
|
||||
nsresult
|
||||
Geolocation::GetCurrentPosition(GeoPositionCallback callback,
|
||||
GeoPositionErrorCallback errorCallback,
|
||||
|
@ -1263,15 +1276,16 @@ Geolocation::GetCurrentPosition(GeoPositionCallback callback,
|
|||
Telemetry::Accumulate(Telemetry::GEOLOCATION_GETCURRENTPOSITION_SECURE_ORIGIN,
|
||||
static_cast<uint8_t>(mProtocolType));
|
||||
|
||||
nsIEventTarget* target = MainThreadTarget(this);
|
||||
RefPtr<nsGeolocationRequest> request =
|
||||
new nsGeolocationRequest(this, Move(callback), Move(errorCallback),
|
||||
Move(options), static_cast<uint8_t>(mProtocolType),
|
||||
Move(options), static_cast<uint8_t>(mProtocolType), target,
|
||||
false, EventStateManager::IsHandlingUserInput());
|
||||
|
||||
if (!sGeoEnabled || ShouldBlockInsecureRequests() ||
|
||||
nsContentUtils::ResistFingerprinting(aCallerType)) {
|
||||
nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request);
|
||||
NS_DispatchToMainThread(ev);
|
||||
target->Dispatch(ev.forget());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1292,7 +1306,7 @@ Geolocation::GetCurrentPosition(GeoPositionCallback callback,
|
|||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(true, request);
|
||||
NS_DispatchToMainThread(ev);
|
||||
target->Dispatch(ev.forget());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1350,16 +1364,17 @@ Geolocation::WatchPosition(GeoPositionCallback aCallback,
|
|||
// The watch ID:
|
||||
*aRv = mLastWatchId++;
|
||||
|
||||
nsIEventTarget* target = MainThreadTarget(this);
|
||||
RefPtr<nsGeolocationRequest> request =
|
||||
new nsGeolocationRequest(this, Move(aCallback), Move(aErrorCallback),
|
||||
Move(aOptions),
|
||||
static_cast<uint8_t>(mProtocolType), true,
|
||||
static_cast<uint8_t>(mProtocolType), target, true,
|
||||
EventStateManager::IsHandlingUserInput(), *aRv);
|
||||
|
||||
if (!sGeoEnabled || ShouldBlockInsecureRequests() ||
|
||||
nsContentUtils::ResistFingerprinting(aCallerType)) {
|
||||
nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(false, request);
|
||||
NS_DispatchToMainThread(ev);
|
||||
target->Dispatch(ev.forget());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1453,15 +1468,16 @@ Geolocation::NotifyAllowedRequest(nsGeolocationRequest* aRequest)
|
|||
bool
|
||||
Geolocation::RegisterRequestWithPrompt(nsGeolocationRequest* request)
|
||||
{
|
||||
nsIEventTarget* target = MainThreadTarget(this);
|
||||
if (Preferences::GetBool("geo.prompt.testing", false)) {
|
||||
bool allow = Preferences::GetBool("geo.prompt.testing.allow", false);
|
||||
nsCOMPtr<nsIRunnable> ev = new RequestAllowEvent(allow, request);
|
||||
NS_DispatchToMainThread(ev);
|
||||
target->Dispatch(ev.forget());
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRunnable> ev = new RequestPromptEvent(request, mOwner);
|
||||
NS_DispatchToMainThread(ev);
|
||||
target->Dispatch(ev.forget());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -502,7 +502,12 @@ Promise::ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise)
|
|||
win ? win->AsInner()->WindowID() : 0);
|
||||
|
||||
// Now post an event to do the real reporting async
|
||||
NS_DispatchToMainThread(new AsyncErrorReporter(xpcReport));
|
||||
RefPtr<nsIRunnable> event = new AsyncErrorReporter(xpcReport);
|
||||
if (win) {
|
||||
win->Dispatch(mozilla::TaskCategory::Other, event.forget());
|
||||
} else {
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -1615,7 +1615,7 @@ nsXMLContentSink::ContinueInterruptedParsingAsync()
|
|||
this,
|
||||
&nsXMLContentSink::ContinueInterruptedParsingIfEnabled);
|
||||
|
||||
NS_DispatchToCurrentThread(ev);
|
||||
mDocument->Dispatch(mozilla::TaskCategory::Other, ev.forget());
|
||||
}
|
||||
|
||||
nsIParser*
|
||||
|
|
|
@ -131,11 +131,13 @@ class EnumerateFontsTask final : public Runnable
|
|||
public:
|
||||
EnumerateFontsTask(nsAtom* aLangGroupAtom,
|
||||
const nsAutoCString& aGeneric,
|
||||
UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise)
|
||||
UniquePtr<EnumerateFontsPromise> aEnumerateFontsPromise,
|
||||
nsIEventTarget* aMainThreadTarget)
|
||||
: Runnable("EnumerateFontsTask")
|
||||
, mLangGroupAtom(aLangGroupAtom)
|
||||
, mGeneric(aGeneric)
|
||||
, mEnumerateFontsPromise(Move(aEnumerateFontsPromise))
|
||||
, mMainThreadTarget(aMainThreadTarget)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
}
|
||||
|
@ -150,7 +152,7 @@ public:
|
|||
GetFontList(mLangGroupAtom, mGeneric, fontList);
|
||||
nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsResult(
|
||||
rv, Move(mEnumerateFontsPromise), Move(fontList));
|
||||
NS_DispatchToMainThread(runnable.forget());
|
||||
mMainThreadTarget->Dispatch(runnable.forget());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -159,6 +161,7 @@ private:
|
|||
RefPtr<nsAtom> mLangGroupAtom;
|
||||
nsAutoCStringN<16> mGeneric;
|
||||
UniquePtr<EnumerateFontsPromise> mEnumerateFontsPromise;
|
||||
RefPtr<nsIEventTarget> mMainThreadTarget;
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -207,8 +210,9 @@ nsThebesFontEnumerator::EnumerateFontsAsync(const char* aLangGroup,
|
|||
generic.SetIsVoid(true);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIEventTarget> target = global->EventTargetFor(mozilla::TaskCategory::Other);
|
||||
nsCOMPtr<nsIRunnable> runnable = new EnumerateFontsTask(
|
||||
langGroupAtom, generic, Move(enumerateFontsPromise));
|
||||
langGroupAtom, generic, Move(enumerateFontsPromise), target);
|
||||
thread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
|
||||
|
||||
if (!ToJSValue(aCx, promise, aRval)) {
|
||||
|
|
|
@ -11,6 +11,11 @@ UNIFIED_SOURCES += [
|
|||
'tests.cpp',
|
||||
]
|
||||
|
||||
if CONFIG['JS_BUILD_BINAST']:
|
||||
UNIFIED_SOURCES += [
|
||||
'testBinASTReader.cpp',
|
||||
]
|
||||
|
||||
DEFINES['EXPORT_JS_API'] = True
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
*/
|
||||
/* 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 "mozilla/ScopeExit.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
|
||||
#include "frontend/BinSource.h"
|
||||
#include "frontend/FullParseHandler.h"
|
||||
#include "frontend/ParseContext.h"
|
||||
#include "frontend/Parser.h"
|
||||
#include "fuzz-tests/tests.h"
|
||||
#include "vm/Interpreter.h"
|
||||
|
||||
#include "vm/JSContext-inl.h"
|
||||
|
||||
using UsedNameTracker = js::frontend::UsedNameTracker;
|
||||
using namespace js;
|
||||
|
||||
// These are defined and pre-initialized by the harness (in tests.cpp).
|
||||
extern JS::PersistentRootedObject gGlobal;
|
||||
extern JSContext* gCx;
|
||||
|
||||
static int
|
||||
testBinASTReaderInit(int *argc, char ***argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
testBinASTReaderFuzz(const uint8_t* buf, size_t size) {
|
||||
auto gcGuard = mozilla::MakeScopeExit([&] {
|
||||
JS::PrepareForFullGC(gCx);
|
||||
JS::GCForReason(gCx, GC_NORMAL, JS::gcreason::API);
|
||||
});
|
||||
|
||||
if (!size) return 0;
|
||||
|
||||
CompileOptions options(gCx);
|
||||
options.setIntroductionType("fuzzing parse")
|
||||
.setFileAndLine("<string>", 1);
|
||||
|
||||
js::Vector<uint8_t> binSource(gCx);
|
||||
if (!binSource.append(buf, size)) {
|
||||
ReportOutOfMemory(gCx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
js::frontend::UsedNameTracker binUsedNames(gCx);
|
||||
if (!binUsedNames.init()) {
|
||||
ReportOutOfMemory(gCx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
js::frontend::BinASTParser reader(gCx, gCx->tempLifoAlloc(), binUsedNames, options);
|
||||
|
||||
// Will be deallocated once `reader` goes out of scope.
|
||||
auto binParsed = reader.parse(binSource);
|
||||
RootedValue binExn(gCx);
|
||||
if (binParsed.isErr()) {
|
||||
js::GetAndClearException(gCx, &binExn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(DEBUG) // Dumping an AST is only defined in DEBUG builds
|
||||
Sprinter binPrinter(gCx);
|
||||
if (!binPrinter.init()) {
|
||||
ReportOutOfMemory(gCx);
|
||||
return 0;
|
||||
}
|
||||
DumpParseTree(binParsed.unwrap(), binPrinter);
|
||||
#endif // defined(DEBUG)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MOZ_FUZZING_INTERFACE_RAW(testBinASTReaderInit, testBinASTReaderFuzz, BinAST);
|
|
@ -0,0 +1,128 @@
|
|||
// Receiver shadows
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
let a = { x: "a" };
|
||||
let b = { __proto__: a };
|
||||
let c = { __proto__: b };
|
||||
let d = { __proto__: c };
|
||||
|
||||
assertEq(check(d), "a");
|
||||
assertEq(check(d), "a");
|
||||
d.x = "d";
|
||||
assertEq(check(d), "d");
|
||||
})();
|
||||
|
||||
// Intermediate proto shadows
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
let a = { x: "a" };
|
||||
let b = { __proto__: a };
|
||||
let c = { __proto__: b };
|
||||
let d = { __proto__: c };
|
||||
|
||||
assertEq(check(d), "a");
|
||||
assertEq(check(d), "a");
|
||||
c.x = "c";
|
||||
assertEq(check(d), "c");
|
||||
})();
|
||||
|
||||
// Receiver proto changes
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
let a = { x: "a" };
|
||||
let b = { __proto__: a };
|
||||
let c = { __proto__: b };
|
||||
let d = { __proto__: c };
|
||||
|
||||
assertEq(check(d), "a");
|
||||
assertEq(check(d), "a");
|
||||
d.__proto__ = { x: "?" };
|
||||
assertEq(check(d), "?");
|
||||
})();
|
||||
|
||||
// Intermediate proto changes
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
let a = { x: "a" };
|
||||
let b = { __proto__: a };
|
||||
let c = { __proto__: b };
|
||||
let d = { __proto__: c };
|
||||
|
||||
assertEq(check(d), "a");
|
||||
assertEq(check(d), "a");
|
||||
c.__proto__ = { x: "?" };
|
||||
assertEq(check(d), "?");
|
||||
})();
|
||||
|
||||
// Uncacheable holder proto
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
function Base() { this.x = "a"; }
|
||||
let a = new Base;
|
||||
a.__proto__ = new Object;
|
||||
let b = { __proto__: a };
|
||||
let c = { __proto__: b };
|
||||
let d = { __proto__: c };
|
||||
|
||||
assertEq(check(d), "a");
|
||||
assertEq(check(d), "a");
|
||||
b.__proto__ = { x: "?" };
|
||||
assertEq(check(d), "?");
|
||||
})();
|
||||
|
||||
// Uncacheable intermediate proto
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
function Base() { this.x = "a"; }
|
||||
function Node() { }
|
||||
|
||||
let a = new Base;
|
||||
let b = new Node; b.__proto__ = a;
|
||||
let c = { __proto__: b };
|
||||
let d = { __proto__: c };
|
||||
|
||||
assertEq(check(d), "a");
|
||||
assertEq(check(d), "a");
|
||||
b.__proto__ = { x: "?" };
|
||||
assertEq(check(d), "?");
|
||||
})();
|
||||
|
||||
// Uncacheable receiver proto
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
function Base() { this.x = "a"; }
|
||||
function Node() { }
|
||||
|
||||
let a = new Base;
|
||||
let b = { __proto__: a };
|
||||
let c = { __proto__: b };
|
||||
let d = new Node; d.__proto__ = c;
|
||||
|
||||
assertEq(check(d), "a");
|
||||
assertEq(check(d), "a");
|
||||
d.__proto__ = { x: "?" };
|
||||
assertEq(check(d), "?");
|
||||
})();
|
||||
|
||||
// Uncacheable receiver proto (only receiver / holder)
|
||||
(function() {
|
||||
function check(p) { return p.x; }
|
||||
|
||||
function Base() { this.x = "a"; }
|
||||
function Node() { }
|
||||
|
||||
let a = new Base;
|
||||
let b = new Node; b.__proto__ = a;
|
||||
|
||||
assertEq(check(b), "a");
|
||||
assertEq(check(b), "a");
|
||||
b.__proto__ = { x: "?" };
|
||||
assertEq(check(b), "?");
|
||||
})();
|
|
@ -525,56 +525,133 @@ CanAttachNativeGetProp(JSContext* cx, HandleObject obj, HandleId id,
|
|||
return CanAttachNone;
|
||||
}
|
||||
|
||||
static void
|
||||
GuardGroupProto(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId)
|
||||
{
|
||||
// Uses the group to determine if the prototype is unchanged. If the
|
||||
// group's prototype is mutable, we must check the actual prototype,
|
||||
// otherwise checking the group is sufficient. This can be used if object
|
||||
// is not ShapedObject or if Shape has UNCACHEABLE_PROTO flag set.
|
||||
|
||||
ObjectGroup* group = obj->groupRaw();
|
||||
|
||||
if (group->hasUncacheableProto())
|
||||
writer.guardProto(objId, obj->staticPrototype());
|
||||
else
|
||||
writer.guardGroupForProto(objId, group);
|
||||
}
|
||||
|
||||
static void
|
||||
GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, ObjOperandId objId)
|
||||
{
|
||||
// The guards here protect against the effects of JSObject::swap(). If the
|
||||
// prototype chain is directly altered, then TI will toss the jitcode, so we
|
||||
// don't have to worry about it, and any other change to the holder, or
|
||||
// adding a shadowing property will result in reshaping the holder, and thus
|
||||
// the failure of the shape guard.
|
||||
// Assuming target property is on |holder|, generate appropriate guards to
|
||||
// ensure |holder| is still on the prototype chain of |obj| and we haven't
|
||||
// introduced any shadowing definitions.
|
||||
//
|
||||
// For each item in the proto chain before holder, we must ensure that
|
||||
// [[GetPrototypeOf]] still has the expected result, and that
|
||||
// [[GetOwnProperty]] has no definition of the target property.
|
||||
//
|
||||
//
|
||||
// Shape Teleporting Optimization
|
||||
// ------------------------------
|
||||
//
|
||||
// Starting with the assumption (and guideline to developers) that mutating
|
||||
// prototypes is an uncommon and fair-to-penalize operation we move cost
|
||||
// from the access side to the mutation side.
|
||||
//
|
||||
// Consider the following proto chain, with B defining a property 'x':
|
||||
//
|
||||
// D -> C -> B{x: 3} -> A -> null
|
||||
//
|
||||
// When accessing |D.x| we refer to D as the "receiver", and B as the
|
||||
// "holder". To optimize this access we need to ensure that neither D nor C
|
||||
// has since defined a shadowing property 'x'. Since C is a prototype that
|
||||
// we assume is rarely mutated we would like to avoid checking each time if
|
||||
// new properties are added. To do this we require that everytime C is
|
||||
// mutated that in addition to generating a new shape for itself, it will
|
||||
// walk the proto chain and generate new shapes for those objects on the
|
||||
// chain (B and A). As a result, checking the shape of D and B is
|
||||
// sufficient. Note that we do not care if the shape or properties of A
|
||||
// change since the lookup of 'x' will stop at B.
|
||||
//
|
||||
// The second condition we must verify is that the prototype chain was not
|
||||
// mutated. The same mechanism as above is used. When the prototype link is
|
||||
// changed, we generate a new shape for the object. If the object whose
|
||||
// link we are mutating is itself a prototype, we regenerate shapes down
|
||||
// the chain. This means the same two shape checks as above are sufficient.
|
||||
//
|
||||
// Unfortunately we don't stop there and add further caveats. We may set
|
||||
// the UNCACHEABLE_PROTO flag on the shape of an object to indicate that it
|
||||
// will not generate a new shape if its prototype link is modified. If the
|
||||
// object is itself a prototype we follow the shape chain and regenerate
|
||||
// shapes (if they aren't themselves uncacheable).
|
||||
//
|
||||
// Let's consider the effect of the UNCACHEABLE_PROTO flag on our example:
|
||||
// - D is uncacheable: Add check that D still links to C
|
||||
// - C is uncacheable: Modifying C.__proto__ will still reshape B (if B is
|
||||
// not uncacheable)
|
||||
// - B is uncacheable: Add shape check C since B will not reshape OR check
|
||||
// proto of D and C
|
||||
//
|
||||
// See:
|
||||
// - ReshapeForProtoMutation
|
||||
// - ReshapeForShadowedProp
|
||||
|
||||
MOZ_ASSERT(holder);
|
||||
MOZ_ASSERT(obj != holder);
|
||||
|
||||
if (obj->hasUncacheableProto()) {
|
||||
// If the shape does not imply the proto, emit an explicit proto guard.
|
||||
writer.guardProto(objId, obj->staticPrototype());
|
||||
}
|
||||
// Only DELEGATE objects participate in teleporting so peel off the first
|
||||
// object in the chain if needed and handle it directly.
|
||||
JSObject* pobj = obj;
|
||||
if (!obj->isDelegate()) {
|
||||
if (obj->hasUncacheableProto())
|
||||
GuardGroupProto(writer, obj, objId);
|
||||
|
||||
JSObject* pobj = obj->staticPrototype();
|
||||
if (!pobj)
|
||||
pobj = obj->staticPrototype();
|
||||
}
|
||||
MOZ_ASSERT(pobj->isDelegate());
|
||||
|
||||
// In the common case, holder has a cacheable prototype and will regenerate
|
||||
// its shape if any (delegate) objects in the proto chain are updated.
|
||||
if (!holder->hasUncacheableProto())
|
||||
return;
|
||||
|
||||
// If already at the holder, no further proto checks are needed.
|
||||
if (pobj == holder)
|
||||
return;
|
||||
|
||||
// NOTE: We could be clever and look for a middle prototype to shape check
|
||||
// and elide some (but not all) of the group checks. Unless we have
|
||||
// real-world examples, let's avoid the complexity.
|
||||
|
||||
// Synchronize pobj and protoId.
|
||||
MOZ_ASSERT(pobj == obj || pobj == obj->staticPrototype());
|
||||
ObjOperandId protoId = (pobj == obj) ? objId
|
||||
: writer.loadProto(objId);
|
||||
|
||||
// Guard prototype links from |pobj| to |holder|.
|
||||
while (pobj != holder) {
|
||||
if (pobj->hasUncacheableProto()) {
|
||||
ObjOperandId protoId = writer.loadObject(pobj);
|
||||
if (pobj->isSingleton()) {
|
||||
// Singletons can have their group's |proto| mutated directly.
|
||||
writer.guardProto(protoId, pobj->staticPrototype());
|
||||
} else {
|
||||
writer.guardGroup(protoId, pobj->group());
|
||||
}
|
||||
}
|
||||
pobj = pobj->staticPrototype();
|
||||
protoId = writer.loadProto(protoId);
|
||||
|
||||
writer.guardSpecificObject(protoId, pobj);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
GeneratePrototypeHoleGuards(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId)
|
||||
{
|
||||
if (obj->hasUncacheableProto()) {
|
||||
// If the shape does not imply the proto, emit an explicit proto guard.
|
||||
writer.guardProto(objId, obj->staticPrototype());
|
||||
}
|
||||
if (obj->hasUncacheableProto())
|
||||
GuardGroupProto(writer, obj, objId);
|
||||
|
||||
JSObject* pobj = obj->staticPrototype();
|
||||
while (pobj) {
|
||||
ObjOperandId protoId = writer.loadObject(pobj);
|
||||
|
||||
// Non-singletons with uncacheable protos can change their proto
|
||||
// without a shape change, so also guard on the group (which determines
|
||||
// the proto) in this case.
|
||||
if (pobj->hasUncacheableProto() && !pobj->isSingleton())
|
||||
writer.guardGroup(protoId, pobj->group());
|
||||
// If shape doesn't imply proto, additional guards are needed.
|
||||
if (pobj->hasUncacheableProto())
|
||||
GuardGroupProto(writer, pobj, protoId);
|
||||
|
||||
// Make sure the shape matches, to avoid non-dense elements or anything
|
||||
// else that is being checked by CanAttachDenseElementHole.
|
||||
|
@ -592,7 +669,7 @@ TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId,
|
|||
Maybe<ObjOperandId>* expandoId)
|
||||
{
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
|
||||
expandoId->emplace(writer.guardAndLoadUnboxedExpando(objId));
|
||||
|
@ -601,7 +678,7 @@ TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId,
|
|||
writer.guardNoUnboxedExpando(objId);
|
||||
}
|
||||
} else if (obj->is<TypedObject>()) {
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
} else {
|
||||
Shape* shape = obj->maybeShape();
|
||||
MOZ_ASSERT(shape);
|
||||
|
@ -617,9 +694,10 @@ EmitReadSlotGuard(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
|
|||
TestMatchingReceiver(writer, obj, objId, &expandoId);
|
||||
|
||||
if (obj != holder) {
|
||||
GeneratePrototypeGuards(writer, obj, holder, objId);
|
||||
|
||||
if (holder) {
|
||||
// Guard proto chain integrity.
|
||||
GeneratePrototypeGuards(writer, obj, holder, objId);
|
||||
|
||||
// Guard on the holder's shape.
|
||||
holderId->emplace(writer.loadObject(holder));
|
||||
writer.guardShape(holderId->ref(), holder->as<NativeObject>().lastProperty());
|
||||
|
@ -631,6 +709,11 @@ EmitReadSlotGuard(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
|
|||
ObjOperandId lastObjId = objId;
|
||||
while (proto) {
|
||||
ObjOperandId protoId = writer.loadProto(lastObjId);
|
||||
|
||||
// If shape doesn't imply proto, additional guards are needed.
|
||||
if (proto->hasUncacheableProto())
|
||||
GuardGroupProto(writer, proto, lastObjId);
|
||||
|
||||
writer.guardShape(protoId, proto->as<NativeObject>().lastProperty());
|
||||
proto = proto->staticPrototype();
|
||||
lastObjId = protoId;
|
||||
|
@ -1329,7 +1412,7 @@ GetPropIRGenerator::tryAttachUnboxed(HandleObject obj, ObjOperandId objId, Handl
|
|||
return false;
|
||||
|
||||
maybeEmitIdGuard(id);
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
writer.loadUnboxedPropertyResult(objId, property->type,
|
||||
UnboxedPlainObject::offsetOfData() + property->offset);
|
||||
if (property->type == JSVAL_TYPE_OBJECT)
|
||||
|
@ -2422,10 +2505,10 @@ HasPropIRGenerator::tryAttachSparse(HandleObject obj, ObjOperandId objId,
|
|||
// Generate prototype guards if needed. This includes monitoring that
|
||||
// properties were not added in the chain.
|
||||
if (!hasOwn) {
|
||||
if (!obj->hasUncacheableProto()) {
|
||||
// Make sure the proto does not change without checking the shape.
|
||||
writer.guardProto(objId, obj->staticPrototype());
|
||||
}
|
||||
// If GeneratePrototypeHoleGuards below won't add guards for prototype,
|
||||
// we should add our own since we aren't guarding shape.
|
||||
if (!obj->hasUncacheableProto())
|
||||
GuardGroupProto(writer, obj, objId);
|
||||
|
||||
GeneratePrototypeHoleGuards(writer, obj, objId);
|
||||
}
|
||||
|
@ -2521,7 +2604,7 @@ HasPropIRGenerator::tryAttachUnboxed(JSObject* obj, ObjOperandId objId,
|
|||
return false;
|
||||
|
||||
emitIdGuard(keyId, key);
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
writer.loadBooleanResult(true);
|
||||
writer.returnFromIC();
|
||||
|
||||
|
@ -2588,7 +2671,7 @@ HasPropIRGenerator::tryAttachTypedObject(JSObject* obj, ObjOperandId objId,
|
|||
return false;
|
||||
|
||||
emitIdGuard(keyId, key);
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
writer.loadBooleanResult(true);
|
||||
writer.returnFromIC();
|
||||
|
||||
|
@ -2941,7 +3024,7 @@ SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId,
|
|||
// types match, we don't need the group guard.
|
||||
NativeObject* nobj = &obj->as<NativeObject>();
|
||||
if (typeCheckInfo_.needsTypeBarrier())
|
||||
writer.guardGroup(objId, nobj->group());
|
||||
writer.guardGroupForTypeBarrier(objId, nobj->group());
|
||||
writer.guardShape(objId, nobj->lastProperty());
|
||||
|
||||
if (IsPreliminaryObject(obj))
|
||||
|
@ -2972,7 +3055,7 @@ SetPropIRGenerator::tryAttachUnboxedExpandoSetSlot(HandleObject obj, ObjOperandI
|
|||
return false;
|
||||
|
||||
maybeEmitIdGuard(id);
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
ObjOperandId expandoId = writer.guardAndLoadUnboxedExpando(objId);
|
||||
writer.guardShape(expandoId, expando->lastProperty());
|
||||
|
||||
|
@ -3008,7 +3091,7 @@ SetPropIRGenerator::tryAttachUnboxedProperty(HandleObject obj, ObjOperandId objI
|
|||
return false;
|
||||
|
||||
maybeEmitIdGuard(id);
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
EmitGuardUnboxedPropertyType(writer, property->type, rhsId);
|
||||
writer.storeUnboxedProperty(objId, property->type,
|
||||
UnboxedPlainObject::offsetOfData() + property->offset,
|
||||
|
@ -3050,7 +3133,7 @@ SetPropIRGenerator::tryAttachTypedObjectProperty(HandleObject obj, ObjOperandId
|
|||
maybeEmitIdGuard(id);
|
||||
writer.guardNoDetachedTypedObjects();
|
||||
writer.guardShape(objId, obj->as<TypedObject>().shape());
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.guardGroupForLayout(objId, obj->group());
|
||||
|
||||
typeCheckInfo_.set(obj->group(), id);
|
||||
|
||||
|
@ -3273,7 +3356,7 @@ SetPropIRGenerator::tryAttachSetDenseElement(HandleObject obj, ObjOperandId objI
|
|||
return false;
|
||||
|
||||
if (typeCheckInfo_.needsTypeBarrier())
|
||||
writer.guardGroup(objId, nobj->group());
|
||||
writer.guardGroupForTypeBarrier(objId, nobj->group());
|
||||
writer.guardShape(objId, nobj->shape());
|
||||
|
||||
writer.storeDenseElement(objId, indexId, rhsId);
|
||||
|
@ -3331,10 +3414,8 @@ static void
|
|||
ShapeGuardProtoChain(CacheIRWriter& writer, JSObject* obj, ObjOperandId objId)
|
||||
{
|
||||
while (true) {
|
||||
// Guard on the proto if the shape does not imply the proto. Singleton
|
||||
// objects always trigger a shape change when the proto changes, so we
|
||||
// don't need a guard in that case.
|
||||
bool guardProto = obj->hasUncacheableProto() && !obj->isSingleton();
|
||||
// Guard on the proto if the shape does not imply the proto.
|
||||
bool guardProto = obj->hasUncacheableProto();
|
||||
|
||||
obj = obj->staticPrototype();
|
||||
if (!obj)
|
||||
|
@ -3391,7 +3472,7 @@ SetPropIRGenerator::tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId
|
|||
return false;
|
||||
|
||||
if (typeCheckInfo_.needsTypeBarrier())
|
||||
writer.guardGroup(objId, nobj->group());
|
||||
writer.guardGroupForTypeBarrier(objId, nobj->group());
|
||||
writer.guardShape(objId, nobj->shape());
|
||||
|
||||
// Also shape guard the proto chain, unless this is an INITELEM or we know
|
||||
|
@ -3570,7 +3651,7 @@ SetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objI
|
|||
guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
|
||||
|
||||
NativeObject* nativeExpandoObj = &expandoObj->as<NativeObject>();
|
||||
writer.guardGroup(expandoObjId, nativeExpandoObj->group());
|
||||
writer.guardGroupForTypeBarrier(expandoObjId, nativeExpandoObj->group());
|
||||
typeCheckInfo_.set(nativeExpandoObj->group(), id);
|
||||
|
||||
EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, propShape, rhsId);
|
||||
|
@ -3717,7 +3798,7 @@ SetPropIRGenerator::tryAttachWindowProxy(HandleObject obj, ObjOperandId objId, H
|
|||
ObjOperandId windowObjId = writer.loadObject(windowObj);
|
||||
|
||||
writer.guardShape(windowObjId, windowObj->lastProperty());
|
||||
writer.guardGroup(windowObjId, windowObj->group());
|
||||
writer.guardGroupForTypeBarrier(windowObjId, windowObj->group());
|
||||
typeCheckInfo_.set(windowObj->group(), id);
|
||||
|
||||
EmitStoreSlotAndReturn(writer, windowObjId, windowObj, propShape, rhsId);
|
||||
|
@ -3853,7 +3934,10 @@ SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape
|
|||
ObjOperandId objId = writer.guardIsObject(objValId);
|
||||
maybeEmitIdGuard(id);
|
||||
|
||||
writer.guardGroup(objId, oldGroup);
|
||||
// In addition to guarding for type barrier, we need this group guard (or
|
||||
// shape guard below) to ensure class is unchanged.
|
||||
MOZ_ASSERT(!oldGroup->hasUncacheableClass() || obj->is<ShapedObject>());
|
||||
writer.guardGroupForTypeBarrier(objId, oldGroup);
|
||||
|
||||
// If we are adding a property to an object for which the new script
|
||||
// properties analysis hasn't been performed yet, make sure the stub fails
|
||||
|
@ -4255,7 +4339,7 @@ CallIRGenerator::tryAttachArrayPush()
|
|||
|
||||
// Guard that the group and shape matches.
|
||||
if (typeCheckInfo_.needsTypeBarrier())
|
||||
writer.guardGroup(thisObjId, thisobj->group());
|
||||
writer.guardGroupForTypeBarrier(thisObjId, thisobj->group());
|
||||
writer.guardShape(thisObjId, thisarray->shape());
|
||||
|
||||
// Guard proto chain shapes.
|
||||
|
@ -4683,4 +4767,4 @@ GetIntrinsicIRGenerator::tryAttachStub()
|
|||
writer.returnFromIC();
|
||||
trackAttached("GetIntrinsic");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -557,10 +557,32 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
|||
writeOperandId(protoId);
|
||||
addStubField(slot, StubField::Type::RawWord);
|
||||
}
|
||||
private:
|
||||
// Use (or create) a specialization below to clarify what constaint the
|
||||
// group guard is implying.
|
||||
void guardGroup(ObjOperandId obj, ObjectGroup* group) {
|
||||
writeOpWithOperandId(CacheOp::GuardGroup, obj);
|
||||
addStubField(uintptr_t(group), StubField::Type::ObjectGroup);
|
||||
}
|
||||
public:
|
||||
void guardGroupForProto(ObjOperandId obj, ObjectGroup* group) {
|
||||
MOZ_ASSERT(!group->hasUncacheableProto());
|
||||
guardGroup(obj, group);
|
||||
}
|
||||
void guardGroupForTypeBarrier(ObjOperandId obj, ObjectGroup* group) {
|
||||
// Typesets will always be a super-set of any typesets previously seen
|
||||
// for this group. If the type/group of a value being stored to a
|
||||
// property in this group is not known, a TypeUpdate IC chain should be
|
||||
// used as well.
|
||||
guardGroup(obj, group);
|
||||
}
|
||||
void guardGroupForLayout(ObjOperandId obj, ObjectGroup* group) {
|
||||
// NOTE: Comment in guardGroupForTypeBarrier also applies.
|
||||
MOZ_ASSERT(!group->hasUncacheableClass());
|
||||
MOZ_ASSERT(IsUnboxedObjectClass(group->clasp()) ||
|
||||
IsTypedObjectClass(group->clasp()));
|
||||
guardGroup(obj, group);
|
||||
}
|
||||
void guardProto(ObjOperandId obj, JSObject* proto) {
|
||||
writeOpWithOperandId(CacheOp::GuardProto, obj);
|
||||
addStubField(uintptr_t(proto), StubField::Type::JSObject);
|
||||
|
|
|
@ -676,6 +676,12 @@ if CONFIG['JS_BUILD_BINAST']:
|
|||
'frontend/BinToken.cpp'
|
||||
]
|
||||
|
||||
# Instrument BinAST files for fuzzing as we have a fuzzing target for BinAST.
|
||||
if CONFIG['FUZZING_INTERFACES'] and CONFIG['LIBFUZZER']:
|
||||
SOURCES['frontend/BinSource.cpp'].flags += ['-fsanitize-coverage=trace-pc-guard']
|
||||
SOURCES['frontend/BinToken.cpp'].flags += ['-fsanitize-coverage=trace-pc-guard']
|
||||
SOURCES['frontend/BinTokenReaderTester.cpp'].flags += ['-fsanitize-coverage=trace-pc-guard']
|
||||
|
||||
# Wasm code should use WASM_HUGE_MEMORY instead of JS_CODEGEN_X64
|
||||
# so that it is easy to use the huge-mapping optimization for other
|
||||
# 64-bit platforms in the future.
|
||||
|
|
|
@ -1027,7 +1027,7 @@ LexicalEnvironmentObject::clone(JSContext* cx, Handle<LexicalEnvironmentObject*>
|
|||
return nullptr;
|
||||
|
||||
// We can't assert that the clone has the same shape, because it could
|
||||
// have been reshaped by PurgeEnvironmentChain.
|
||||
// have been reshaped by ReshapeForShadowedProp.
|
||||
MOZ_ASSERT(env->slotSpan() == copy->slotSpan());
|
||||
for (uint32_t i = JSSLOT_FREE(&class_); i < copy->slotSpan(); i++)
|
||||
copy->setSlot(i, env->getSlot(i));
|
||||
|
|
|
@ -1664,6 +1664,13 @@ JSObject::swap(JSContext* cx, HandleObject a, HandleObject b)
|
|||
MOZ_ASSERT(!a->is<TypedArrayObject>() && !b->is<TypedArrayObject>());
|
||||
MOZ_ASSERT(!a->is<TypedObject>() && !b->is<TypedObject>());
|
||||
|
||||
// Don't swap objects that may currently be participating in shape
|
||||
// teleporting optimizations.
|
||||
//
|
||||
// See: ReshapeForProtoMutation, ReshapeForShadowedProp
|
||||
MOZ_ASSERT_IF(a->isNative() && a->isDelegate(), a->taggedProto() == TaggedProto());
|
||||
MOZ_ASSERT_IF(b->isNative() && b->isDelegate(), b->taggedProto() == TaggedProto());
|
||||
|
||||
bool aIsProxyWithInlineValues =
|
||||
a->is<ProxyObject>() && a->as<ProxyObject>().usingInlineValueArray();
|
||||
bool bIsProxyWithInlineValues =
|
||||
|
@ -1986,48 +1993,64 @@ JSObject::fixupAfterMovingGC()
|
|||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
ReshapeForProtoMutation(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
// To avoid the JIT guarding on each prototype in chain to detect prototype
|
||||
// mutation, we can instead reshape the rest of the proto chain such that a
|
||||
// guard on any of them is sufficient. To avoid excessive reshaping and
|
||||
// invalidation, we apply heuristics to decide when to apply this and when
|
||||
// to require a guard.
|
||||
//
|
||||
// Heuristics:
|
||||
// - Always reshape singleton objects. This historically avoided
|
||||
// de-optimizing in cases that compiler doesn't support
|
||||
// uncacheable-proto. TODO: Revisit if this is a good idea.
|
||||
// - Other objects instead set UNCACHEABLE_PROTO flag on shape to avoid
|
||||
// creating too many private shape copies.
|
||||
// - Only propegate along proto chain if we are mark DELEGATE. This avoids
|
||||
// reshaping in normal object access cases.
|
||||
//
|
||||
// NOTE: We only handle NativeObjects and don't propegate reshapes through
|
||||
// any non-native objects on the chain.
|
||||
//
|
||||
// See Also:
|
||||
// - GeneratePrototypeGuards
|
||||
// - GeneratePrototypeHoleGuards
|
||||
// - ObjectGroup::defaultNewGroup
|
||||
|
||||
RootedObject pobj(cx, obj);
|
||||
|
||||
while (pobj && pobj->isNative()) {
|
||||
if (pobj->isSingleton()) {
|
||||
// If object was converted to a singleton it should have cleared
|
||||
// any UNCACHEABLE_PROTO flags.
|
||||
MOZ_ASSERT(!pobj->hasUncacheableProto());
|
||||
|
||||
if (!NativeObject::reshapeForProtoMutation(cx, pobj.as<NativeObject>()))
|
||||
return false;
|
||||
} else {
|
||||
if (!JSObject::setUncacheableProto(cx, pobj))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!obj->isDelegate())
|
||||
break;
|
||||
|
||||
pobj = pobj->staticPrototype();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
SetClassAndProto(JSContext* cx, HandleObject obj,
|
||||
const Class* clasp, Handle<js::TaggedProto> proto)
|
||||
{
|
||||
// Regenerate the object's shape. If the object is a proto (isDelegate()),
|
||||
// we also need to regenerate shapes for all of the objects along the old
|
||||
// prototype chain, in case any entries were filled by looking up through
|
||||
// obj. Stop when a non-native object is found, prototype lookups will not
|
||||
// be cached across these.
|
||||
//
|
||||
// How this shape change is done is very delicate; the change can be made
|
||||
// either by marking the object's prototype as uncacheable (such that the
|
||||
// JIT'ed ICs cannot assume the shape determines the prototype) or by just
|
||||
// generating a new shape for the object. Choosing the former is bad if the
|
||||
// object is on the prototype chain of other objects, as the uncacheable
|
||||
// prototype can inhibit iterator caches on those objects and slow down
|
||||
// prototype accesses. Choosing the latter is bad if there are many similar
|
||||
// objects to this one which will have their prototype mutated, as the
|
||||
// generateOwnShape forces the object into dictionary mode and similar
|
||||
// property lineages will be repeatedly cloned.
|
||||
//
|
||||
// :XXX: bug 707717 make this code less brittle.
|
||||
RootedObject oldproto(cx, obj);
|
||||
while (oldproto && oldproto->isNative()) {
|
||||
if (oldproto->isSingleton()) {
|
||||
// We always generate a new shape if the object is a singleton,
|
||||
// regardless of the uncacheable-proto flag. ICs may rely on
|
||||
// this.
|
||||
if (!NativeObject::generateOwnShape(cx, oldproto.as<NativeObject>()))
|
||||
return false;
|
||||
} else {
|
||||
if (!JSObject::setUncacheableProto(cx, oldproto))
|
||||
return false;
|
||||
}
|
||||
if (!obj->isDelegate()) {
|
||||
// If |obj| is not a proto of another object, we don't need to
|
||||
// reshape the whole proto chain.
|
||||
MOZ_ASSERT(obj == oldproto);
|
||||
break;
|
||||
}
|
||||
oldproto = oldproto->staticPrototype();
|
||||
}
|
||||
// Regenerate object shape (and possibly prototype shape) to invalidate JIT
|
||||
// code that is affected by a prototype mutation.
|
||||
if (!ReshapeForProtoMutation(cx, obj))
|
||||
return false;
|
||||
|
||||
if (proto.isObject()) {
|
||||
RootedObject protoObj(cx, proto.toObject());
|
||||
|
|
|
@ -179,15 +179,9 @@ class JSObject : public js::gc::Cell
|
|||
GenerateShape generateShape = GENERATE_NONE);
|
||||
inline bool hasAllFlags(js::BaseShape::Flag flags) const;
|
||||
|
||||
/*
|
||||
* An object is a delegate if it is on another object's prototype or scope
|
||||
* chain, and therefore the delegate might be asked implicitly to get or
|
||||
* set a property on behalf of another object. Delegates may be accessed
|
||||
* directly too, as may any object, but only those objects linked after the
|
||||
* head of any prototype or scope chain are flagged as delegates. This
|
||||
* definition helps to optimize shape-based property cache invalidation
|
||||
* (see Purge{Scope,Proto}Chain in JSObject.cpp).
|
||||
*/
|
||||
// An object is a delegate if it is on another object's prototype or
|
||||
// environment chain. Optimization heuristics will make use of this flag.
|
||||
// See: ReshapeForProtoMutation, ReshapeForShadowedProp
|
||||
inline bool isDelegate() const;
|
||||
static bool setDelegate(JSContext* cx, JS::HandleObject obj) {
|
||||
return setFlags(cx, obj, js::BaseShape::DELEGATE, GENERATE_SHAPE);
|
||||
|
|
|
@ -1353,7 +1353,7 @@ PurgeProtoChain(JSContext* cx, JSObject* objArg, HandleId id)
|
|||
|
||||
shape = obj->as<NativeObject>().lookup(cx, id);
|
||||
if (shape)
|
||||
return NativeObject::shadowingShapeChange(cx, obj.as<NativeObject>(), *shape);
|
||||
return NativeObject::reshapeForShadowedProp(cx, obj.as<NativeObject>());
|
||||
|
||||
obj = obj->staticPrototype();
|
||||
}
|
||||
|
@ -1395,20 +1395,32 @@ PurgeEnvironmentChainHelper(JSContext* cx, HandleObject objArg, HandleId id)
|
|||
}
|
||||
|
||||
/*
|
||||
* PurgeEnvironmentChain does nothing if obj is not itself a prototype or
|
||||
* ReshapeForShadowedProp does nothing if obj is not itself a prototype or
|
||||
* parent environment, else it reshapes the scope and prototype chains it
|
||||
* links. It calls PurgeEnvironmentChainHelper, which asserts that obj is
|
||||
* flagged as a delegate (i.e., obj has ever been on a prototype or parent
|
||||
* chain).
|
||||
*/
|
||||
static MOZ_ALWAYS_INLINE bool
|
||||
PurgeEnvironmentChain(JSContext* cx, HandleObject obj, HandleId id)
|
||||
ReshapeForShadowedProp(JSContext* cx, HandleObject obj, HandleId id)
|
||||
{
|
||||
if (obj->isDelegate() && obj->isNative())
|
||||
return PurgeEnvironmentChainHelper(cx, obj, id);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
NativeObject::reshapeForShadowedProp(JSContext* cx, HandleNativeObject obj)
|
||||
{
|
||||
return generateOwnShape(cx, obj);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
NativeObject::reshapeForProtoMutation(JSContext* cx, HandleNativeObject obj)
|
||||
{
|
||||
return generateOwnShape(cx, obj);
|
||||
}
|
||||
|
||||
enum class IsAddOrChange { Add, AddOrChange };
|
||||
|
||||
template <IsAddOrChange AddOrChange>
|
||||
|
@ -1418,7 +1430,7 @@ AddOrChangeProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
|
|||
{
|
||||
desc.assertComplete();
|
||||
|
||||
if (!PurgeEnvironmentChain(cx, obj, id))
|
||||
if (!ReshapeForShadowedProp(cx, obj, id))
|
||||
return false;
|
||||
|
||||
// Use dense storage for new indexed properties where possible.
|
||||
|
@ -1494,7 +1506,7 @@ AddDataProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue
|
|||
{
|
||||
MOZ_ASSERT(!JSID_IS_INT(id));
|
||||
|
||||
if (!PurgeEnvironmentChain(cx, obj, id))
|
||||
if (!ReshapeForShadowedProp(cx, obj, id))
|
||||
return false;
|
||||
|
||||
Shape* shape = NativeObject::addEnumerableDataProperty(cx, obj, id);
|
||||
|
@ -2567,7 +2579,7 @@ js::SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v, HandleValue
|
|||
}
|
||||
|
||||
// Purge the property cache of now-shadowed id in receiver's environment chain.
|
||||
if (!PurgeEnvironmentChain(cx, receiver, id))
|
||||
if (!ReshapeForShadowedProp(cx, receiver, id))
|
||||
return false;
|
||||
|
||||
// Steps 5.e.iii-iv. and 5.f.i. Define the new data property.
|
||||
|
@ -2631,7 +2643,7 @@ SetNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id, Handl
|
|||
|
||||
if (DefinePropertyOp op = obj->getOpsDefineProperty()) {
|
||||
// Purge the property cache of now-shadowed id in receiver's environment chain.
|
||||
if (!PurgeEnvironmentChain(cx, obj, id))
|
||||
if (!ReshapeForShadowedProp(cx, obj, id))
|
||||
return false;
|
||||
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
|
|
|
@ -692,8 +692,8 @@ class NativeObject : public ShapedObject
|
|||
return replaceWithNewEquivalentShape(cx, obj, obj->lastProperty(), newShape);
|
||||
}
|
||||
|
||||
static MOZ_MUST_USE bool shadowingShapeChange(JSContext* cx, HandleNativeObject obj,
|
||||
const Shape& shape);
|
||||
static MOZ_MUST_USE bool reshapeForShadowedProp(JSContext* cx, HandleNativeObject obj);
|
||||
static MOZ_MUST_USE bool reshapeForProtoMutation(JSContext* cx, HandleNativeObject obj);
|
||||
static bool clearFlag(JSContext* cx, HandleNativeObject obj, BaseShape::Flag flag);
|
||||
|
||||
// The maximum number of slots in an object.
|
||||
|
|
|
@ -536,6 +536,14 @@ ObjectGroup::defaultNewGroup(JSContext* cx, const Class* clasp,
|
|||
if (protoObj->is<PlainObject>() && !protoObj->isSingleton()) {
|
||||
if (!JSObject::changeToSingleton(cx, protoObj))
|
||||
return nullptr;
|
||||
|
||||
// |ReshapeForProtoMutation| ensures singletons will reshape when
|
||||
// prototype is mutated so clear the UNCACHEABLE_PROTO flag.
|
||||
if (protoObj->hasUncacheableProto()) {
|
||||
HandleNativeObject nobj = protoObj.as<NativeObject>();
|
||||
if (!NativeObject::clearFlag(cx, nobj, BaseShape::UNCACHEABLE_PROTO))
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -103,9 +103,19 @@ class ObjectGroup : public gc::TenuredCell
|
|||
|
||||
void setClasp(const Class* clasp) {
|
||||
MOZ_ASSERT(JS::StringIsASCII(clasp->name));
|
||||
MOZ_ASSERT(hasUncacheableClass());
|
||||
clasp_ = clasp;
|
||||
}
|
||||
|
||||
// Certain optimizations may mutate the class of an ObjectGroup - and thus
|
||||
// all objects in it - after it is created. If true, the JIT must not
|
||||
// assume objects of a previously seen group have the same class as before.
|
||||
//
|
||||
// See: TryConvertToUnboxedLayout
|
||||
bool hasUncacheableClass() const {
|
||||
return clasp_->isNative();
|
||||
}
|
||||
|
||||
bool hasDynamicPrototype() const {
|
||||
return proto_.isDynamic();
|
||||
}
|
||||
|
@ -121,6 +131,14 @@ class ObjectGroup : public gc::TenuredCell
|
|||
void setProto(TaggedProto proto);
|
||||
void setProtoUnchecked(TaggedProto proto);
|
||||
|
||||
bool hasUncacheableProto() const {
|
||||
// We allow singletons to mutate their prototype after the group has
|
||||
// been created. If true, the JIT must re-check prototype even if group
|
||||
// has been seen before.
|
||||
MOZ_ASSERT(!hasDynamicPrototype());
|
||||
return singleton();
|
||||
}
|
||||
|
||||
bool singleton() const {
|
||||
return flagsDontCheckGeneration() & OBJECT_FLAG_SINGLETON;
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ void
|
|||
Shape::insertIntoDictionary(GCPtrShape* dictp)
|
||||
{
|
||||
// Don't assert inDictionaryMode() here because we may be called from
|
||||
// JSObject::toDictionaryMode via JSObject::newDictionaryShape.
|
||||
// NativeObject::toDictionaryMode via Shape::initDictionaryShape.
|
||||
MOZ_ASSERT(inDictionary());
|
||||
MOZ_ASSERT(!listp);
|
||||
|
||||
|
@ -1313,12 +1313,6 @@ NativeObject::replaceWithNewEquivalentShape(JSContext* cx, HandleNativeObject ob
|
|||
return newShape;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
NativeObject::shadowingShapeChange(JSContext* cx, HandleNativeObject obj, const Shape& shape)
|
||||
{
|
||||
return generateOwnShape(cx, obj);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
JSObject::setFlags(JSContext* cx, HandleObject obj, BaseShape::Flag flags,
|
||||
GenerateShape generateShape)
|
||||
|
@ -1359,10 +1353,13 @@ JSObject::setFlags(JSContext* cx, HandleObject obj, BaseShape::Flag flags,
|
|||
/* static */ bool
|
||||
NativeObject::clearFlag(JSContext* cx, HandleNativeObject obj, BaseShape::Flag flag)
|
||||
{
|
||||
MOZ_ASSERT(obj->inDictionaryMode());
|
||||
|
||||
MOZ_ASSERT(obj->lastProperty()->getObjectFlags() & flag);
|
||||
|
||||
if (!obj->inDictionaryMode()) {
|
||||
if (!toDictionaryMode(cx, obj))
|
||||
return false;
|
||||
}
|
||||
|
||||
StackBaseShape base(obj->lastProperty());
|
||||
base.flags &= ~flag;
|
||||
UnownedBaseShape* nbase = BaseShape::getUnowned(cx, base);
|
||||
|
|
|
@ -317,6 +317,13 @@ class UnboxedPlainObject : public UnboxedObject
|
|||
}
|
||||
};
|
||||
|
||||
inline bool
|
||||
IsUnboxedObjectClass(const Class* class_)
|
||||
{
|
||||
return class_ == &UnboxedPlainObject::class_;
|
||||
}
|
||||
|
||||
|
||||
// Try to construct an UnboxedLayout for each of the preliminary objects,
|
||||
// provided they all match the template shape. If successful, converts the
|
||||
// preliminary objects and their group to the new unboxed representation.
|
||||
|
|
|
@ -1142,7 +1142,6 @@ class AstConversionOperator final : public AstExpr
|
|||
AstExpr* operand() const { return operand_; }
|
||||
};
|
||||
|
||||
#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
|
||||
// Like AstConversionOperator, but for opcodes encoded with the Numeric prefix.
|
||||
class AstExtraConversionOperator final : public AstExpr
|
||||
{
|
||||
|
@ -1159,7 +1158,6 @@ class AstExtraConversionOperator final : public AstExpr
|
|||
NumericOp op() const { return op_; }
|
||||
AstExpr* operand() const { return operand_; }
|
||||
};
|
||||
#endif
|
||||
|
||||
// This is an artificial AST node which can fill operand slots in an AST
|
||||
// constructed from parsing or decoding stack-machine code that doesn't have
|
||||
|
|
|
@ -1354,12 +1354,10 @@ RenderExpr(WasmRenderContext& c, AstExpr& expr, bool newLine /* = true */)
|
|||
if (!RenderConversionOperator(c, expr.as<AstConversionOperator>()))
|
||||
return false;
|
||||
break;
|
||||
#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
|
||||
case AstExprKind::ExtraConversionOperator:
|
||||
if (!RenderExtraConversionOperator(c, expr.as<AstExtraConversionOperator>()))
|
||||
return false;
|
||||
break;
|
||||
#endif
|
||||
case AstExprKind::Load:
|
||||
if (!RenderLoad(c, expr.as<AstLoad>()))
|
||||
return false;
|
||||
|
|
|
@ -8096,39 +8096,63 @@ nsLayoutUtils::GetFontFacesForFrames(nsIFrame* aFrame,
|
|||
|
||||
static void
|
||||
AddFontsFromTextRun(gfxTextRun* aTextRun,
|
||||
nsIContent* aContent,
|
||||
nsTextFrame* aFrame,
|
||||
gfxSkipCharsIterator& aSkipIter,
|
||||
uint32_t aOffset,
|
||||
uint32_t aLength,
|
||||
const gfxTextRun::Range& aRange,
|
||||
nsLayoutUtils::UsedFontFaceTable& aFontFaces,
|
||||
uint32_t aMaxRanges)
|
||||
{
|
||||
gfxTextRun::Range range(aOffset, aOffset + aLength);
|
||||
gfxTextRun::GlyphRunIterator iter(aTextRun, range);
|
||||
while (iter.NextRun()) {
|
||||
gfxFontEntry *fe = iter.GetGlyphRun()->mFont->GetFontEntry();
|
||||
gfxTextRun::GlyphRunIterator glyphRuns(aTextRun, aRange);
|
||||
nsIContent* content = aFrame->GetContent();
|
||||
int32_t contentLimit = aFrame->GetContentOffset() +
|
||||
aFrame->GetInFlowContentLength();
|
||||
while (glyphRuns.NextRun()) {
|
||||
gfxFontEntry *fe = glyphRuns.GetGlyphRun()->mFont->GetFontEntry();
|
||||
// if we have already listed this face, just make sure the match type is
|
||||
// recorded
|
||||
InspectorFontFace* fontFace = aFontFaces.Get(fe);
|
||||
if (fontFace) {
|
||||
fontFace->AddMatchType(iter.GetGlyphRun()->mMatchType);
|
||||
fontFace->AddMatchType(glyphRuns.GetGlyphRun()->mMatchType);
|
||||
} else {
|
||||
// A new font entry we haven't seen before
|
||||
fontFace = new InspectorFontFace(fe, aTextRun->GetFontGroup(),
|
||||
iter.GetGlyphRun()->mMatchType);
|
||||
glyphRuns.GetGlyphRun()->mMatchType);
|
||||
aFontFaces.Put(fe, fontFace);
|
||||
}
|
||||
|
||||
// Add this glyph run to the fontFace's list of ranges, unless we have
|
||||
// already collected as many as wanted.
|
||||
if (fontFace->RangeCount() < aMaxRanges) {
|
||||
uint32_t start = aSkipIter.ConvertSkippedToOriginal(iter.GetStringStart());
|
||||
uint32_t end = aSkipIter.ConvertSkippedToOriginal(iter.GetStringEnd());
|
||||
RefPtr<nsRange> range;
|
||||
nsRange::CreateRange(aContent, start, aContent, end, getter_AddRefs(range));
|
||||
fontFace->AddRange(range);
|
||||
int32_t start =
|
||||
aSkipIter.ConvertSkippedToOriginal(glyphRuns.GetStringStart());
|
||||
int32_t end =
|
||||
aSkipIter.ConvertSkippedToOriginal(glyphRuns.GetStringEnd());
|
||||
|
||||
// Mapping back from textrun offsets ("skipped" offsets that reflect the
|
||||
// text after whitespace collapsing, etc) to DOM content offsets in the
|
||||
// original text is ambiguous, because many original characters can
|
||||
// map to a single skipped offset. aSkipIter.ConvertSkippedToOriginal()
|
||||
// will return an "original" offset that corresponds to the *end* of
|
||||
// a collapsed run of characters in this case; but that might extend
|
||||
// beyond the current content node if the textrun mapped multiple nodes.
|
||||
// So we clamp the end offset to keep it valid for the content node
|
||||
// that corresponds to the current textframe.
|
||||
end = std::min(end, contentLimit);
|
||||
|
||||
if (end > start) {
|
||||
RefPtr<nsRange> range;
|
||||
if (NS_FAILED(nsRange::CreateRange(content, start, content, end,
|
||||
getter_AddRefs(range)))) {
|
||||
NS_WARNING("failed to create range");
|
||||
} else {
|
||||
fontFace->AddRange(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ nsresult
|
||||
/* static */ void
|
||||
nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset,
|
||||
|
@ -8139,7 +8163,11 @@ nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame,
|
|||
NS_PRECONDITION(aFrame, "NULL frame pointer");
|
||||
|
||||
if (!aFrame->IsTextFrame()) {
|
||||
return NS_OK;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!aFrame->StyleVisibility()->IsVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsTextFrame* curr = static_cast<nsTextFrame*>(aFrame);
|
||||
|
@ -8154,7 +8182,10 @@ nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame,
|
|||
// curr is overlapping with the offset we want
|
||||
gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated);
|
||||
gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated);
|
||||
NS_ENSURE_TRUE(textRun, NS_ERROR_OUT_OF_MEMORY);
|
||||
if (!textRun) {
|
||||
NS_WARNING("failed to get textRun, low memory?");
|
||||
return;
|
||||
}
|
||||
|
||||
// include continuations in the range that share the same textrun
|
||||
nsTextFrame* next = nullptr;
|
||||
|
@ -8167,14 +8198,12 @@ nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame,
|
|||
}
|
||||
}
|
||||
|
||||
uint32_t skipStart = iter.ConvertOriginalToSkipped(fstart);
|
||||
uint32_t skipEnd = iter.ConvertOriginalToSkipped(fend);
|
||||
AddFontsFromTextRun(textRun, aFrame->GetContent(), iter,
|
||||
skipStart, skipEnd - skipStart, aFontFaces, aMaxRanges);
|
||||
gfxTextRun::Range range(iter.ConvertOriginalToSkipped(fstart),
|
||||
iter.ConvertOriginalToSkipped(fend));
|
||||
AddFontsFromTextRun(textRun, curr, iter, range, aFontFaces, aMaxRanges);
|
||||
|
||||
curr = next;
|
||||
} while (aFollowContinuations && curr);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
|
@ -2294,12 +2294,12 @@ public:
|
|||
* entire text is to be considered.
|
||||
* aMaxRanges: maximum number of text ranges to record for each face.
|
||||
*/
|
||||
static nsresult GetFontFacesForText(nsIFrame* aFrame,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset,
|
||||
bool aFollowContinuations,
|
||||
UsedFontFaceTable& aResult,
|
||||
uint32_t aMaxRanges);
|
||||
static void GetFontFacesForText(nsIFrame* aFrame,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset,
|
||||
bool aFollowContinuations,
|
||||
UsedFontFaceTable& aResult,
|
||||
uint32_t aMaxRanges);
|
||||
|
||||
/**
|
||||
* Walks the frame tree starting at aFrame looking for textRuns.
|
||||
|
|
|
@ -168,6 +168,62 @@ function RunTest() {
|
|||
}
|
||||
is(familyNames.size, 3, "found all expected families");
|
||||
|
||||
// Testcase involving collapsed whitespace, to test mapping from textrun
|
||||
// offsets back to DOM content offsets.
|
||||
elem = document.getElementById("test5");
|
||||
rng.selectNode(elem);
|
||||
fonts = InspectorUtils.getUsedFontFaces(rng, 10);
|
||||
is(fonts.length, 3, "number of font faces");
|
||||
familyNames.clear();
|
||||
for (var i = 0; i < fonts.length; i++) {
|
||||
var f = fonts[i];
|
||||
familyNames.set(f.CSSFamilyName, true);
|
||||
switch (true) {
|
||||
case f.CSSFamilyName == "capitals":
|
||||
expectRanges(f, ["H"]);
|
||||
break;
|
||||
case f.CSSFamilyName == "lowercase":
|
||||
expectRanges(f, ["ello", "linked", "world"]);
|
||||
break;
|
||||
case f.CSSFamilyName == "gentium":
|
||||
expectRanges(f, ["\n ", "\n ", "\n ", "!\n "]);
|
||||
break;
|
||||
default:
|
||||
// There shouldn't be any other font used
|
||||
ok(false, "unexpected font: " + f.CSSFamilyName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
is(familyNames.size, 3, "found all expected families");
|
||||
|
||||
// Test that fonts used in non-visible elements are not reported,
|
||||
// nor non-visible ranges for fonts that are also used in visible content.
|
||||
elem = document.getElementById("test6");
|
||||
rng.selectNode(elem);
|
||||
fonts = InspectorUtils.getUsedFontFaces(rng, 10);
|
||||
is(fonts.length, 2, "number of font faces");
|
||||
familyNames.clear();
|
||||
for (var i = 0; i < fonts.length; i++) {
|
||||
var f = fonts[i];
|
||||
familyNames.set(f.CSSFamilyName, true);
|
||||
switch (true) {
|
||||
case f.CSSFamilyName == "capitals":
|
||||
ok(false, "font used in hidden element should not be reported");
|
||||
break;
|
||||
case f.CSSFamilyName == "lowercase":
|
||||
expectRanges(f, ["hello", "visible", "world"]);
|
||||
break;
|
||||
case f.CSSFamilyName == "gentium":
|
||||
expectRanges(f, ["\n ", "\n ", "\n ", "!"]);
|
||||
break;
|
||||
default:
|
||||
// There shouldn't be any other font used
|
||||
ok(false, "unexpected font: " + f.CSSFamilyName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
is(familyNames.size, 2, "found all expected families");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
@ -184,6 +240,18 @@ function RunTest() {
|
|||
<div class="gentium" id="test2">Hello العربي World!</div>
|
||||
<div class="gentium" id="test3" style="width:3em">Hello mul­ti­line World!</div>
|
||||
<div class="gentium" id="test4"><span>Hello</span><span>cruel</span>world!</div>
|
||||
<div class="gentium" id="test5">
|
||||
Hello
|
||||
<!-- comment -->
|
||||
<a href="#foo">linked</a>
|
||||
<!-- comment -->
|
||||
world!
|
||||
</div>
|
||||
<div class="gentium" id="test6">
|
||||
hello
|
||||
<a href="#foo" style="visibility:hidden">Non-Visible
|
||||
<span style="visibility:visible">visible</span></a>
|
||||
world!</div>
|
||||
</body>
|
||||
|
||||
</window>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
fuzzy-if(Android,12,1) == select-fieldset-1.html select-fieldset-ref.html
|
||||
fuzzy-if(skiaContent&&!Android,2,17) == select-fieldset-2.html select-fieldset-ref-disabled.html
|
||||
fuzzy-if(Android,12,1) fuzzy-if(skiaContent&&!Android,2,17) == select-fieldset-2.html select-fieldset-ref-disabled.html
|
||||
fuzzy-if(skiaContent&&!Android,2,17) == select-fieldset-3.html select-fieldset-ref-disabled.html
|
||||
fuzzy-if(skiaContent&&!Android,2,13) == select-fieldset-4.html select-fieldset-ref.html
|
||||
fuzzy-if(Android,12,1) fuzzy-if(skiaContent&&!Android,2,13) == select-fieldset-4.html select-fieldset-ref.html
|
||||
== select-fieldset-legend-1.html select-fieldset-legend-ref-1.html
|
||||
fuzzy-if(skiaContent&&!Android,2,6) == select-fieldset-legend-2.html select-fieldset-legend-ref-2.html
|
||||
fuzzy-if(skiaContent&&!Android,2,8) == select-fieldset-legend-3.html select-fieldset-legend-ref-3.html
|
||||
fuzzy-if(Android,12,1) fuzzy-if(skiaContent&&!Android,2,6) == select-fieldset-legend-2.html select-fieldset-legend-ref-2.html
|
||||
fuzzy-if(Android,12,1) fuzzy-if(skiaContent&&!Android,2,8) == select-fieldset-legend-3.html select-fieldset-legend-ref-3.html
|
||||
fuzzy-if(skiaContent,2,12) == select-fieldset-legend-4.html select-fieldset-legend-ref-4.html
|
||||
fuzzy-if(skiaContent,2,5) == select-fieldset-legend-5.html select-fieldset-legend-ref-5.html
|
||||
|
|
|
@ -1106,10 +1106,12 @@ private:
|
|||
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);
|
||||
#ifdef DEBUG
|
||||
NS_ERROR(aMsg);
|
||||
#else
|
||||
printf_stderr("%s\n", aMsg);
|
||||
#endif
|
||||
}
|
||||
|
||||
// This is static so that HandlePref() can increment it easily. This is ok
|
||||
|
@ -1130,13 +1132,13 @@ TestParseErrorHandlePref(const char* aPrefName,
|
|||
{
|
||||
}
|
||||
|
||||
static char* gTestParseErrorMsg;
|
||||
static nsCString gTestParseErrorMsgs;
|
||||
|
||||
static void
|
||||
TestParseErrorHandleError(const char* aMsg)
|
||||
{
|
||||
// aMsg's lifetime is shorter than we need, so duplicate it.
|
||||
gTestParseErrorMsg = moz_xstrdup(aMsg);
|
||||
gTestParseErrorMsgs.Append(aMsg);
|
||||
gTestParseErrorMsgs.Append('\n');
|
||||
}
|
||||
|
||||
// Keep this in sync with the declaration in test/gtest/Parser.cpp.
|
||||
|
@ -1149,10 +1151,10 @@ TestParseError(const char* aText, nsCString& aErrorMsg)
|
|||
TestParseErrorHandlePref,
|
||||
TestParseErrorHandleError);
|
||||
|
||||
// Copy the duplicated error message into the outparam, then free it.
|
||||
aErrorMsg.Assign(gTestParseErrorMsg);
|
||||
free(gTestParseErrorMsg);
|
||||
gTestParseErrorMsg = nullptr;
|
||||
// Copy the error messages into the outparam, then clear them from
|
||||
// gTestParseErrorMsgs.
|
||||
aErrorMsg.Assign(gTestParseErrorMsgs);
|
||||
gTestParseErrorMsgs.Truncate();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -41,6 +41,11 @@
|
|||
//! 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.
|
||||
//!
|
||||
//! The parser performs error recovery. On a syntax error, it will scan forward
|
||||
//! to the next ';' token and then continue parsing. If the syntax error occurs
|
||||
//! in the middle of a token, it will first finish obtaining the current token
|
||||
//! in an appropriate fashion.
|
||||
|
||||
// This parser uses several important optimizations.
|
||||
//
|
||||
|
@ -117,6 +122,10 @@ type ErrorFn = unsafe extern "C" fn(msg: *const c_char);
|
|||
/// `buf` is a null-terminated string. `len` is its length, excluding the
|
||||
/// null terminator.
|
||||
///
|
||||
/// `pref_fn` is called once for each successfully parsed pref.
|
||||
///
|
||||
/// `error_fn` is called once for each parse error detected.
|
||||
///
|
||||
/// Keep this in sync with the prefs_parser_parse() declaration in
|
||||
/// Preferences.cpp.
|
||||
#[no_mangle]
|
||||
|
@ -162,6 +171,13 @@ enum Token {
|
|||
|
||||
// Malformed token.
|
||||
Error(&'static str),
|
||||
|
||||
// Malformed token at a particular line number. For use when
|
||||
// Parser::line_num might not be the right line number when the error is
|
||||
// reported. E.g. if a multi-line string has a bad escape sequence on the
|
||||
// first line, we don't report the error until the string's end has been
|
||||
// reached.
|
||||
ErrorAtLine(&'static str, u32),
|
||||
}
|
||||
|
||||
// We categorize every char by what action should be taken when it appears at
|
||||
|
@ -272,6 +288,7 @@ struct Parser<'t> {
|
|||
line_num: u32, // Current line number within the text.
|
||||
pref_fn: PrefFn, // Callback for processing each pref.
|
||||
error_fn: ErrorFn, // Callback for parse errors.
|
||||
has_errors: bool, // Have we encountered errors?
|
||||
}
|
||||
|
||||
// As described above, we use 0 to represent EOF.
|
||||
|
@ -290,6 +307,7 @@ impl<'t> Parser<'t> {
|
|||
line_num: 1,
|
||||
pref_fn: pref_fn,
|
||||
error_fn: error_fn,
|
||||
has_errors: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,55 +317,49 @@ impl<'t> Parser<'t> {
|
|||
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.
|
||||
|
||||
let mut token = self.get_token(&mut none_str);
|
||||
|
||||
// At the top of the loop we already have a token. In a valid input
|
||||
// this will be either the first token of a new pref, or EOF.
|
||||
loop {
|
||||
// Note: if you add error recovery here, be aware that the
|
||||
// erroneous char may have been the text-ending EOF, in which case
|
||||
// self.i will point one past the end of the text. You should check
|
||||
// for that possibility before getting more chars.
|
||||
|
||||
// EOF?
|
||||
let token = self.get_token(&mut none_str);
|
||||
if token == Token::SingleChar(EOF) {
|
||||
break;
|
||||
}
|
||||
|
||||
// <pref-spec>
|
||||
let (pref_value_kind, is_sticky) = match token {
|
||||
Token::Pref => {
|
||||
(PrefValueKind::Default, false)
|
||||
Token::Pref => (PrefValueKind::Default, false),
|
||||
Token::StickyPref => (PrefValueKind::Default, true),
|
||||
Token::UserPref => (PrefValueKind::User, false),
|
||||
Token::SingleChar(EOF) => return !self.has_errors,
|
||||
_ => {
|
||||
token = self.error_and_recover(
|
||||
token, "expected pref specifier at start of pref definition");
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b'(') {
|
||||
return self.error(token, "expected '(' after pref specifier");
|
||||
token = self.error_and_recover(token, "expected '(' after pref specifier");
|
||||
continue;
|
||||
}
|
||||
|
||||
// <pref-name>
|
||||
let token = self.get_token(&mut name_str);
|
||||
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 '('");
|
||||
token = self.error_and_recover(token, "expected pref name after '('");
|
||||
continue;
|
||||
};
|
||||
|
||||
// ","
|
||||
let token = self.get_token(&mut none_str);
|
||||
token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b',') {
|
||||
return self.error(token, "expected ',' after pref name");
|
||||
token = self.error_and_recover(token, "expected ',' after pref name");
|
||||
continue;
|
||||
}
|
||||
|
||||
// <pref-value>
|
||||
let token = self.get_token(&mut value_str);
|
||||
token = self.get_token(&mut value_str);
|
||||
let (pref_type, pref_value) = match token {
|
||||
Token::True => {
|
||||
(PrefType::Bool, PrefValue { bool_val: true })
|
||||
|
@ -365,12 +377,13 @@ impl<'t> Parser<'t> {
|
|||
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 = self.error_and_recover(
|
||||
Token::Error("integer literal overflowed"), "");
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
Token::SingleChar(b'-') => {
|
||||
let token = self.get_token(&mut none_str);
|
||||
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 {
|
||||
|
@ -378,63 +391,96 @@ impl<'t> Parser<'t> {
|
|||
} 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"), "");
|
||||
token = self.error_and_recover(
|
||||
Token::Error("integer literal overflowed"), "");
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
return self.error(token, "expected integer literal after '-'");
|
||||
token = self.error_and_recover(
|
||||
token, "expected integer literal after '-'");
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
Token::SingleChar(b'+') => {
|
||||
let token = self.get_token(&mut none_str);
|
||||
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"), "");
|
||||
token = self.error_and_recover(
|
||||
Token::Error("integer literal overflowed"), "");
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
return self.error(token, "expected integer literal after '+'");
|
||||
token = self.error_and_recover(token, "expected integer literal after '+'");
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
_ => return self.error(token, "expected pref value after ','")
|
||||
_ => {
|
||||
token = self.error_and_recover(token, "expected pref value after ','");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// ")"
|
||||
let token = self.get_token(&mut none_str);
|
||||
token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b')') {
|
||||
return self.error(token, "expected ')' after pref value");
|
||||
token = self.error_and_recover(token, "expected ')' after pref value");
|
||||
continue;
|
||||
}
|
||||
|
||||
// ";"
|
||||
let token = self.get_token(&mut none_str);
|
||||
token = self.get_token(&mut none_str);
|
||||
if token != Token::SingleChar(b';') {
|
||||
return self.error(token, "expected ';' after ')'");
|
||||
token = self.error_and_recover(token, "expected ';' after ')'");
|
||||
continue;
|
||||
}
|
||||
|
||||
unsafe { (self.pref_fn)(pref_name.as_ptr() as *const c_char, pref_type, pref_value_kind,
|
||||
pref_value, is_sticky) };
|
||||
}
|
||||
|
||||
true
|
||||
token = self.get_token(&mut none_str);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
fn error_and_recover(&mut self, token: Token, msg: &str) -> Token {
|
||||
self.has_errors = true;
|
||||
|
||||
// If `token` is a Token::{Error,ErrorAtLine}, 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, line_num) = match token {
|
||||
Token::Error(token_msg) => (token_msg, self.line_num),
|
||||
Token::ErrorAtLine(token_msg, line_num) => (token_msg, line_num),
|
||||
_ => (msg, self.line_num),
|
||||
};
|
||||
let msg = format!("{}:{}: prefs parse error: {}", self.path, self.line_num, msg);
|
||||
let msg = format!("{}:{}: prefs parse error: {}", self.path, line_num, msg);
|
||||
let msg = std::ffi::CString::new(msg).unwrap();
|
||||
unsafe { (self.error_fn)(msg.as_ptr() as *const c_char) };
|
||||
|
||||
false
|
||||
// "Panic-mode" recovery: consume tokens until one of the following
|
||||
// occurs.
|
||||
// - We hit a semicolon, whereupon we return the following token.
|
||||
// - We hit EOF, whereupon we return EOF.
|
||||
//
|
||||
// For this to work, if the lexing functions hit EOF in an error case
|
||||
// they must unget it so we can safely reget it here.
|
||||
//
|
||||
// If the starting token (passed in above) is EOF we must not get
|
||||
// another token otherwise we will read past the end of `self.buf`.
|
||||
let mut dummy_str = Vec::with_capacity(128);
|
||||
let mut token = token;
|
||||
loop {
|
||||
match token {
|
||||
Token::SingleChar(b';') => return self.get_token(&mut dummy_str),
|
||||
Token::SingleChar(EOF) => return token,
|
||||
_ => {}
|
||||
}
|
||||
token = self.get_token(&mut dummy_str);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -444,10 +490,11 @@ impl<'t> Parser<'t> {
|
|||
c
|
||||
}
|
||||
|
||||
// This function skips the bounds check. Using it at the hottest two call
|
||||
// sites gives a ~15% parsing speed boost.
|
||||
// This function skips the bounds check in non-optimized builds. Using it
|
||||
// at the hottest two call sites gives a ~15% parsing speed boost.
|
||||
#[inline(always)]
|
||||
unsafe fn get_char_unchecked(&mut self) -> u8 {
|
||||
debug_assert!(self.i < self.buf.len());
|
||||
let c = *self.buf.get_unchecked(self.i);
|
||||
self.i += 1;
|
||||
c
|
||||
|
@ -491,9 +538,7 @@ impl<'t> Parser<'t> {
|
|||
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.
|
||||
// Unget EOF so subsequent calls to get_char() are safe.
|
||||
self.unget_char();
|
||||
break;
|
||||
}
|
||||
|
@ -520,6 +565,8 @@ impl<'t> Parser<'t> {
|
|||
self.match_char(b'\n');
|
||||
}
|
||||
EOF => {
|
||||
// Unget EOF so subsequent calls to get_char() are safe.
|
||||
self.unget_char();
|
||||
return false
|
||||
}
|
||||
_ => continue
|
||||
|
@ -536,7 +583,11 @@ impl<'t> Parser<'t> {
|
|||
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
|
||||
_ => {
|
||||
// Unget in case the char was a closing quote or EOF.
|
||||
self.unget_char();
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(value)
|
||||
|
@ -620,24 +671,21 @@ impl<'t> Parser<'t> {
|
|||
continue;
|
||||
}
|
||||
CharKind::Digit => {
|
||||
let mut value = (c - b'0') as u32;
|
||||
let mut value = Some((c - b'0') as u32);
|
||||
loop {
|
||||
let c = self.get_char();
|
||||
match Parser::char_kind(c) {
|
||||
CharKind::Digit => {
|
||||
fn add_digit(v: u32, c: u8) -> Option<u32> {
|
||||
v.checked_mul(10)?.checked_add((c - b'0') as u32)
|
||||
}
|
||||
if let Some(v) = add_digit(value, c) {
|
||||
value = v;
|
||||
} else {
|
||||
return Token::Error("integer literal overflowed");
|
||||
fn add_digit(value: Option<u32>, c: u8) -> Option<u32> {
|
||||
value?.checked_mul(10)?.checked_add((c - b'0') as u32)
|
||||
}
|
||||
value = add_digit(value, c);
|
||||
}
|
||||
CharKind::Keyword => {
|
||||
// Reject things like "123foo".
|
||||
return Token::Error(
|
||||
"unexpected character in integer literal");
|
||||
// Reject things like "123foo". Error recovery
|
||||
// will retokenize from "foo" onward.
|
||||
self.unget_char();
|
||||
return Token::Error("unexpected character in integer literal");
|
||||
}
|
||||
_ => {
|
||||
self.unget_char();
|
||||
|
@ -645,7 +693,10 @@ impl<'t> Parser<'t> {
|
|||
}
|
||||
}
|
||||
}
|
||||
return Token::Int(value);
|
||||
return match value {
|
||||
Some(v) => Token::Int(v),
|
||||
None => Token::Error("integer literal overflowed"),
|
||||
};
|
||||
}
|
||||
CharKind::Hash => {
|
||||
self.match_single_line_comment();
|
||||
|
@ -656,11 +707,19 @@ impl<'t> Parser<'t> {
|
|||
self.line_num += 1;
|
||||
continue;
|
||||
}
|
||||
// Error recovery will retokenize from the next character.
|
||||
_ => return Token::Error("unexpected character")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn string_error_token(&self, token: &mut Token, msg: &'static str) {
|
||||
// We only want to capture the first tokenization error within a string.
|
||||
if *token == Token::String {
|
||||
*token = Token::ErrorAtLine(msg, self.line_num);
|
||||
}
|
||||
}
|
||||
|
||||
// Always inline this because it has a single call site.
|
||||
#[inline(always)]
|
||||
fn get_string_token(&mut self, quote_char: u8, str_buf: &mut Vec<u8>) -> Token {
|
||||
|
@ -690,7 +749,12 @@ impl<'t> Parser<'t> {
|
|||
|
||||
// There were special chars. Re-scan the string, filling in str_buf one
|
||||
// char at a time.
|
||||
//
|
||||
// On error, we change `token` to an error token and then keep going to
|
||||
// the end of the string literal. `str_buf` won't be used in that case.
|
||||
self.i = start;
|
||||
let mut token = Token::String;
|
||||
|
||||
loop {
|
||||
let c = self.get_char();
|
||||
let c2 = if !Parser::is_special_string_char(c) {
|
||||
|
@ -712,10 +776,12 @@ impl<'t> Parser<'t> {
|
|||
if value != 0 {
|
||||
value as u8
|
||||
} else {
|
||||
return Token::Error("\\x00 is not allowed");
|
||||
self.string_error_token(&mut token, "\\x00 is not allowed");
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
return Token::Error("malformed \\x escape sequence");
|
||||
self.string_error_token(&mut token, "malformed \\x escape sequence");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
b'u' => {
|
||||
|
@ -729,28 +795,39 @@ impl<'t> Parser<'t> {
|
|||
// Found a valid low surrogate.
|
||||
utf16.push(lo);
|
||||
} else {
|
||||
return Token::Error(
|
||||
self.string_error_token(
|
||||
&mut token,
|
||||
"invalid low surrogate value after high surrogate");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
if utf16.len() != 2 {
|
||||
return Token::Error(
|
||||
"expected low surrogate after high surrogate");
|
||||
self.string_error_token(
|
||||
&mut token, "expected low surrogate after high surrogate");
|
||||
continue;
|
||||
}
|
||||
} else if value == 0 {
|
||||
return Token::Error("\\u0000 is not allowed");
|
||||
self.string_error_token(&mut token, "\\u0000 is not allowed");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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");
|
||||
self.string_error_token(&mut token, "malformed \\u escape sequence");
|
||||
continue;
|
||||
}
|
||||
continue; // We don't want to str_buf.push(c2) below.
|
||||
}
|
||||
_ => return Token::Error("unexpected escape sequence character after '\\'")
|
||||
_ => {
|
||||
// Unget in case the char is an EOF.
|
||||
self.unget_char();
|
||||
self.string_error_token(
|
||||
&mut token, "unexpected escape sequence character after '\\'");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
} else if c == b'\n' {
|
||||
|
@ -767,7 +844,10 @@ impl<'t> Parser<'t> {
|
|||
}
|
||||
|
||||
} else if c == EOF {
|
||||
return Token::Error("unterminated string literal");
|
||||
// Unget EOF so subsequent calls to get_char() are safe.
|
||||
self.unget_char();
|
||||
self.string_error_token(&mut token, "unterminated string literal");
|
||||
break;
|
||||
|
||||
} else {
|
||||
// This case is only hit for the non-closing quote char.
|
||||
|
@ -777,6 +857,7 @@ impl<'t> Parser<'t> {
|
|||
str_buf.push(c2);
|
||||
}
|
||||
str_buf.push(b'\0');
|
||||
return Token::String;
|
||||
|
||||
token
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,10 +28,11 @@ TEST(PrefsParser, Errors)
|
|||
// 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.)
|
||||
// Valid syntax. (Other testing of more typical valid syntax and semantics is
|
||||
// done in modules/libpref/test/unit/test_parser.js.)
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Normal prefs.
|
||||
P(R"(
|
||||
pref("bool", true);
|
||||
sticky_pref("int", 123);
|
||||
|
@ -40,6 +41,18 @@ user_pref("string", "value");
|
|||
""
|
||||
);
|
||||
|
||||
// Totally empty input.
|
||||
P("",
|
||||
""
|
||||
);
|
||||
|
||||
// Whitespace-only input.
|
||||
P(R"(
|
||||
|
||||
)" "\v \t \v \f",
|
||||
""
|
||||
);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// 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
|
||||
|
@ -47,195 +60,121 @@ user_pref("string", "value");
|
|||
//-------------------------------------------------------------------------
|
||||
|
||||
// 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"
|
||||
"test:3: prefs parse error: integer literal overflowed\n"
|
||||
"test:5: prefs parse error: integer literal overflowed\n"
|
||||
"test:7: prefs parse error: integer literal overflowed\n"
|
||||
"test:8: prefs parse error: integer literal overflowed\n"
|
||||
"test:9: prefs parse error: integer literal overflowed\n"
|
||||
"test:10: prefs parse error: integer literal overflowed\n"
|
||||
"test:11: prefs parse error: integer literal overflowed\n"
|
||||
"test:12: prefs parse error: integer literal overflowed\n"
|
||||
);
|
||||
|
||||
// Other integer errors.
|
||||
|
||||
P(R"(
|
||||
pref("int.unexpected", 100foo);
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected character in integer literal"
|
||||
"test:2: prefs parse error: unexpected character in integer literal\n"
|
||||
);
|
||||
|
||||
// \x escape errors.
|
||||
|
||||
// \x00 is not allowed.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x00bar");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: \\x00 is not allowed"
|
||||
"test:2: prefs parse error: \\x00 is not allowed\n"
|
||||
);
|
||||
|
||||
// End of string after \x.
|
||||
// Various bad things after \x: end of string, punctuation, space, newline,
|
||||
// EOF.
|
||||
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"
|
||||
"test:2: prefs parse error: malformed \\x escape sequence\n"
|
||||
"test:3: prefs parse error: malformed \\x escape sequence\n"
|
||||
"test:4: prefs parse error: malformed \\x escape sequence\n"
|
||||
"test:5: prefs parse error: malformed \\x escape sequence\n"
|
||||
"test:7: prefs parse error: malformed \\x escape sequence\n"
|
||||
);
|
||||
|
||||
// Not enough hex digits.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x1");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
"test:2: prefs parse error: malformed \\x escape sequence\n"
|
||||
);
|
||||
|
||||
// Invalid hex digit.
|
||||
P(R"(
|
||||
pref("string.bad-x-escape", "foo\x1G");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\x escape sequence"
|
||||
"test:2: prefs parse error: malformed \\x escape sequence\n"
|
||||
);
|
||||
|
||||
// \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");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: \\u0000 is not allowed"
|
||||
"test:2: prefs parse error: \\u0000 is not allowed\n"
|
||||
);
|
||||
|
||||
// End of string after \u.
|
||||
// Various bad things after \u: end of string, punctuation, space, newline,
|
||||
// EOF.
|
||||
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"
|
||||
"test:2: prefs parse error: malformed \\u escape sequence\n"
|
||||
"test:3: prefs parse error: malformed \\u escape sequence\n"
|
||||
"test:4: prefs parse error: malformed \\u escape sequence\n"
|
||||
"test:5: prefs parse error: malformed \\u escape sequence\n"
|
||||
"test:7: prefs parse error: malformed \\u escape sequence\n"
|
||||
);
|
||||
|
||||
// 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");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
"test:2: prefs parse error: malformed \\u escape sequence\n"
|
||||
"test:3: prefs parse error: malformed \\u escape sequence\n"
|
||||
"test:4: prefs parse error: malformed \\u escape sequence\n"
|
||||
);
|
||||
|
||||
// Invalid hex digit.
|
||||
P(R"(
|
||||
pref("string.bad-u-escape", "foo\u1G34");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: malformed \\u escape sequence"
|
||||
"test:2: prefs parse error: malformed \\u escape sequence\n"
|
||||
);
|
||||
|
||||
// High surrogate not followed by low surrogate.
|
||||
|
@ -243,8 +182,9 @@ pref("string.bad-u-escape", "foo\u1G34");
|
|||
// an invalid universal-character-name.)
|
||||
P(R"(
|
||||
pref("string.bad-u-surrogate", "foo\)" R"(ud83c,blah");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: expected low surrogate after high surrogate"
|
||||
"test:2: prefs parse error: expected low surrogate after high surrogate\n"
|
||||
);
|
||||
|
||||
// High surrogate followed by invalid low surrogate value.
|
||||
|
@ -252,80 +192,41 @@ pref("string.bad-u-surrogate", "foo\)" R"(ud83c,blah");
|
|||
// an invalid universal-character-name.)
|
||||
P(R"(
|
||||
pref("string.bad-u-surrogate", "foo\)" R"(ud83c\u1234");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: invalid low surrogate value after high surrogate"
|
||||
"test:2: prefs parse error: invalid low surrogate value after high surrogate\n"
|
||||
);
|
||||
|
||||
// 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.
|
||||
// Unlike in JavaScript, \b, \f, \t, \v aren't allowed.
|
||||
P(R"(
|
||||
pref("string.bad-escape", "foo\b");
|
||||
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");
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'"
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:3: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:4: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:5: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
);
|
||||
|
||||
// Non-special letter after \.
|
||||
// Various bad things after \: non-special letter, number, punctuation,
|
||||
// space, newline, EOF.
|
||||
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 '\\'"
|
||||
"test:2: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:3: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:4: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:5: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:6: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
"test:8: prefs parse error: unexpected escape sequence character after '\\'\n"
|
||||
);
|
||||
|
||||
// Unterminated string literals.
|
||||
|
@ -334,94 +235,85 @@ pref("string.bad-escape", "foo\)",
|
|||
P(R"(
|
||||
pref("string.unterminated-string", "foo
|
||||
)",
|
||||
"test:3: prefs parse error: unterminated string literal"
|
||||
"test:3: prefs parse error: unterminated string literal\n"
|
||||
);
|
||||
|
||||
// Alternative case; `int` comes after the string and is seen as a keyword.
|
||||
// The parser then skips to the ';', so no error about the unterminated
|
||||
// string is issued.
|
||||
P(R"(
|
||||
pref("string.unterminated-string", "foo);
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:3: prefs parse error: unknown keyword\n"
|
||||
);
|
||||
|
||||
// Mismatched quotes (1).
|
||||
P(R"(
|
||||
pref("string.unterminated-string", "foo');
|
||||
)",
|
||||
"test:3: prefs parse error: unterminated string literal"
|
||||
"test:3: prefs parse error: unterminated string literal\n"
|
||||
);
|
||||
|
||||
// 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"
|
||||
"test:3: prefs parse error: unterminated string literal\n"
|
||||
);
|
||||
|
||||
// Unknown keywords.
|
||||
P(R"(
|
||||
foo;
|
||||
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"
|
||||
"test:2: prefs parse error: unknown keyword\n"
|
||||
"test:3: prefs parse error: unknown keyword\n"
|
||||
"test:4: prefs parse error: unknown keyword\n"
|
||||
"test:5: prefs parse error: unknown keyword\n"
|
||||
"test:6: prefs parse error: unknown keyword\n"
|
||||
);
|
||||
|
||||
// Unterminated C-style comment
|
||||
// Unterminated C-style comment.
|
||||
P(R"(
|
||||
/* comment
|
||||
)",
|
||||
"test:3: prefs parse error: unterminated /* comment"
|
||||
"test:3: prefs parse error: unterminated /* comment\n"
|
||||
);
|
||||
|
||||
// Malformed comments.
|
||||
|
||||
// Malformed comment.
|
||||
P(R"(
|
||||
/ comment
|
||||
)",
|
||||
"test:2: prefs parse error: expected '/' or '*' after '/'"
|
||||
"test:2: prefs parse error: expected '/' or '*' after '/'\n"
|
||||
);
|
||||
|
||||
// Unexpected characters
|
||||
// C++-style comment ending in EOF (1).
|
||||
P(R"(
|
||||
// comment)",
|
||||
""
|
||||
);
|
||||
|
||||
// C++-style comment ending in EOF (2).
|
||||
P(R"(
|
||||
//)",
|
||||
""
|
||||
);
|
||||
|
||||
// Various 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"
|
||||
"test:2: prefs parse error: unexpected character\n"
|
||||
"test:3: prefs parse error: unexpected character\n"
|
||||
"test:4: prefs parse error: unexpected character\n"
|
||||
"test:5: prefs parse error: unexpected character\n"
|
||||
);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
@ -430,65 +322,84 @@ pref["unexpected.chars": true];
|
|||
|
||||
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);
|
||||
pref("parse.error", +"value");
|
||||
pref("parse.error", true;
|
||||
)",
|
||||
"test:2: prefs parse error: expected ')' after pref value"
|
||||
pref("parse.error", true)
|
||||
pref("int.ok", 1);
|
||||
pref("parse.error", true))",
|
||||
"test:2: prefs parse error: expected pref specifier at start of pref definition\n"
|
||||
"test:3: prefs parse error: expected '(' after pref specifier\n"
|
||||
"test:4: prefs parse error: expected pref name after '('\n"
|
||||
"test:5: prefs parse error: expected ',' after pref name\n"
|
||||
"test:6: prefs parse error: expected pref value after ','\n"
|
||||
"test:7: prefs parse error: expected integer literal after '-'\n"
|
||||
"test:8: prefs parse error: expected integer literal after '+'\n"
|
||||
"test:9: prefs parse error: expected ')' after pref value\n"
|
||||
"test:11: prefs parse error: expected ';' after ')'\n"
|
||||
"test:12: prefs parse error: expected ';' after ')'\n"
|
||||
);
|
||||
|
||||
// Parse errors involving unexpected EOF.
|
||||
|
||||
P(R"(
|
||||
pref)",
|
||||
"test:2: prefs parse error: expected '(' after pref specifier\n"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", true)
|
||||
pref("parse.error", true)
|
||||
)",
|
||||
"test:3: prefs parse error: expected ';' after ')'"
|
||||
pref()",
|
||||
"test:2: prefs parse error: expected pref name after '('\n"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error")",
|
||||
"test:2: prefs parse error: expected ',' after pref name\n"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error",)",
|
||||
"test:2: prefs parse error: expected pref value after ','\n"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", -)",
|
||||
"test:2: prefs parse error: expected integer literal after '-'\n"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", +)",
|
||||
"test:2: prefs parse error: expected integer literal after '+'\n"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", true)",
|
||||
"test:2: prefs parse error: expected ')' after pref value\n"
|
||||
);
|
||||
|
||||
P(R"(
|
||||
pref("parse.error", true))",
|
||||
"test:2: prefs parse error: expected ';' after ')'\n"
|
||||
);
|
||||
|
||||
// This is something we saw in practice with the old parser, which allowed
|
||||
// repeated semicolons.
|
||||
P(R"(
|
||||
pref("parse.error", true);;
|
||||
pref("parse.error", true);;;
|
||||
pref("parse.error", true);;;;
|
||||
pref("int.ok", 0);
|
||||
)",
|
||||
"test:2: prefs parse error: expected pref specifier at start of pref definition"
|
||||
"test:2: prefs parse error: expected pref specifier at start of pref definition\n"
|
||||
"test:3: prefs parse error: expected pref specifier at start of pref definition\n"
|
||||
"test:3: prefs parse error: expected pref specifier at start of pref definition\n"
|
||||
"test:4: prefs parse error: expected pref specifier at start of pref definition\n"
|
||||
"test:4: prefs parse error: expected pref specifier at start of pref definition\n"
|
||||
"test:4: prefs parse error: expected pref specifier at start of pref definition\n"
|
||||
);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
@ -501,24 +412,24 @@ pref("parse.error", true);;
|
|||
// because MSVC somehow swallows any \r that appears in them.)
|
||||
|
||||
P("\n \r \r\n bad",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
"test:4: prefs parse error: unknown keyword\n"
|
||||
);
|
||||
|
||||
P("#\n#\r#\r\n bad",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
"test:4: prefs parse error: unknown keyword\n"
|
||||
);
|
||||
|
||||
P("//\n//\r//\r\n bad",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
"test:4: prefs parse error: unknown keyword\n"
|
||||
);
|
||||
|
||||
P("/*\n \r \r\n*/ bad",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
"test:4: prefs parse error: unknown keyword\n"
|
||||
);
|
||||
|
||||
// Note: the escape sequences do *not* affect the line number.
|
||||
P("pref(\"foo\\n\n foo\\r\r foo\\r\\n\r\n foo\", bad);",
|
||||
"test:4: prefs parse error: unknown keyword"
|
||||
"test:4: prefs parse error: unknown keyword\n"
|
||||
);
|
||||
|
||||
// clang-format on
|
||||
|
|
|
@ -670,7 +670,7 @@ nsInputStreamPump::OnStateStop()
|
|||
if (!NS_IsMainThread()) {
|
||||
// This method can be called on a different thread if nsInputStreamPump
|
||||
// is used off the main-thread.
|
||||
nsresult rv = NS_DispatchToMainThread(
|
||||
nsresult rv = mLabeledMainThreadTarget->Dispatch(
|
||||
NewRunnableMethod("nsInputStreamPump::CallOnStateStop",
|
||||
this,
|
||||
&nsInputStreamPump::CallOnStateStop));
|
||||
|
|
|
@ -4594,7 +4594,8 @@ nsHalfOpenSocket::SetFastOpenConnected(nsresult aError, bool aWillRetry)
|
|||
MOZ_ASSERT((mFastOpenStatus == TFO_NOT_TRIED) ||
|
||||
(mFastOpenStatus == TFO_DATA_SENT) ||
|
||||
(mFastOpenStatus == TFO_TRIED) ||
|
||||
(mFastOpenStatus == TFO_DATA_COOKIE_NOT_ACCEPTED));
|
||||
(mFastOpenStatus == TFO_DATA_COOKIE_NOT_ACCEPTED) ||
|
||||
(mFastOpenStatus == TFO_DISABLED));
|
||||
|
||||
RefPtr<nsHalfOpenSocket> deleteProtector(this);
|
||||
|
||||
|
|
|
@ -98,8 +98,8 @@ reftest:
|
|||
virtualization: virtual-with-gpu
|
||||
chunks:
|
||||
by-test-platform:
|
||||
android-4.3-arm7-api-16/debug: 48
|
||||
android.*: 24
|
||||
android-4.3-arm7-api-16/debug: 56
|
||||
android.*: 28
|
||||
macosx64.*/opt: 1
|
||||
macosx64.*/debug: 2
|
||||
windows10-64.*/opt: 1
|
||||
|
|
|
@ -13,7 +13,7 @@ if [ -f /etc/lsb-release ]; then
|
|||
# shellcheck disable=SC1091
|
||||
. /etc/lsb-release
|
||||
|
||||
if [ "${DISTRIB_ID}" = "Ubuntu" ] && [ "${DISTRIB_RELEASE}" = "16.04" ]
|
||||
if [ "${DISTRIB_ID}" = "Ubuntu" ] && [[ "${DISTRIB_RELEASE}" = "16.04" || "${DISTRIB_RELEASE}" = "17.10" ]]
|
||||
then
|
||||
HG_DEB=1
|
||||
HG_DIGEST=458746bd82b4732c72c611f1041f77a47a683bc75ff3f6ab7ed86ea394f48d94cd7e2d3d1d5b020906318a9a24bea27401a3a63d7e645514dbc2cb581621977f
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
FROM ubuntu:16.04
|
||||
# Ideally we'd use LTS here, but 16.04 doesn't have a new enough
|
||||
# p7zip-full for us. We should change this to the next LTS
|
||||
# (18.04), once available
|
||||
FROM ubuntu:17.10
|
||||
|
||||
MAINTAINER release@mozilla.com
|
||||
|
||||
|
|
|
@ -159,9 +159,6 @@ class Theme {
|
|||
case "toolbar_vertical_separator":
|
||||
case "button_background_hover":
|
||||
case "button_background_active":
|
||||
case "popup":
|
||||
case "popup_text":
|
||||
case "popup_border":
|
||||
this.lwtStyles[color] = cssColor;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -152,18 +152,6 @@
|
|||
"button_background_active": {
|
||||
"$ref": "ThemeColor",
|
||||
"optional": true
|
||||
},
|
||||
"popup": {
|
||||
"$ref": "ThemeColor",
|
||||
"optional": true
|
||||
},
|
||||
"popup_text": {
|
||||
"$ref": "ThemeColor",
|
||||
"optional": true
|
||||
},
|
||||
"popup_border": {
|
||||
"$ref": "ThemeColor",
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": { "$ref": "UnrecognizedProperty" }
|
||||
|
|
|
@ -20,4 +20,3 @@ support-files =
|
|||
[browser_ext_themes_toolbars.js]
|
||||
[browser_ext_themes_toolbarbutton_icons.js]
|
||||
[browser_ext_themes_toolbarbutton_colors.js]
|
||||
[browser_ext_themes_arrowpanels.js]
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
function openIdentityPopup() {
|
||||
let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
|
||||
gIdentityHandler._identityBox.click();
|
||||
return promise;
|
||||
}
|
||||
|
||||
function closeIdentityPopup() {
|
||||
let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
|
||||
gIdentityHandler._identityPopup.hidePopup();
|
||||
return promise;
|
||||
}
|
||||
|
||||
// This test checks applied WebExtension themes that attempt to change
|
||||
// popup properties
|
||||
|
||||
add_task(async function test_popup_styling(browser, accDoc) {
|
||||
const POPUP_BACKGROUND_COLOR = "#FF0000";
|
||||
const POPUP_TEXT_COLOR = "#008000";
|
||||
const POPUP_BORDER_COLOR = "#0000FF";
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"theme": {
|
||||
"images": {
|
||||
"headerURL": "image1.png",
|
||||
},
|
||||
"colors": {
|
||||
"accentcolor": ACCENT_COLOR,
|
||||
"textcolor": TEXT_COLOR,
|
||||
"popup": POPUP_BACKGROUND_COLOR,
|
||||
"popup_text": POPUP_TEXT_COLOR,
|
||||
"popup_border": POPUP_BORDER_COLOR,
|
||||
},
|
||||
},
|
||||
},
|
||||
files: {
|
||||
"image1.png": BACKGROUND,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: "https://example.com"}, async function(browser) {
|
||||
await extension.startup();
|
||||
|
||||
// Open the information arrow panel
|
||||
await openIdentityPopup();
|
||||
|
||||
let arrowContent = document.getAnonymousElementByAttribute(gIdentityHandler._identityPopup, "class", "panel-arrowcontent");
|
||||
let arrowContentComputedStyle = window.getComputedStyle(arrowContent);
|
||||
// Ensure popup background color was set properly
|
||||
Assert.equal(
|
||||
arrowContentComputedStyle.getPropertyValue("background-color"),
|
||||
`rgb(${hexToRGB(POPUP_BACKGROUND_COLOR).join(", ")})`,
|
||||
"Popup background color should have been themed"
|
||||
);
|
||||
|
||||
// Ensure popup text color was set properly
|
||||
Assert.equal(
|
||||
arrowContentComputedStyle.getPropertyValue("color"),
|
||||
`rgb(${hexToRGB(POPUP_TEXT_COLOR).join(", ")})`,
|
||||
"Popup text color should have been themed"
|
||||
);
|
||||
|
||||
// Ensure popup border color was set properly
|
||||
if (AppConstants.platform == "macosx") {
|
||||
Assert.ok(
|
||||
arrowContentComputedStyle.getPropertyValue("box-shadow").includes(`rgb(${hexToRGB(POPUP_BORDER_COLOR).join(", ")})`),
|
||||
"Popup border color should be set"
|
||||
);
|
||||
} else {
|
||||
testBorderColor(arrowContent, POPUP_BORDER_COLOR);
|
||||
}
|
||||
|
||||
await closeIdentityPopup();
|
||||
await extension.unload();
|
||||
});
|
||||
});
|
|
@ -10,6 +10,21 @@ add_task(async function setup() {
|
|||
]});
|
||||
});
|
||||
|
||||
function testBorderColor(element, expected) {
|
||||
Assert.equal(window.getComputedStyle(element).borderLeftColor,
|
||||
hexToCSS(expected),
|
||||
"Field left border color should be set.");
|
||||
Assert.equal(window.getComputedStyle(element).borderRightColor,
|
||||
hexToCSS(expected),
|
||||
"Field right border color should be set.");
|
||||
Assert.equal(window.getComputedStyle(element).borderTopColor,
|
||||
hexToCSS(expected),
|
||||
"Field top border color should be set.");
|
||||
Assert.equal(window.getComputedStyle(element).borderBottomColor,
|
||||
hexToCSS(expected),
|
||||
"Field bottom border color should be set.");
|
||||
}
|
||||
|
||||
add_task(async function test_support_toolbar_field_properties() {
|
||||
const TOOLBAR_FIELD_BACKGROUND = "#ff00ff";
|
||||
const TOOLBAR_FIELD_COLOR = "#00ff00";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* exported ACCENT_COLOR, BACKGROUND, ENCODED_IMAGE_DATA, FRAME_COLOR, TAB_TEXT_COLOR,
|
||||
TEXT_COLOR, BACKGROUND_TAB_TEXT_COLOR, imageBufferFromDataURI, hexToCSS, hexToRGB, testBorderColor */
|
||||
TEXT_COLOR, BACKGROUND_TAB_TEXT_COLOR, imageBufferFromDataURI, hexToCSS, hexToRGB */
|
||||
|
||||
"use strict";
|
||||
|
||||
|
@ -53,19 +53,3 @@ function imageBufferFromDataURI(encodedImageData) {
|
|||
let decodedImageData = atob(encodedImageData);
|
||||
return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
|
||||
}
|
||||
|
||||
function testBorderColor(element, expected) {
|
||||
let computedStyle = window.getComputedStyle(element);
|
||||
Assert.equal(computedStyle.borderLeftColor,
|
||||
hexToCSS(expected),
|
||||
"Element left border color should be set.");
|
||||
Assert.equal(computedStyle.borderRightColor,
|
||||
hexToCSS(expected),
|
||||
"Element right border color should be set.");
|
||||
Assert.equal(computedStyle.borderTopColor,
|
||||
hexToCSS(expected),
|
||||
"Element top border color should be set.");
|
||||
Assert.equal(computedStyle.borderBottomColor,
|
||||
hexToCSS(expected),
|
||||
"Element bottom border color should be set.");
|
||||
}
|
||||
|
|
|
@ -197,15 +197,15 @@ nsBinaryOutputStream::Write64(uint64_t aNum)
|
|||
NS_IMETHODIMP
|
||||
nsBinaryOutputStream::WriteFloat(float aFloat)
|
||||
{
|
||||
NS_ASSERTION(sizeof(float) == sizeof(uint32_t),
|
||||
"False assumption about sizeof(float)");
|
||||
static_assert(sizeof(float) == sizeof(uint32_t),
|
||||
"False assumption about sizeof(float)");
|
||||
return Write32(*reinterpret_cast<uint32_t*>(&aFloat));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBinaryOutputStream::WriteDouble(double aDouble)
|
||||
{
|
||||
NS_ASSERTION(sizeof(double) == sizeof(uint64_t),
|
||||
static_assert(sizeof(double) == sizeof(uint64_t),
|
||||
"False assumption about sizeof(double)");
|
||||
return Write64(*reinterpret_cast<uint64_t*>(&aDouble));
|
||||
}
|
||||
|
@ -370,11 +370,9 @@ nsBinaryOutputStream::WriteID(const nsIID& aIID)
|
|||
return rv;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
rv = Write8(aIID.m3[i]);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
rv = WriteBytes(reinterpret_cast<const char*>(&aIID.m3[0]), sizeof(aIID.m3));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -625,15 +623,15 @@ nsBinaryInputStream::Read64(uint64_t* aNum)
|
|||
NS_IMETHODIMP
|
||||
nsBinaryInputStream::ReadFloat(float* aFloat)
|
||||
{
|
||||
NS_ASSERTION(sizeof(float) == sizeof(uint32_t),
|
||||
"False assumption about sizeof(float)");
|
||||
static_assert(sizeof(float) == sizeof(uint32_t),
|
||||
"False assumption about sizeof(float)");
|
||||
return Read32(reinterpret_cast<uint32_t*>(aFloat));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBinaryInputStream::ReadDouble(double* aDouble)
|
||||
{
|
||||
NS_ASSERTION(sizeof(double) == sizeof(uint64_t),
|
||||
static_assert(sizeof(double) == sizeof(uint64_t),
|
||||
"False assumption about sizeof(double)");
|
||||
return Read64(reinterpret_cast<uint64_t*>(aDouble));
|
||||
}
|
||||
|
@ -714,7 +712,7 @@ WriteSegmentToString(nsIInputStream* aStream,
|
|||
uint32_t* aWriteCount)
|
||||
{
|
||||
NS_PRECONDITION(aCount > 0, "Why are we being told to write 0 bytes?");
|
||||
NS_PRECONDITION(sizeof(char16_t) == 2, "We can't handle other sizes!");
|
||||
static_assert(sizeof(char16_t) == 2, "We can't handle other sizes!");
|
||||
|
||||
WriteStringClosure* closure = static_cast<WriteStringClosure*>(aClosure);
|
||||
char16_t* cursor = closure->mWriteCursor;
|
||||
|
@ -1009,11 +1007,14 @@ nsBinaryInputStream::ReadID(nsID* aResult)
|
|||
return rv;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
rv = Read8(&aResult->m3[i]);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
const uint32_t toRead = sizeof(aResult->m3);
|
||||
uint32_t bytesRead = 0;
|
||||
rv = Read(reinterpret_cast<char*>(&aResult->m3[0]), toRead, &bytesRead);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
if (bytesRead != toRead) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
|
Загрузка…
Ссылка в новой задаче