Merge inbound to mozilla-central. a=merge

This commit is contained in:
Gurzau Raul 2018-02-21 12:01:33 +02:00
Родитель 5e33371f9e da3fbf11ae
Коммит 082f8e6d89
44 изменённых файлов: 1126 добавлений и 681 удалений

Просмотреть файл

@ -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&#xAD;ti&#xAD;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;