Merge mozilla-central to inbound. a=merge

This commit is contained in:
Cosmin Sabou 2018-05-17 12:49:47 +03:00
commit caba9f10be
85 changed files with 1827 additions and 668 deletions

View File

@ -13,8 +13,9 @@ var isDevtools = SimpleTest.harnessParameters.subsuite == "devtools";
var gExceptionPaths = [
"chrome://browser/content/defaultthemes/",
"chrome://browser/locale/searchplugins/",
"resource://app/defaults/blocklists/",
"resource://app/defaults/pinning/",
"resource://app/defaults/settings/blocklists/",
"resource://app/defaults/settings/main/",
"resource://app/defaults/settings/pinning/",
"resource://app/defaults/preferences/",
"resource://gre/modules/commonjs/",
"resource://gre/defaults/pref/",

View File

@ -150,3 +150,6 @@ browser/features/formautofill@mozilla.org/chrome/content/editCreditCard.xhtml
browser/chrome/browser/res/payments/formautofill/editCreditCard.xhtml
browser/features/formautofill@mozilla.org/chrome/content/autofillEditForms.js
browser/chrome/browser/res/payments/formautofill/autofillEditForms.js
# Bug 1451050 - Remote settings empty dumps (will be populated with data eventually)
browser/defaults/settings/pinning/pins.json
browser/defaults/settings/main/tippytop.json

View File

@ -449,8 +449,9 @@
@RESPATH@/greprefs.js
@RESPATH@/defaults/autoconfig/prefcalls.js
@RESPATH@/browser/defaults/permissions
@RESPATH@/browser/defaults/blocklists
@RESPATH@/browser/defaults/pinning
@RESPATH@/browser/defaults/settings/blocklists
@RESPATH@/browser/defaults/settings/pinning
@RESPATH@/browser/defaults/settings/main
; Warning: changing the path to channel-prefs.js can cause bugs (Bug 756325)
; Technically this is an app pref file, but we are keeping it in the original

View File

@ -123,13 +123,14 @@ if test "$GNU_CC"; then
if test -z "$DEVELOPER_OPTIONS"; then
CFLAGS="$CFLAGS -ffunction-sections -fdata-sections"
CXXFLAGS="$CXXFLAGS -ffunction-sections -fdata-sections"
# For MinGW, we need big-obj otherwise we create too many sections in Unified builds
if test "${OS_ARCH}" = "WINNT"; then
CFLAGS="$CFLAGS -Wa,-mbig-obj"
CXXFLAGS="$CXXFLAGS -Wa,-mbig-obj"
fi
fi
# For MinGW, we need big-obj otherwise we create too many sections in Unified builds
if test "${OS_ARCH}" = "WINNT"; then
CFLAGS="$CFLAGS -Wa,-mbig-obj"
CXXFLAGS="$CXXFLAGS -Wa,-mbig-obj"
fi
CFLAGS="$CFLAGS -fno-math-errno"
CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-math-errno"
fi

View File

@ -126,8 +126,7 @@ var PromisesActor = protocol.ActorClassWithSpec(promisesSpec, {
sources: () => this.parentActor.sources,
createEnvironmentActor: () => DevToolsUtils.reportException(
"PromisesActor", Error("createEnvironmentActor not yet implemented")),
getGlobalDebugObject: () => DevToolsUtils.reportException(
"PromisesActor", Error("getGlobalDebugObject not yet implemented")),
getGlobalDebugObject: () => null,
});
this._navigationLifetimePool.addActor(actor);

View File

@ -239,6 +239,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CustomElementRegistry)
tmp->mConstructors.clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomDefinitions)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWhenDefinedPromiseMap)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mElementCreationCallbacks)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -246,6 +247,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CustomElementRegistry)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomDefinitions)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWhenDefinedPromiseMap)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElementCreationCallbacks)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
@ -301,11 +303,40 @@ CustomElementRegistry::IsCustomElementEnabled(nsIDocument* aDoc)
return XRE_IsParentProcess() && nsContentUtils::AllowXULXBLForPrincipal(aDoc->NodePrincipal());
}
NS_IMETHODIMP
CustomElementRegistry::RunCustomElementCreationCallback::Run()
{
ErrorResult er;
nsDependentAtomString value(mAtom);
mCallback->Call(value, er);
MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()),
"chrome JavaScript error in the callback.");
MOZ_ASSERT(mRegistry->mCustomDefinitions.GetWeak(mAtom),
"Callback should define the definition of type.");
MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom),
"Callback should be removed.");
return NS_OK;
}
CustomElementDefinition*
CustomElementRegistry::LookupCustomElementDefinition(nsAtom* aNameAtom,
nsAtom* aTypeAtom) const
nsAtom* aTypeAtom)
{
CustomElementDefinition* data = mCustomDefinitions.GetWeak(aTypeAtom);
if (!data) {
RefPtr<CustomElementCreationCallback> callback;
mElementCreationCallbacks.Get(aTypeAtom, getter_AddRefs(callback));
if (callback) {
RefPtr<Runnable> runnable =
new RunCustomElementCreationCallback(this, aTypeAtom, callback);
nsContentUtils::AddScriptRunner(runnable);
mElementCreationCallbacks.Remove(aTypeAtom);
}
}
if (data && data->mLocalName == aNameAtom) {
return data;
}
@ -951,6 +982,28 @@ CustomElementRegistry::Define(const nsAString& aName,
promise->MaybeResolveWithUndefined();
}
/**
* Clean-up mElementCreationCallbacks (if it exists)
*/
mElementCreationCallbacks.Remove(nameAtom);
}
void
CustomElementRegistry::SetElementCreationCallback(const nsAString& aName,
CustomElementCreationCallback& aCallback,
ErrorResult& aRv)
{
RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
if (mElementCreationCallbacks.GetWeak(nameAtom) ||
mCustomDefinitions.GetWeak(nameAtom)) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return;
}
RefPtr<CustomElementCreationCallback> callback = &aCallback;
mElementCreationCallbacks.Put(nameAtom, callback.forget());
return;
}
void

View File

@ -12,6 +12,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/CustomElementRegistryBinding.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/WebComponentsBinding.h"
@ -366,12 +367,37 @@ public:
explicit CustomElementRegistry(nsPIDOMWindowInner* aWindow);
private:
class RunCustomElementCreationCallback : public mozilla::Runnable
{
public:
NS_DECL_NSIRUNNABLE
explicit RunCustomElementCreationCallback(CustomElementRegistry* aRegistry,
nsAtom* aAtom,
CustomElementCreationCallback* aCallback)
: mozilla::Runnable("CustomElementRegistry::RunCustomElementCreationCallback")
#ifdef DEBUG
, mRegistry(aRegistry)
#endif
, mAtom(aAtom)
, mCallback(aCallback)
{
}
private:
#ifdef DEBUG
RefPtr<CustomElementRegistry> mRegistry;
#endif
RefPtr<nsAtom> mAtom;
RefPtr<CustomElementCreationCallback> mCallback;
};
public:
/**
* Looking up a custom element definition.
* https://html.spec.whatwg.org/#look-up-a-custom-element-definition
*/
CustomElementDefinition* LookupCustomElementDefinition(
nsAtom* aNameAtom, nsAtom* aTypeAtom) const;
nsAtom* aNameAtom, nsAtom* aTypeAtom);
CustomElementDefinition* LookupCustomElementDefinition(
JSContext* aCx, JSObject *aConstructor) const;
@ -419,6 +445,8 @@ private:
typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, CustomElementDefinition>
DefinitionMap;
typedef nsRefPtrHashtable<nsRefPtrHashKey<nsAtom>, CustomElementCreationCallback>
ElementCreationCallbackMap;
typedef nsClassHashtable<nsRefPtrHashKey<nsAtom>,
nsTHashtable<nsRefPtrHashKey<nsIWeakReference>>>
CandidateMap;
@ -432,6 +460,11 @@ private:
// defined.
DefinitionMap mCustomDefinitions;
// Hashtable for chrome-only callbacks that is called *before* we return
// a CustomElementDefinition, when the typeAtom matches.
// The callbacks are registered with the setElementCreationCallback method.
ElementCreationCallbackMap mElementCreationCallbacks;
// Hashtable for looking up definitions by using constructor as key.
// Custom elements' name are stored here and we need to lookup
// mCustomDefinitions again to get definitions.
@ -484,6 +517,10 @@ public:
JS::MutableHandle<JS::Value> aRetVal);
already_AddRefed<Promise> WhenDefined(const nsAString& aName, ErrorResult& aRv);
// Chrome-only method that give JS an opportunity to only load the custom
// element definition script when needed.
void SetElementCreationCallback(const nsAString& aName, CustomElementCreationCallback& aCallback, ErrorResult& aRv);
};
class MOZ_RAII AutoCEReaction final {

View File

@ -27,6 +27,7 @@ support-files =
[test_custom_element_stack.html]
[test_custom_element_define.html]
[test_custom_element_define_parser.html]
[test_custom_element_set_element_creation_callback.html]
[test_custom_element_template.html]
[test_detached_style.html]
[test_document_adoptnode.html]

View File

@ -0,0 +1,170 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1460815
-->
<head>
<title>Test for customElements.setElementCreationCallback</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460815">Bug 1460815</a>
<div>
</div>
<script>
let registry = SpecialPowers.wrap(customElements);
function simpleTest() {
let callbackCalled = false;
class XObjElement extends HTMLElement {};
registry.setElementCreationCallback("x-html-obj-elem", (type) => {
if (callbackCalled) {
ok(false, "Callback should not be invoked more than once.");
}
callbackCalled = true;
is(type, "x-html-obj-elem", "Type is passed to the callback.");
customElements.define("x-html-obj-elem", XObjElement);
});
ok(!callbackCalled, "Callback should not be called.");
let el = document.createElement("x-html-obj-elem");
ok(callbackCalled, "Callback should be called.");
isnot(Object.getPrototypeOf(el), XObjElement.prototype, "Created element is not upgraded.");
document.body.appendChild(el);
is(Object.getPrototypeOf(el), XObjElement.prototype, "Created element should have the prototype of the custom type.");
}
function multipleDefinitionTest() {
let callbackCalled = false;
class XObjElement1 extends HTMLElement {};
class XObjElement2 extends HTMLElement {};
let callback = (type) => {
if (callbackCalled) {
ok(false, "Callback should not be invoked more than once.");
}
callbackCalled = true;
is(type, "x-html-obj-elem1", "Type is passed to the callback.");
customElements.define("x-html-obj-elem1", XObjElement1);
customElements.define("x-html-obj-elem2", XObjElement2);
};
registry.setElementCreationCallback("x-html-obj-elem1", callback);
registry.setElementCreationCallback("x-html-obj-elem2", callback);
ok(!callbackCalled, "Callback should not be called.");
let el1 = document.createElement("x-html-obj-elem1");
ok(callbackCalled, "Callback should be called.");
isnot(Object.getPrototypeOf(el1), XObjElement1.prototype, "Created element is not upgraded.");
document.body.appendChild(el1);
is(Object.getPrototypeOf(el1), XObjElement1.prototype, "Created element should have the prototype of the custom type.");
let el2 = document.createElement("x-html-obj-elem2");
is(Object.getPrototypeOf(el2), XObjElement2.prototype, "Created element should have the prototype of the custom type.");
}
function throwIfDefined() {
let callbackCalled = false;
class XObjElement3 extends HTMLElement {};
customElements.define("x-html-obj-elem3", XObjElement3);
try {
registry.setElementCreationCallback(
"x-html-obj-elem3", () => callbackCalled = true);
} catch (e) {
ok(true, "Set a callback on defined type should throw.");
}
ok(!callbackCalled, "Callback should not be called.");
}
function throwIfSetTwice() {
let callbackCalled = false;
registry.setElementCreationCallback(
"x-html-obj-elem4", () => callbackCalled = true);
try {
registry.setElementCreationCallback(
"x-html-obj-elem4", () => callbackCalled = true);
} catch (e) {
ok(true, "Callack shouldn't be set twice on the same type.");
}
ok(!callbackCalled, "Callback should not be called.");
}
function simpleExtendedTest() {
let callbackCalled = false;
class ExtendButton extends HTMLButtonElement {};
registry.setElementCreationCallback("x-extended-button", (type) => {
if (callbackCalled) {
ok(false, "Callback should not be invoked more than once.");
}
callbackCalled = true;
customElements.define("x-extended-button", ExtendButton, { extends: "button" });
is(type, "x-extended-button", "Type is passed to the callback.");
});
ok(!callbackCalled, "Callback should not be called.");
let el = document.createElement("button", { is: "x-extended-button"});
ok(callbackCalled, "Callback should be called.");
isnot(Object.getPrototypeOf(el), ExtendButton.prototype, "Created element is not upgraded.");
document.body.appendChild(el);
is(Object.getPrototypeOf(el), ExtendButton.prototype, "Created element should have the prototype of the extended type.");
is(el.getAttribute("is"), "x-extended-button", "The |is| attribute of the created element should be the extended type.");
}
function simpleInnerHTMLTest() {
let callbackCalled = false;
class XObjElement4 extends HTMLElement {};
registry.setElementCreationCallback("x-html-obj-elem5", (type) => {
if (callbackCalled) {
ok(false, "Callback should not be invoked more than once.");
}
callbackCalled = true;
is(type, "x-html-obj-elem5", "Type is passed to the callback.");
customElements.define("x-html-obj-elem5", XObjElement4);
});
ok(!callbackCalled, "Callback should not be called.");
let p = document.createElement("p");
p.innerHTML = "<x-html-obj-elem5></x-html-obj-elem5>";
let el = p.firstChild;
ok(callbackCalled, "Callback should be called.");
isnot(Object.getPrototypeOf(el), XObjElement4.prototype, "Created element is not upgraded.");
document.body.appendChild(p);
is(Object.getPrototypeOf(el), XObjElement4.prototype, "Created element should have the prototype of the custom type.");
}
function twoElementInnerHTMLTest() {
let callbackCalled = false;
class XObjElement5 extends HTMLElement {};
registry.setElementCreationCallback("x-html-obj-elem6", (type) => {
if (callbackCalled) {
ok(false, "Callback should not be invoked more than once.");
}
callbackCalled = true;
is(type, "x-html-obj-elem6", "Type is passed to the callback.");
customElements.define("x-html-obj-elem6", XObjElement5);
});
ok(!callbackCalled, "Callback should not be called.");
let p = document.createElement("p");
p.innerHTML =
"<x-html-obj-elem6></x-html-obj-elem6><x-html-obj-elem6></x-html-obj-elem6>";
let el1 = p.firstChild;
let el2 = p.lastChild;
ok(callbackCalled, "Callback should be called.");
isnot(Object.getPrototypeOf(el1), XObjElement5.prototype, "Created element is not upgraded.");
isnot(Object.getPrototypeOf(el2), XObjElement5.prototype, "Created element is not upgraded.");
document.body.appendChild(p);
is(Object.getPrototypeOf(el1), XObjElement5.prototype, "Created element should have the prototype of the custom type.");
is(Object.getPrototypeOf(el2), XObjElement5.prototype, "Created element should have the prototype of the custom type.");
}
function startTest() {
simpleTest();
multipleDefinitionTest();
throwIfDefined();
throwIfSetTwice();
simpleExtendedTest();
simpleInnerHTMLTest();
twoElementInnerHTMLTest();
}
startTest();
</script>
</body>
</html>

View File

@ -8,6 +8,8 @@ interface CustomElementRegistry {
[CEReactions, Throws]
void define(DOMString name, Function functionConstructor,
optional ElementDefinitionOptions options);
[ChromeOnly, Throws]
void setElementCreationCallback(DOMString name, CustomElementCreationCallback callback);
any get(DOMString name);
[Throws]
Promise<void> whenDefined(DOMString name);
@ -17,3 +19,4 @@ dictionary ElementDefinitionOptions {
DOMString extends;
};
callback CustomElementCreationCallback = void (DOMString name);

View File

@ -4009,6 +4009,13 @@ EditorBase::SplitNodeDeepWithTransaction(
nsCOMPtr<nsIContent> newLeftNodeOfMostAncestor;
EditorDOMPoint atStartOfRightNode(aStartOfDeepestRightNode);
while (true) {
// Need to insert rules code call here to do things like not split a list
// if you are after the last <li> or before the first, etc. For now we
// just have some smarts about unneccessarily splitting text nodes, which
// should be universal enough to put straight in this EditorBase routine.
if (NS_WARN_IF(!atStartOfRightNode.GetContainerAsContent())) {
return SplitNodeResult(NS_ERROR_FAILURE);
}
// If we meet an orphan node before meeting aMostAncestorToSplit, we need
// to stop splitting. This is a bug of the caller.
if (NS_WARN_IF(atStartOfRightNode.GetContainer() != &aMostAncestorToSplit &&
@ -4016,14 +4023,6 @@ EditorBase::SplitNodeDeepWithTransaction(
return SplitNodeResult(NS_ERROR_FAILURE);
}
// Need to insert rules code call here to do things like not split a list
// if you are after the last <li> or before the first, etc. For now we
// just have some smarts about unneccessarily splitting text nodes, which
// should be universal enough to put straight in this EditorBase routine.
if (NS_WARN_IF(!atStartOfRightNode.GetContainerAsContent())) {
return SplitNodeResult(NS_ERROR_FAILURE);
}
nsIContent* currentRightNode = atStartOfRightNode.GetContainerAsContent();
// If the split point is middle of the node or the node is not a text node

View File

@ -0,0 +1,13 @@
<script>
function go() {
document.getElementById("label1").addEventListener("DOMNodeRemoved", () => {
document.getElementById("a1").innerText = "";
});
document.execCommand("indent", false);
}
</script>
<body onload=go()>
<li contenteditable="">
<a id="a1">
<label id="label1"></br>
<!---

View File

@ -96,6 +96,7 @@ load 1405747.html
load 1408170.html
load 1414581.html
load 1415231.html
load 1423767.html
needs-focus load 1424450.html
load 1425091.html
load 1443664.html

4
gfx/skia/skia/src/opts/SkOpts_hsw.cpp Normal file → Executable file
View File

@ -12,7 +12,9 @@
#include <immintrin.h> // ODR safe
#include <stdint.h> // ODR safe
#if defined(__AVX2__)
// As described in https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85525, MinGW will produce
// unaligned instructions for this code, resulting in a crash.
#if defined(__AVX2__) && !defined(__MINGW32__)
namespace hsw {

View File

@ -797,11 +797,16 @@ case "$target" in
;;
esac
if test -n "$GNU_CC"; then
CFLAGS="$CFLAGS -fno-keep-inline-dllexport"
CXXFLAGS="$CXXFLAGS -fno-keep-inline-dllexport"
fi
case "$target" in
i*86-*)
if test -n "$GNU_CC"; then
CFLAGS="$CFLAGS -mstackrealign -fno-keep-inline-dllexport"
CXXFLAGS="$CXXFLAGS -mstackrealign -fno-keep-inline-dllexport"
CFLAGS="$CFLAGS -mstackrealign"
CXXFLAGS="$CXXFLAGS -mstackrealign"
LDFLAGS="$LDFLAGS -Wl,--large-address-aware"
else
DSO_LDOPTS="$DSO_LDOPTS -MACHINE:X86"

View File

@ -145,7 +145,6 @@ class RefreshDriverTimer {
public:
RefreshDriverTimer()
: mLastFireEpoch(0)
, mLastFireSkipped(false)
{
}
@ -233,11 +232,6 @@ public:
virtual TimeDuration GetTimerRate() = 0;
bool LastTickSkippedAnyPaints() const
{
return mLastFireSkipped;
}
TimeStamp GetIdleDeadlineHint(TimeStamp aDefault)
{
MOZ_ASSERT(NS_IsMainThread());
@ -305,8 +299,6 @@ protected:
}
TickDriver(driver, aJsNow, aNow);
mLastFireSkipped = mLastFireSkipped || driver->mSkippedPaints;
}
}
@ -319,7 +311,6 @@ protected:
mLastFireEpoch = jsnow;
mLastFireTime = now;
mLastFireSkipped = false;
LOG("[%p] ticking drivers...", this);
// RD is short for RefreshDriver
@ -338,7 +329,6 @@ protected:
}
int64_t mLastFireEpoch;
bool mLastFireSkipped;
TimeStamp mLastFireTime;
TimeStamp mTargetTime;
@ -946,7 +936,6 @@ protected:
mLastFireEpoch = jsnow;
mLastFireTime = now;
mLastFireSkipped = false;
nsTArray<RefPtr<nsRefreshDriver>> drivers(mContentRefreshDrivers);
drivers.AppendElements(mRootRefreshDrivers);
@ -956,7 +945,6 @@ protected:
!drivers[index]->IsTestControllingRefreshesEnabled())
{
TickDriver(drivers[index], jsnow, now);
mLastFireSkipped = mLastFireSkipped || drivers[index]->SkippedPaints();
}
mNextDriverIndex++;
@ -1072,21 +1060,12 @@ nsRefreshDriver::DefaultInterval()
// Backends which block on swap/present/etc should try to not block
// when layout.frame_rate=0 - to comply with "ASAP" as much as possible.
double
nsRefreshDriver::GetRegularTimerInterval(bool *outIsDefault) const
nsRefreshDriver::GetRegularTimerInterval() const
{
int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
if (rate < 0) {
rate = gfxPlatform::GetDefaultFrameRate();
if (outIsDefault) {
*outIsDefault = true;
}
} else {
if (outIsDefault) {
*outIsDefault = false;
}
}
if (rate == 0) {
} else if (rate == 0) {
rate = 10000;
}
@ -1114,12 +1093,6 @@ nsRefreshDriver::GetMinRecomputeVisibilityInterval()
return TimeDuration::FromMilliseconds(interval);
}
double
nsRefreshDriver::GetRefreshTimerInterval() const
{
return mThrottled ? GetThrottledTimerInterval() : GetRegularTimerInterval();
}
RefreshDriverTimer*
nsRefreshDriver::ChooseTimer() const
{
@ -1131,8 +1104,7 @@ nsRefreshDriver::ChooseTimer() const
}
if (!sRegularRateTimer) {
bool isDefault = true;
double rate = GetRegularTimerInterval(&isDefault);
double rate = GetRegularTimerInterval();
// Try to use vsync-base refresh timer first for sRegularRateTimer.
CreateVsyncRefreshTimer();

View File

@ -393,11 +393,6 @@ public:
uint32_t aDelay);
static void CancelIdleRunnable(nsIRunnable* aRunnable);
bool SkippedPaints() const
{
return mSkippedPaints;
}
void NotifyDOMContentLoaded();
private:
@ -436,8 +431,7 @@ private:
// Trigger a refresh immediately, if haven't been disconnected or frozen.
void DoRefresh();
double GetRefreshTimerInterval() const;
double GetRegularTimerInterval(bool *outIsDefault = nullptr) const;
double GetRegularTimerInterval() const;
static double GetThrottledTimerInterval();
static mozilla::TimeDuration GetMinRecomputeVisibilityInterval();

View File

@ -904,7 +904,7 @@ nsIFrame::AddDisplayItem(nsDisplayItem* aItem)
items = new DisplayItemArray();
AddProperty(DisplayItems(), items);
}
MOZ_ASSERT(!items->Contains(aItem));
MOZ_DIAGNOSTIC_ASSERT(!items->Contains(aItem));
items->AppendElement(aItem);
}
@ -2710,7 +2710,8 @@ ComputeClipForMaskItem(nsDisplayListBuilder* aBuilder, nsIFrame* aMaskedFrame,
nsSVGUtils::eBBoxIncludeClipped |
nsSVGUtils::eBBoxIncludeFill |
nsSVGUtils::eBBoxIncludeMarkers |
nsSVGUtils::eBBoxIncludeStroke);
nsSVGUtils::eBBoxIncludeStroke |
nsSVGUtils::eDoNotClipToBBoxOfContentInsideClipPath);
combinedClip = Some(cssToDevMatrix.TransformBounds(result));
} else {
// The code for this case is adapted from ComputeMaskGeometry().

View File

@ -720,6 +720,8 @@ void
nsSimplePageSequenceFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists)
{
aBuilder->SetInPageSequence(true);
aBuilder->SetDisablePartialUpdates(true);
DisplayBorderBackgroundOutline(aBuilder, aLists);
nsDisplayList content;
@ -753,6 +755,7 @@ nsSimplePageSequenceFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
::ComputePageSequenceTransform));
aLists.Content()->AppendToTop(&content);
aBuilder->SetInPageSequence(false);
}
//------------------------------------------------------------------------------

View File

@ -5127,14 +5127,12 @@ FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData,
layerBuilder->WillEndTransaction();
tempManager->AbortTransaction();
#ifdef MOZ_DUMP_PAINTING
if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) {
fprintf_stderr(gfxUtils::sDumpPaintFile, "Basic layer tree for painting contents of display item %s(%p):\n", aItem.mItem->Name(), aItem.mItem->Frame());
std::stringstream stream;
tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile());
fprint_stderr(gfxUtils::sDumpPaintFile, stream); // not a typo, fprint_stderr declared in LayersLogging.h
}
#endif
nsIntPoint offset = GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer);
props->MoveBy(-offset);

View File

@ -117,6 +117,11 @@ RetainedDisplayListBuilder::PreProcessDisplayList(RetainedDisplayList* aList,
aList->mOldItems.SetCapacity(aList->Count());
MOZ_ASSERT(aList->mOldItems.IsEmpty());
while (nsDisplayItem* item = aList->RemoveBottom()) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
item->mMergedItem = false;
item->mPreProcessedItem = true;
#endif
if (item->HasDeletedFrame() || !item->CanBeReused()) {
size_t i = aList->mOldItems.Length();
aList->mOldItems.AppendElement(OldItemInfo(nullptr));
@ -275,8 +280,10 @@ public:
if (!HasModifiedFrame(aNewItem) &&
HasMatchingItemInOldList(aNewItem, &oldIndex)) {
nsDisplayItem* oldItem = mOldItems[oldIndex.val].mItem;
MOZ_DIAGNOSTIC_ASSERT(oldItem->GetPerFrameKey() == aNewItem->GetPerFrameKey() &&
oldItem->Frame() == aNewItem->Frame());
if (!mOldItems[oldIndex.val].IsChanged()) {
MOZ_ASSERT(!mOldItems[oldIndex.val].IsUsed());
MOZ_DIAGNOSTIC_ASSERT(!mOldItems[oldIndex.val].IsUsed());
if (aNewItem->GetChildren()) {
Maybe<const ActiveScrolledRoot*> containerASRForChildren;
if (mBuilder->MergeDisplayLists(aNewItem->GetChildren(),
@ -355,6 +362,20 @@ public:
Span<const MergedListIndex> aDirectPredecessors,
const Maybe<MergedListIndex>& aExtraDirectPredecessor) {
UpdateContainerASR(aItem);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
nsIFrame::DisplayItemArray* items = aItem->Frame()->GetProperty(nsIFrame::DisplayItems());
for (nsDisplayItem* i : *items) {
if (i->Frame() == aItem->Frame() &&
i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
MOZ_DIAGNOSTIC_ASSERT(!i->mMergedItem);
}
}
aItem->mMergedItem = true;
aItem->mPreProcessedItem = false;
#endif
mMergedItems.AppendToTop(aItem);
MergedListIndex newIndex = mMergedDAG.AddNode(aDirectPredecessors, aExtraDirectPredecessor);
return newIndex;

View File

@ -124,6 +124,26 @@ SpammyLayoutWarningsEnabled()
}
#endif
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
void AssertUniqueItem(nsDisplayItem* aItem) {
nsIFrame::DisplayItemArray* items = aItem->Frame()->GetProperty(nsIFrame::DisplayItems());
if (!items) {
return;
}
for (nsDisplayItem* i : *items) {
if (i != aItem &&
!i->HasDeletedFrame() &&
i->Frame() == aItem->Frame() &&
i->GetPerFrameKey() == aItem->GetPerFrameKey()) {
if (i->mPreProcessedItem) {
continue;
}
MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!");
}
}
}
#endif
/* static */ bool
ActiveScrolledRoot::IsAncestor(const ActiveScrolledRoot* aAncestor,
const ActiveScrolledRoot* aDescendant)
@ -996,6 +1016,7 @@ nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
mAllowMergingAndFlattening(true),
mWillComputePluginGeometry(false),
mInTransform(false),
mInPageSequence(false),
mIsInChromePresContext(false),
mSyncDecodeImages(false),
mIsPaintingToWindow(false),
@ -3088,7 +3109,8 @@ nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
{}
nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
const ActiveScrolledRoot* aActiveScrolledRoot)
const ActiveScrolledRoot* aActiveScrolledRoot,
bool aAnonymous)
: mFrame(aFrame)
, mActiveScrolledRoot(aActiveScrolledRoot)
, mAnimatedGeometryRoot(nullptr)
@ -3102,7 +3124,7 @@ nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
#endif
{
MOZ_COUNT_CTOR(nsDisplayItem);
if (aBuilder->IsRetainingDisplayList()) {
if (aBuilder->IsRetainingDisplayList() && !aAnonymous) {
mFrame->AddDisplayItem(this);
}
mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame);
@ -6093,17 +6115,18 @@ nsDisplayBoxShadowInner::ComputeVisibility(nsDisplayListBuilder* aBuilder,
}
nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayList* aList)
nsIFrame* aFrame, nsDisplayList* aList, bool aAnonymous)
: nsDisplayWrapList(aBuilder, aFrame, aList,
aBuilder->CurrentActiveScrolledRoot())
aBuilder->CurrentActiveScrolledRoot(), false, 0, aAnonymous)
{}
nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayList* aList,
const ActiveScrolledRoot* aActiveScrolledRoot,
bool aClearClipChain,
uint32_t aIndex)
: nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot)
uint32_t aIndex,
bool aAnonymous)
: nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot, aAnonymous)
, mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot())
, mOverrideZIndex(0)
, mIndex(aIndex)
@ -6146,8 +6169,8 @@ nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
}
nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame, nsDisplayItem* aItem)
: nsDisplayItem(aBuilder, aFrame)
nsIFrame* aFrame, nsDisplayItem* aItem, bool aAnonymous)
: nsDisplayItem(aBuilder, aFrame, aBuilder->CurrentActiveScrolledRoot(), aAnonymous)
, mOverrideZIndex(0)
, mIndex(0)
, mHasZIndexOverride(false)
@ -9139,7 +9162,7 @@ nsDisplayPerspective::nsDisplayPerspective(nsDisplayListBuilder* aBuilder,
nsIFrame* aPerspectiveFrame,
nsDisplayList* aList)
: nsDisplayItem(aBuilder, aPerspectiveFrame)
, mList(aBuilder, aPerspectiveFrame, aList)
, mList(aBuilder, aPerspectiveFrame, aList, true)
, mTransformFrame(aTransformFrame)
, mIndex(aBuilder->AllocatePerspectiveItemIndex())
{

View File

@ -836,6 +836,9 @@ public:
*/
void SetInTransform(bool aInTransform) { mInTransform = aInTransform; }
bool IsInPageSequence() const { return mInPageSequence; }
void SetInPageSequence(bool aInPage) { mInPageSequence = aInPage; }
/**
* Return true if we're currently building a display list for a
* nested presshell.
@ -1980,6 +1983,7 @@ private:
// True when we're building a display list that's directly or indirectly
// under an nsDisplayTransform
bool mInTransform;
bool mInPageSequence;
bool mIsInChromePresContext;
bool mSyncDecodeImages;
bool mIsPaintingToWindow;
@ -2025,6 +2029,10 @@ protected:
class nsDisplayWrapList;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
void AssertUniqueItem(nsDisplayItem* aItem);
#endif
template<typename T, typename... Args>
MOZ_ALWAYS_INLINE T*
MakeDisplayItem(nsDisplayListBuilder* aBuilder, Args&&... aArgs)
@ -2043,6 +2051,14 @@ MakeDisplayItem(nsDisplayListBuilder* aBuilder, Args&&... aArgs)
}
}
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
if (aBuilder->IsRetainingDisplayList() &&
!aBuilder->IsInPageSequence() &&
aBuilder->IsBuilding()) {
AssertUniqueItem(item);
}
#endif
return item;
}
@ -2084,7 +2100,8 @@ public:
// need to count constructors and destructors.
nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
const ActiveScrolledRoot* aActiveScrolledRoot);
const ActiveScrolledRoot* aActiveScrolledRoot,
bool aAnonymous = false);
/**
* This constructor is only used in rare cases when we need to construct
@ -2839,12 +2856,18 @@ public:
// used by RetainedDisplayListBuilder.
void SetOldListIndex(nsDisplayList* aList, OldListIndex aIndex)
{
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
mOldList = reinterpret_cast<uintptr_t>(aList);
#endif
mOldListIndex = aIndex;
}
OldListIndex GetOldListIndex(nsDisplayList* aList)
{
MOZ_ASSERT(mOldList == reinterpret_cast<uintptr_t>(aList));
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
if (mOldList != reinterpret_cast<uintptr_t>(aList)) {
MOZ_CRASH_UNSAFE_PRINTF("Item found was in the wrong list! type %d", GetPerFrameKey());
}
#endif
return mOldListIndex;
}
@ -2883,7 +2906,13 @@ private:
protected:
mozilla::DebugOnly<uintptr_t> mOldList;
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
public:
uintptr_t mOldList = 0;
bool mMergedItem = false;
bool mPreProcessedItem = false;
protected:
#endif
OldListIndex mOldListIndex;
bool mForceNotVisible;
@ -5009,14 +5038,15 @@ public:
* Takes all the items from aList and puts them in our list.
*/
nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList);
nsDisplayList* aList, bool aAnonymous = false);
nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList,
const ActiveScrolledRoot* aActiveScrolledRoot,
bool aClearClipChain = false,
uint32_t aIndex = 0);
uint32_t aIndex = 0,
bool aAnonymous = false);
nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayItem* aItem);
nsDisplayItem* aItem, bool aAnonymous = false);
nsDisplayWrapList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
: nsDisplayItem(aBuilder, aFrame)
, mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot())
@ -6292,10 +6322,10 @@ class nsDisplayTransform: public nsDisplayItem
public:
StoreList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayList* aList) :
nsDisplayWrapList(aBuilder, aFrame, aList) {}
nsDisplayWrapList(aBuilder, aFrame, aList, true) {}
StoreList(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
nsDisplayItem* aItem) :
nsDisplayWrapList(aBuilder, aFrame, aItem) {}
nsDisplayWrapList(aBuilder, aFrame, aItem, true) {}
virtual ~StoreList() {}
virtual void UpdateBounds(nsDisplayListBuilder* aBuilder) override {
@ -6663,6 +6693,12 @@ public:
mFrame->Combines3DTransformWithAncestors();
}
virtual void RemoveFrame(nsIFrame* aFrame) override
{
nsDisplayItem::RemoveFrame(aFrame);
mStoredList.RemoveFrame(aFrame);
}
private:
void ComputeBounds(nsDisplayListBuilder* aBuilder);
void SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder);
@ -6837,6 +6873,7 @@ public:
mTransformFrame = nullptr;
}
nsDisplayItem::RemoveFrame(aFrame);
mList.RemoveFrame(aFrame);
}
private:

View File

@ -0,0 +1,12 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="786" viewBox="0,0,512,512">
<defs>
<filter id="filter-wVmTgUOU" filterUnits="objectBoundingBox" x="-30%" y="-30%" width="160%" height="170%" color-interpolation-filters="sRGB">
<feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" in="SourceGraphic"/>
<feOffset dy="28"/>
<feGaussianBlur stdDeviation="18.67" result="blur1"/>
</filter>
</defs>
<g filter="url(#filter-wVmTgUOU)">
<rect x="58.88" y="58.88" width="394.24" height="394.24" color="#07773e" fill="red"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 627 B

View File

@ -0,0 +1,17 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="786" viewBox="0,0,512,512">
<defs>
<clipPath id="clip-vXP8Ybe5">
<path d="M0,512v-512h512v512z"/>
</clipPath>
<filter id="filter-wVmTgUOU" filterUnits="objectBoundingBox" x="-30%" y="-30%" width="160%" height="170%" color-interpolation-filters="sRGB">
<feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" in="SourceGraphic"/>
<feOffset dy="28"/>
<feGaussianBlur stdDeviation="18.67" result="blur1"/>
</filter>
</defs>
<g clip-path="url(#clip-vXP8Ybe5)">
<g filter="url(#filter-wVmTgUOU)">
<rect x="58.88" y="58.88" width="394.24" height="394.24" color="#07773e" fill="red"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 762 B

View File

@ -55,6 +55,8 @@ pref(layout.css.mix-blend-mode.enabled,true) == blend-normal.svg blend-normal-re
#skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) == blend-soft-light.svg blend-soft-light-ref.svg
skip-if(Android) pref(layout.css.mix-blend-mode.enabled,true) fails-if(webrender) == blend-difference-stacking.html blend-difference-stacking-ref.html
fuzzy(11,7155) == blur-inside-clipPath.svg blur-inside-clipPath-ref.svg
== border-radius-01.html pass.svg
== clip-01.svg pass.svg

View File

@ -97,20 +97,48 @@ ImageLoader::AssociateRequestToFrame(imgIRequest* aRequest,
// If we weren't already blocking onload, do that now.
if ((fwfToModify->mFlags & REQUEST_HAS_BLOCKED_ONLOAD) == 0) {
fwfToModify->mFlags |= REQUEST_HAS_BLOCKED_ONLOAD;
// Block document onload until we either remove the frame in
// RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
mDocument->BlockOnload();
// We need to stay blocked until we get a reflow. If the first frame
// is not yet decoded, we'll trigger that reflow from onFrameComplete.
// But if the first frame is already decoded, we need to trigger that
// reflow now, because we'll never get a call to onFrameComplete.
// Get request status to see if we should block onload, and if we can
// request reflow immediately.
uint32_t status = 0;
if(NS_SUCCEEDED(aRequest->GetImageStatus(&status)) &&
status & imgIRequest::STATUS_FRAME_COMPLETE) {
RequestReflowOnFrame(fwfToModify, aRequest);
if (NS_SUCCEEDED(aRequest->GetImageStatus(&status)) &&
!(status & imgIRequest::STATUS_ERROR)) {
// No error, so we can block onload.
fwfToModify->mFlags |= REQUEST_HAS_BLOCKED_ONLOAD;
// Block document onload until we either remove the frame in
// RemoveRequestToFrameMapping or onLoadComplete, or complete a reflow.
mDocument->BlockOnload();
// We need to stay blocked until we get a reflow. If the first frame
// is not yet decoded, we'll trigger that reflow from onFrameComplete.
// But if the first frame is already decoded, we need to trigger that
// reflow now, because we'll never get a call to onFrameComplete.
if(status & imgIRequest::STATUS_FRAME_COMPLETE) {
RequestReflowOnFrame(fwfToModify, aRequest);
} else {
// If we don't already have a complete frame, kickoff decode. This
// will ensure that either onFrameComplete or onLoadComplete will
// unblock document onload.
// We want to request decode in such a way that avoids triggering
// sync decode. First, we attempt to convert the aRequest into
// a imgIContainer. If that succeeds, then aRequest has an image
// and we can request decoding for size at zero size, and that will
// trigger async decode. If the conversion to imgIContainer is
// unsuccessful, then that means aRequest doesn't have an image yet,
// which means we can safely call StartDecoding() on it without
// triggering any synchronous work.
nsCOMPtr<imgIContainer> imgContainer;
aRequest->GetImage(getter_AddRefs(imgContainer));
if (imgContainer) {
imgContainer->RequestDecodeForSize(gfx::IntSize(0, 0),
imgIContainer::DECODE_FLAGS_DEFAULT);
} else {
// It's safe to call StartDecoding directly, since it can't
// trigger synchronous decode without an image. Flags are ignored.
aRequest->StartDecoding(imgIContainer::FLAG_NONE);
}
}
}
}
}
@ -491,10 +519,6 @@ ImageLoader::RequestReflowIfNeeded(FrameSet* aFrameSet, imgIRequest* aRequest)
void
ImageLoader::RequestReflowOnFrame(FrameWithFlags* aFwf, imgIRequest* aRequest)
{
// Set the flag indicating that we've requested reflow. This flag will never
// be unset.
aFwf->mFlags |= REQUEST_HAS_REQUESTED_REFLOW;
nsIFrame* frame = aFwf->mFrame;
// Actually request the reflow.
@ -667,19 +691,19 @@ ImageLoader::OnLoadComplete(imgIRequest* aRequest)
return NS_OK;
}
// This may be called for a request that never sent a complete frame.
// This is what happens in a CORS mode violation, and may happen during
// other network events. We check for any frames that have blocked
// onload but haven't requested reflow. In such a case, we unblock
// onload here, since onFrameComplete will not be called for this request.
FrameFlags flagsToCheck(REQUEST_HAS_BLOCKED_ONLOAD |
REQUEST_HAS_REQUESTED_REFLOW);
for (FrameWithFlags& fwf : *frameSet) {
if ((fwf.mFlags & flagsToCheck) == REQUEST_HAS_BLOCKED_ONLOAD) {
// We've blocked onload but haven't requested reflow. Unblock onload
// and clear the flag.
mDocument->UnblockOnload(false);
fwf.mFlags &= ~REQUEST_HAS_BLOCKED_ONLOAD;
// Check if aRequest has an error state. If it does, we need to unblock
// Document onload for all the frames associated with this request that
// have blocked onload. This is what happens in a CORS mode violation, and
// may happen during other network events.
uint32_t status = 0;
if(NS_SUCCEEDED(aRequest->GetImageStatus(&status)) &&
status & imgIRequest::STATUS_ERROR) {
for (FrameWithFlags& fwf : *frameSet) {
if (fwf.mFlags & REQUEST_HAS_BLOCKED_ONLOAD) {
// We've blocked onload. Unblock onload and clear the flag.
mDocument->UnblockOnload(false);
fwf.mFlags &= ~REQUEST_HAS_BLOCKED_ONLOAD;
}
}
}

View File

@ -40,8 +40,7 @@ public:
typedef uint32_t FrameFlags;
enum {
REQUEST_REQUIRES_REFLOW = 1u << 0,
REQUEST_HAS_REQUESTED_REFLOW = 1u << 1,
REQUEST_HAS_BLOCKED_ONLOAD = 1u << 2
REQUEST_HAS_BLOCKED_ONLOAD = 1u << 1,
};
typedef mozilla::css::ImageValue Image;

View File

@ -475,7 +475,8 @@ nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame)
SVGBBox
nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox,
const gfxMatrix &aMatrix)
const gfxMatrix &aMatrix,
uint32_t aFlags)
{
nsIContent* node = GetContent()->GetFirstChild();
SVGBBox unionBBox, tmpBBox;
@ -494,10 +495,12 @@ nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox,
nsSVGClipPathFrame *clipPathFrame =
effectProperties.GetClipPathFrame();
if (clipPathFrame) {
tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix);
tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags);
}
}
tmpBBox.Intersect(aBBox);
if (!(aFlags & nsSVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) {
tmpBBox.Intersect(aBBox);
}
unionBBox.UnionEdges(tmpBBox);
}
}
@ -511,7 +514,7 @@ nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox,
} else {
nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame();
if (clipPathFrame) {
tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix);
tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix, aFlags);
unionBBox.Intersect(tmpBBox);
}
}

View File

@ -132,7 +132,8 @@ public:
#endif
SVGBBox GetBBoxForClipPathFrame(const SVGBBox& aBBox,
const gfxMatrix& aMatrix);
const gfxMatrix& aMatrix,
uint32_t aFlags);
/**
* If the clipPath element transforms its children due to

View File

@ -1181,7 +1181,7 @@ nsSVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
}
matrix = clipContent->PrependLocalTransformsTo(matrix, eUserSpaceToParent);
bbox =
clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix).ToThebesRect();
clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags).ToThebesRect();
}
if (hasClip) {

View File

@ -412,6 +412,9 @@ public:
// this flag is set; Otherwise, getBBox returns the union bounds in
// the coordinate system formed by the <use> element.
eUseUserSpaceOfUseElement = 1 << 9,
// For a frame with a clip-path, if this flag is set then the result
// will not be clipped to the bbox of the content inside the clip-path.
eDoNotClipToBBoxOfContentInsideClipPath = 1 << 10,
};
/**
* This function in primarily for implementing the SVG DOM function getBBox()

View File

@ -42,6 +42,7 @@ static char *RCSSTRING __UNUSED__ ="$Id: r_log.c,v 1.10 2008/11/25 22:25:18 adam
#ifdef LINUX
#define _BSD_SOURCE
#define _DEFAULT_SOURCE
#endif
#include "r_log.h"

View File

@ -237,7 +237,7 @@ ThreadLocal<T, Storage>::set(const T aValue)
}
}
#if defined(XP_WIN) || defined(MACOSX_HAS_THREAD_LOCAL)
#if (defined(XP_WIN) || defined(MACOSX_HAS_THREAD_LOCAL)) && !defined(__MINGW32__)
#define MOZ_THREAD_LOCAL(TYPE) thread_local mozilla::detail::ThreadLocal<TYPE, mozilla::detail::ThreadLocalNativeStorage>
#elif defined(HAVE_THREAD_TLS_KEYWORD)
#define MOZ_THREAD_LOCAL(TYPE) __thread mozilla::detail::ThreadLocal<TYPE, mozilla::detail::ThreadLocalNativeStorage>

View File

@ -92,6 +92,7 @@ public class AppConstants {
public static final String MOZ_APP_NAME = "@MOZ_APP_NAME@";
public static final String MOZ_APP_VENDOR = "@MOZ_APP_VENDOR@";
public static final String MOZ_APP_VERSION = "@MOZ_APP_VERSION@";
public static final String MOZ_APP_VERSION_DISPLAY = "@MOZ_APP_VERSION_DISPLAY@";
public static final String MOZ_APP_DISPLAYNAME = "@MOZ_APP_DISPLAYNAME@";
public static final String MOZ_APP_UA_NAME = "@MOZ_APP_UA_NAME@";

View File

@ -82,6 +82,7 @@ def _defines():
'MOZ_APP_UA_NAME',
'MOZ_APP_VENDOR',
'MOZ_APP_VERSION',
'MOZ_APP_VERSION_DISPLAY',
'MOZ_CHILD_PROCESS_NAME',
'MOZ_MOZILLA_API_KEY',
'MOZ_UPDATE_CHANNEL',

View File

@ -7,7 +7,6 @@ package org.mozilla.gecko.preferences;
import java.util.Locale;
import org.mozilla.gecko.AppConstants.Versions;
import org.mozilla.gecko.BrowserLocaleManager;
import org.mozilla.gecko.GeckoApplication;
import org.mozilla.gecko.GeckoSharedPrefs;
@ -21,7 +20,6 @@ import org.mozilla.gecko.fxa.AccountLoader;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import android.accounts.Account;
import android.app.ActionBar;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context;
@ -29,15 +27,12 @@ import android.content.Loader;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import com.squareup.leakcanary.RefWatcher;
/* A simple implementation of PreferenceFragment for large screen devices
* This will strip category headers (so that they aren't shown to the user twice)
* as well as initializing Gecko prefs when a fragment is shown.
@ -97,29 +92,30 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
/**
* Return the title to use for this preference fragment.
*
* We only return titles for the preference screens that are
* launched directly, and thus might need to be redisplayed.
*
* This method sets the title that you see on non-multi-pane devices.
* The result will be used to set the title that you see on non-multi-pane devices.
*/
private String getTitle() {
final int res = getResource();
if (res == R.xml.preferences) {
return getString(R.string.settings_title);
}
// We can launch this category from the Data Reporting notification.
if (res == R.xml.preferences_privacy) {
return getString(R.string.pref_category_privacy_short);
}
// We can launch this category from the the magnifying glass in the quick search bar.
if (res == R.xml.preferences_search) {
} else if (res == R.xml.preferences_general || res == R.xml.preferences_general_tablet) {
return getString(R.string.pref_category_general);
} else if (res == R.xml.preferences_home) {
return getString(R.string.pref_category_home);
} else if (res == R.xml.preferences_locale) {
return getString(R.string.pref_category_language);
} else if (res == R.xml.preferences_search) {
return getString(R.string.pref_category_search);
}
if (res == R.xml.preferences_notifications) {
} else if (res == R.xml.preferences_privacy) {
return getString(R.string.pref_category_privacy_short);
} else if (res == R.xml.preferences_accessibility) {
return getString(R.string.pref_category_accessibility);
} else if (res == R.xml.preferences_notifications) {
return getString(R.string.pref_category_notifications);
} else if (res == R.xml.preferences_advanced) {
return getString(R.string.pref_category_advanced);
} else if (res == R.xml.preferences_vendor) {
return getString(R.string.pref_category_vendor);
}
return null;
@ -156,7 +152,7 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
return -1;
}
private void updateTitle() {
private void updateParentTitle() {
final String newTitle = getTitle();
if (newTitle == null) {
Log.d(LOGTAG, "No new title to show.");
@ -187,6 +183,7 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
public void onResume() {
// This is a little delicate. Ensure that you do nothing prior to
// super.onResume that you wouldn't do in onCreate.
// This will also set the title in the parent activity's ActionBar to the title of the current PreferenceScreen
applyLocale(Locale.getDefault());
super.onResume();
@ -210,7 +207,7 @@ public class GeckoPreferenceFragment extends PreferenceFragment {
}
// Fix the parent title regardless.
updateTitle();
updateParentTitle();
}
/*

View File

@ -17,6 +17,7 @@ import android.text.TextUtils;
import android.util.Log;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
@ -62,6 +63,7 @@ public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
private static final String DEFAULT_SEARCH_ENGINE = "defaultSearch";
private static final String DEVICE = "device";
private static final String DISTRIBUTION_ID = "distributionId";
private static final String DISPLAY_VERSION = "displayVersion";
private static final String EXPERIMENTS = "experiments";
private static final String LOCALE = "locale";
private static final String OS_ATTR = "os";
@ -101,6 +103,7 @@ public class TelemetryCorePingBuilder extends TelemetryPingBuilder {
payload.put(OS_VERSION, Integer.toString(Build.VERSION.SDK_INT)); // A String for cross-platform reasons.
payload.put(PING_CREATION_DATE, pingCreationDateFormat.format(nowCalendar.getTime()));
payload.put(TIMEZONE_OFFSET, DateUtil.getTimezoneOffsetInMinutesForGivenDate(nowCalendar));
payload.put(DISPLAY_VERSION, AppConstants.MOZ_APP_VERSION_DISPLAY);
payload.putArray(EXPERIMENTS, Experiments.getActiveExperiments(context));
synchronized (this) {
SharedPreferences prefs = GeckoSharedPrefs.forApp(context);

View File

@ -112,4 +112,7 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
@Suppress("UNCHECKED_CAST")
fun <T> Any?.asJSList(): List<T> = this as List<T>
fun Any?.asJSPromise(): GeckoSessionTestRule.PromiseWrapper =
this as GeckoSessionTestRule.PromiseWrapper
}

View File

@ -9,6 +9,7 @@ import org.mozilla.geckoview.GeckoSessionSettings
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.RejectedPromiseException
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.TimeoutException
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.TimeoutMillis
@ -1250,6 +1251,52 @@ class GeckoSessionTestRuleTest : BaseSessionTest(noErrorCollector = true) {
(array dot 0) as String, equalTo("foo"))
assertThat("JS array toString should be expanded after evaluation",
array.toString(), equalTo("[Array(2)][foo, bar]"))
assertThat("JS user function toString should be correct",
sessionRule.session.evaluateJS("(function foo(){})").toString(),
equalTo("[Function(foo)]"))
assertThat("JS window function toString should be correct",
sessionRule.session.evaluateJS("window.alert").toString(),
equalTo("[Function(alert)]"))
assertThat("JS pending promise toString should be correct",
sessionRule.session.evaluateJS("new Promise(_=>{})").toString(),
equalTo("[Promise(pending)]"))
assertThat("JS fulfilled promise toString should be correct",
sessionRule.session.evaluateJS("Promise.resolve('foo')").toString(),
equalTo("[Promise(fulfilled)](foo)"))
assertThat("JS rejected promise toString should be correct",
sessionRule.session.evaluateJS("Promise.reject('bar')").toString(),
equalTo("[Promise(rejected)](bar)"))
}
@WithDevToolsAPI
@Test fun evaluateJS_supportPromises() {
assertThat("Can get resolved promise",
sessionRule.session.evaluateJS(
"Promise.resolve('foo')").asJSPromise().value as String,
equalTo("foo"))
val promise = sessionRule.session.evaluateJS(
"let resolve; new Promise(r => resolve = r)").asJSPromise()
assertThat("Promise is initially pending",
promise.isPending, equalTo(true))
sessionRule.session.evaluateJS("resolve('bar')")
assertThat("Can wait for promise to resolve",
promise.value as String, equalTo("bar"))
assertThat("Resolved promise is no longer pending",
promise.isPending, equalTo(false))
}
@WithDevToolsAPI
@Test(expected = RejectedPromiseException::class)
fun evaluateJS_throwOnRejectedPromise() {
sessionRule.session.evaluateJS("Promise.reject('foo')").asJSPromise().value
}
@WithDevToolsAPI

View File

@ -137,6 +137,9 @@ public class Actor {
}
protected void onPacket(final @NonNull JSONObject packet) {
if (mPendingReplies == null) {
return;
}
for (final Iterator<Reply<?>> it = mPendingReplies.iterator(); it.hasNext();) {
final Reply<?> reply = it.next();
if (reply.parser.canParse(packet)) {

View File

@ -23,7 +23,7 @@ import java.util.Set;
* Provide methods for interacting with grips, including unpacking grips into Java
* objects.
*/
/* package */ final class Grip extends Actor {
public class Grip extends Actor {
private static final class Cache extends HashMap<String, Object> {
}
@ -40,8 +40,6 @@ import java.util.Set;
mCache = cache;
mType = type;
mGrip = grip;
cache.put(mGrip.name, this);
}
private Map<String, Object> ensureRealObject() {
@ -106,8 +104,6 @@ import java.util.Set;
mType = type;
mLength = length;
mGrip = grip;
cache.put(mGrip.name, this);
}
private List<Object> ensureRealObject() {
@ -150,9 +146,32 @@ import java.util.Set;
}
private static final class Function {
private final String mName;
public Function(final @Nullable String name) {
mName = name;
}
@Override
public String toString() {
return "[Function]";
final String name = (mName != null) ? ("(" + mName + ')') : "";
return "[Function" + name + ']';
}
}
private static final class LongString {
private final int mLength;
private final String mInitial;
public LongString(final int length, final @Nullable String initial) {
mLength = length;
mInitial = (initial != null && !initial.isEmpty()) ? initial.substring(0, 50) : null;
}
@Override
public String toString() {
return String.format("[String(%d)]%s", mLength,
(mInitial != null) ? "(" + mInitial + "\u2026)" : "");
}
}
@ -163,8 +182,8 @@ import java.util.Set;
* @param connection Connection associated with this grip.
* @param value Grip value received from the server.
*/
public static Object unpack(final RDPConnection connection,
final Object value) {
/* package */ static Object unpack(final @NonNull RDPConnection connection,
final @Nullable Object value) {
return unpackGrip(new Cache(), connection, value);
}
@ -178,7 +197,8 @@ import java.util.Set;
}
final JSONObject obj = (JSONObject) value;
switch (obj.optString("type")) {
final String type = obj.optString("type");
switch (type) {
case "null":
case "undefined":
return null;
@ -190,10 +210,12 @@ import java.util.Set;
return Double.NaN;
case "-0":
return -0.0;
case "longString":
return new LongString(obj.optInt("length"), obj.optString("initial"));
case "object":
break;
default:
throw new IllegalArgumentException();
throw new IllegalArgumentException(String.valueOf(type));
}
final String actor = obj.optString("actor", null);
@ -204,7 +226,16 @@ import java.util.Set;
final String cls = obj.optString("class", null);
if ("Function".equals(cls)) {
return new Function();
final String name = obj.optString("name", null);
final String displayName = obj.optString("displayName", name);
final String userDisplayName = obj.optString("userDisplayName", displayName);
final Function output = new Function(userDisplayName);
cache.put(actor, output);
return output;
} else if ("Promise".equals(cls)) {
final Promise output = new Promise(connection, obj);
cache.put(actor, output);
return output;
}
final JSONObject preview = obj.optJSONObject("preview");
@ -225,6 +256,7 @@ import java.util.Set;
} else {
output = new LazyObject(cache, cls, grip);
}
cache.put(actor, output);
return output;
}
@ -240,7 +272,7 @@ import java.util.Set;
}
};
private Grip(final RDPConnection connection, final JSONObject grip) {
/* package */ Grip(final @NonNull RDPConnection connection, final @NonNull JSONObject grip) {
super(connection, grip);
}

View File

@ -0,0 +1,111 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.geckoview.test.rdp;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.json.JSONObject;
/**
* Object to represent a Promise object returned by JS.
*/
public final class Promise extends Grip {
private JSONObject mGrip;
private String mState;
private Object mValue;
private Object mReason;
/* package */ Promise(final @NonNull RDPConnection connection, final @NonNull JSONObject grip) {
super(connection, grip);
setPromiseState(grip);
}
/* package */ void setPromiseState(final @NonNull JSONObject grip) {
mGrip = grip;
final JSONObject state = grip.optJSONObject("promiseState");
mState = state.optString("state");
if (isFulfilled()) {
mValue = Grip.unpack(connection, state.opt("value"));
} else if (isRejected()) {
mReason = Grip.unpack(connection, state.opt("reason"));
}
}
/**
* Return whether this promise is pending.
*
* @return True if this promise is pending.
*/
public boolean isPending() {
return "pending".equals(mState);
}
/**
* Return whether this promise is fulfilled.
*
* @return True if this promise is fulfilled.
*/
public boolean isFulfilled() {
return "fulfilled".equals(mState);
}
/**
* Return the promise value, assuming it is fulfilled.
*
* @return Promise value.
*/
public @Nullable Object getValue() {
return mValue;
}
/**
* Return whether this promise is rejected.
*
* @return True if this promise is rejected.
*/
public boolean isRejected() {
return "rejected".equals(mState);
}
/**
* Return the promise reason, assuming it is rejected.
*
* @return Promise reason.
*/
public @Nullable Object getReason() {
return mReason;
}
/**
* Get the value of a property in this promise object.
*
* @return Value or null if there is no such property.
*/
public @Nullable Object getProperty(final @NonNull String name) {
final JSONObject preview = mGrip.optJSONObject("preview");
if (preview == null) {
return null;
}
final JSONObject ownProperties = preview.optJSONObject("ownProperties");
if (ownProperties == null) {
return null;
}
final JSONObject prop = ownProperties.optJSONObject(name);
if (prop == null) {
return null;
}
return Grip.unpack(connection, prop.opt("value"));
}
@Override
public String toString() {
return "[Promise(" + mState + ")]" +
(isFulfilled() ? "(" + mValue + ')' : "") +
(isRejected() ? "(" + mReason + ')' : "");
}
}

View File

@ -0,0 +1,100 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.geckoview.test.rdp;
import android.support.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Provide access to the promises API.
*/
public final class Promises extends Actor {
private final ReplyParser<Promise[]> PROMISE_LIST_PARSER = new ReplyParser<Promise[]>() {
@Override
public boolean canParse(@NonNull JSONObject packet) {
return packet.has("promises");
}
@Override
public @NonNull Promise[] parse(@NonNull JSONObject packet) {
return getPromisesFromArray(packet.optJSONArray("promises"),
/* canCreate */ true);
}
};
private final Set<Promise> mPromises = new HashSet<>();
/* package */ Promises(final RDPConnection connection, final String name) {
super(connection, name);
attach();
}
/**
* Attach to the promises API.
*/
private void attach() {
sendPacket("{\"type\":\"attach\"}", JSON_PARSER).get();
}
/**
* Detach from the promises API.
*/
public void detach() {
for (final Promise promise : mPromises) {
promise.release();
}
sendPacket("{\"type\":\"detach\"}", JSON_PARSER).get();
}
/* package */ Promise[] getPromisesFromArray(final @NonNull JSONArray array,
final boolean canCreate) {
final Promise[] promises = new Promise[array.length()];
for (int i = 0; i < promises.length; i++) {
final JSONObject grip = array.optJSONObject(i);
final Promise promise = (Promise) connection.getActor(grip);
if (promise != null) {
promise.setPromiseState(grip);
promises[i] = promise;
} else if (canCreate) {
promises[i] = new Promise(connection, grip);
}
}
return promises;
}
/**
* Return a list of live promises.
*
* @returns List of promises.
*/
public @NonNull Promise[] listPromises() {
final Promise[] promises = sendPacket("{\"type\":\"listPromises\"}",
PROMISE_LIST_PARSER).get();
mPromises.addAll(Arrays.asList(promises));
return promises;
}
@Override
protected void onPacket(final @NonNull JSONObject packet) {
final String type = packet.optString("type", null);
if ("new-promises".equals(type)) {
// We always call listPromises() to get updated Promises,
// so that means we shouldn't handle "new-promises" here.
} else if ("promises-settled".equals(type)) {
// getPromisesFromArray will update states for us.
getPromisesFromArray(packet.optJSONArray("data"),
/* canCreate */ false);
} else {
super.onPacket(packet);
}
}
}

View File

@ -76,7 +76,7 @@ public final class RDPConnection implements Closeable {
if (RDPConnection.this.mSocket.isClosed()) {
quit = true;
} else {
result = new RuntimeException(e);
result = e;
}
}
if (result == null) {
@ -119,8 +119,9 @@ public final class RDPConnection implements Closeable {
mPendingPackets.decrementAndGet();
if (result instanceof RuntimeException) {
throw (RuntimeException) result;
if (result instanceof Throwable) {
final Throwable throwable = (Throwable) result;
throw new RuntimeException(throwable.getMessage(), throwable);
}
final JSONObject json = (JSONObject) result;
@ -148,7 +149,7 @@ public final class RDPConnection implements Closeable {
try {
length = Integer.valueOf(header.substring(header.lastIndexOf(' ') + 1));
} catch (final NumberFormatException e) {
return new RuntimeException("Invalid packet header: " + header);
return new IllegalStateException("Invalid packet header: " + header);
}
if (header.startsWith("bulk ")) {
@ -178,13 +179,12 @@ public final class RDPConnection implements Closeable {
try {
json = new JSONObject(str);
} catch (final JSONException e) {
return new RuntimeException(e);
return e;
}
final String error = json.optString("error", null);
if (error != null) {
return new RuntimeException("Request failed: " + error + ": " +
json.optString("message", null));
return new Exception(error + ": " + json.optString("message", null));
}
return json;
}

View File

@ -37,12 +37,13 @@ public final class Tab extends Actor {
url = tab.optString("url", null);
outerWindowID = tab.optLong("outerWindowID", -1);
mTab = tab;
attach();
}
/**
* Attach to the server tab.
*/
public void attach() {
private void attach() {
sendPacket("{\"type\":\"attach\"}", TAB_STATE_PARSER).get();
}
@ -63,4 +64,15 @@ public final class Tab extends Actor {
final Actor console = connection.getActor(name);
return (console != null) ? (Console) console : new Console(connection, name);
}
/**
* Get the promises object for access to the promises API.
*
* @return Promises object.
*/
public Promises getPromises() {
final String name = mTab.optString("promisesActor", null);
final Actor promises = connection.getActor(name);
return (promises != null) ? (Promises) promises : new Promises(connection, name);
}
}

View File

@ -13,6 +13,7 @@ import org.mozilla.geckoview.GeckoRuntimeSettings;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.test.rdp.Actor;
import org.mozilla.geckoview.test.rdp.Promise;
import org.mozilla.geckoview.test.rdp.RDPConnection;
import org.mozilla.geckoview.test.rdp.Tab;
import org.mozilla.geckoview.test.util.Callbacks;
@ -301,6 +302,69 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
}
}
public static class RejectedPromiseException extends RuntimeException {
private final Object mReason;
/* package */ RejectedPromiseException(final Object reason) {
super(String.valueOf(reason));
mReason = reason;
}
public Object getReason() {
return mReason;
}
}
public static class PromiseWrapper {
private final Promise mPromise;
private final long mTimeoutMillis;
/* package */ PromiseWrapper(final @NonNull Promise promise, final long timeoutMillis) {
mPromise = promise;
mTimeoutMillis = timeoutMillis;
}
@Override
public boolean equals(final Object o) {
return (o instanceof PromiseWrapper) && mPromise.equals(((PromiseWrapper) o).mPromise);
}
@Override
public int hashCode() {
return mPromise.hashCode();
}
@Override
public String toString() {
return mPromise.toString();
}
/**
* Return whether this promise is pending.
*
* @return True if this promise is pending.
*/
public boolean isPending() {
return mPromise.isPending();
}
/**
* Wait for this promise to settle. If the promise is fulfilled, return its value.
* If the promise is rejected, throw an exception containing the reason.
*
* @return Fulfilled value of the promise.
*/
public Object getValue() {
while (mPromise.isPending()) {
loopUntilIdle(mTimeoutMillis);
}
if (mPromise.isRejected()) {
throw new RejectedPromiseException(mPromise.getReason());
}
return mPromise.getValue();
}
}
public static class CallRequirement {
public final boolean allowed;
public final int count;
@ -1039,7 +1103,6 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
sRDPConnection.setTimeout(mTimeoutMillis);
}
final Tab tab = sRDPConnection.getMostRecentTab();
tab.attach();
mRDPTabs.put(session, tab);
}
}
@ -1085,6 +1148,7 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
protected void cleanupSession(final GeckoSession session) {
final Tab tab = (mRDPTabs != null) ? mRDPTabs.get(session) : null;
if (tab != null) {
tab.getPromises().detach();
tab.detach();
mRDPTabs.remove(session);
}
@ -1760,7 +1824,6 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
mRDPChromeProcess = sRDPConnection.getChromeProcess();
assertThat("Should have chrome process object",
mRDPChromeProcess, notNullValue());
mRDPChromeProcess.attach();
}
return evaluateJS(mRDPChromeProcess, js);
}
@ -1770,7 +1833,24 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
while (!reply.hasResult()) {
loopUntilIdle(mTimeoutMillis);
}
return reply.get();
final Object result = reply.get();
if (result instanceof Promise) {
// Map the static Promise into a live Promise. In order to perform the mapping, we set
// a tag on the static Promise, fetch a list of live Promises, and see which live
// Promise has the same tag on it.
final String tag = String.valueOf(result.hashCode());
tab.getConsole().evaluateJS("$_.tag = " + JSONObject.quote(tag) + ", $_");
final Promise[] promises = tab.getPromises().listPromises();
for (final Promise promise : promises) {
if (tag.equals(promise.getProperty("tag"))) {
return new PromiseWrapper(promise, mTimeoutMillis);
}
}
throw new AssertionError("Cannot find Promise");
}
return result;
}
/**

View File

@ -87,6 +87,8 @@
@BINPATH@/application.ini
@BINPATH@/platform.ini
@BINPATH@/blocklist.xml
@BINPATH@/defaults/settings/blocklists/addons.json
@BINPATH@/defaults/settings/blocklists/certificates.json
; [Components]
@BINPATH@/components/components.manifest

View File

@ -163,6 +163,13 @@
]
}
},
"en-CA": {
"default": {
"visibleDefaultEngines": [
"google", "bing", "amazon-ca", "ddg", "twitter", "wikipedia"
]
}
},
"en-GB": {
"default": {
"visibleDefaultEngines": [

View File

@ -1041,11 +1041,16 @@ case "$target" in
;;
esac
if test -n "$GNU_CC"; then
CFLAGS="$CFLAGS -fno-keep-inline-dllexport"
CXXFLAGS="$CXXFLAGS -fno-keep-inline-dllexport"
fi
case "$target" in
i*86-*)
if test -n "$GNU_CC"; then
CFLAGS="$CFLAGS -mstackrealign -fno-keep-inline-dllexport"
CXXFLAGS="$CXXFLAGS -mstackrealign -fno-keep-inline-dllexport"
CFLAGS="$CFLAGS -mstackrealign"
CXXFLAGS="$CXXFLAGS -mstackrealign"
LDFLAGS="$LDFLAGS -Wl,--enable-stdcall-fixup -Wl,--large-address-aware"
else
DSO_LDOPTS="$DSO_LDOPTS -MACHINE:X86"

View File

@ -273,6 +273,7 @@ function backupCerts() {
fp.appendFilter(bundle.getString("file_browse_PKCS12_spec"),
"*.p12");
fp.appendFilters(nsIFilePicker.filterAll);
fp.defaultExtension = "p12";
fp.open(rv => {
if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
certdb.exportPKCS12File(fp.file, selected_certs.length, selected_certs);

View File

@ -356,6 +356,13 @@ MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSGNDigestInfo,
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueVFYContext,
VFYContext,
internal::VFY_DestroyContext_true)
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSEC_PKCS12DecoderContext,
SEC_PKCS12DecoderContext,
SEC_PKCS12DecoderFinish)
MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSEC_PKCS12ExportContext,
SEC_PKCS12ExportContext,
SEC_PKCS12DestroyExportContext)
} // namespace mozilla
#endif // ScopedNSSTypes_h

View File

@ -2645,6 +2645,21 @@ setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx)
return NS_OK;
}
// NSS will call this during PKCS12 export to potentially switch the endianness
// of the characters of `inBuf` to big (network) endian. Since we already did
// that in nsPKCS12Blob::stringToBigEndianBytes, we just perform a memcpy here.
extern "C" {
PRBool
pkcs12StringEndiannessConversion(PRBool, unsigned char* inBuf,
unsigned int inBufLen, unsigned char* outBuf,
unsigned int, unsigned int* outBufLen, PRBool)
{
*outBufLen = inBufLen;
memcpy(outBuf, inBuf, inBufLen);
return true;
}
}
namespace mozilla {
namespace psm {
@ -2680,7 +2695,7 @@ InitializeCipherSuite()
SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
PORT_SetUCS2_ASCIIConversionFunction(pip_ucs2_ascii_conversion_fn);
PORT_SetUCS2_ASCIIConversionFunction(pkcs12StringEndiannessConversion);
// PSM enforces a minimum RSA key size of 1024 bits, which is overridable.
// NSS has its own minimum, which is not overridable (the default is 1023

View File

@ -1,20 +1,17 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef NSS_HELPER_
#define NSS_HELPER_
#ifndef nsNSSHelper_h
#define nsNSSHelper_h
#include "nsIInterfaceRequestor.h"
#include "nsIInterfaceRequestorUtils.h"
#include "pk11func.h"
//
// Implementation of an nsIInterfaceRequestor for use
// as context for NSS calls
//
// Implementation of an nsIInterfaceRequestor for use as context for NSS calls.
class PipUIContext : public nsIInterfaceRequestor
{
public:
@ -27,30 +24,12 @@ protected:
virtual ~PipUIContext();
};
//
// Function to get the implementor for a certain set of NSS
// specific dialogs.
//
// Function to get the implementor for a certain set of NSS specific dialogs.
nsresult
getNSSDialogs(void **_result, REFNSIID aIID, const char *contract);
extern "C" {
// a "fake" unicode conversion function
PRBool
pip_ucs2_ascii_conversion_fn(PRBool toUnicode,
unsigned char *inBuf,
unsigned int inBufLen,
unsigned char *outBuf,
unsigned int maxOutBufLen,
unsigned int *outBufLen,
PRBool swapBytes);
}
//
// A function that sets the password on an unitialized slot.
//
nsresult
setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx);
#endif
#endif // nsNSSHelper_h

View File

@ -26,139 +26,128 @@
using namespace mozilla;
extern LazyLogModule gPIPNSSLog;
#define PIP_PKCS12_TMPFILENAME NS_LITERAL_CSTRING(".pip_p12tmp")
#define PIP_PKCS12_BUFFER_SIZE 2048
#define PIP_PKCS12_USER_CANCELED 3
#define PIP_PKCS12_NOSMARTCARD_EXPORT 4
#define PIP_PKCS12_RESTORE_FAILED 5
#define PIP_PKCS12_BACKUP_FAILED 6
#define PIP_PKCS12_NSS_ERROR 7
#define PIP_PKCS12_BUFFER_SIZE 2048
#define PIP_PKCS12_NOSMARTCARD_EXPORT 4
#define PIP_PKCS12_RESTORE_FAILED 5
#define PIP_PKCS12_BACKUP_FAILED 6
#define PIP_PKCS12_NSS_ERROR 7
// constructor
nsPKCS12Blob::nsPKCS12Blob()
: mCertArray(nullptr)
, mTmpFile(nullptr)
: mUIContext(new PipUIContext())
{
mUIContext = new PipUIContext();
}
// nsPKCS12Blob::ImportFromFile
//
// Given a file handle, read a PKCS#12 blob from that file, decode it, and
// import the results into the internal database.
nsresult
nsPKCS12Blob::ImportFromFile(nsIFile *file)
nsPKCS12Blob::ImportFromFile(nsIFile* file)
{
nsresult rv = NS_OK;
nsresult rv;
RetryReason wantRetry;
do {
rv = ImportFromFileHelper(file, im_standard_prompt, wantRetry);
rv = ImportFromFileHelper(file, ImportMode::StandardPrompt, wantRetry);
if (NS_SUCCEEDED(rv) && wantRetry == rr_auto_retry_empty_password_flavors) {
rv = ImportFromFileHelper(file, im_try_zero_length_secitem, wantRetry);
if (NS_SUCCEEDED(rv) && wantRetry == RetryReason::AutoRetryEmptyPassword) {
rv = ImportFromFileHelper(file, ImportMode::TryZeroLengthSecitem,
wantRetry);
}
}
while (NS_SUCCEEDED(rv) && (wantRetry != rr_do_not_retry));
} while (NS_SUCCEEDED(rv) && (wantRetry != RetryReason::DoNotRetry));
return rv;
}
nsresult
nsPKCS12Blob::ImportFromFileHelper(nsIFile *file,
nsPKCS12Blob::ImportMode aImportMode,
nsPKCS12Blob::RetryReason &aWantRetry)
void
nsPKCS12Blob::handleImportError(PRErrorCode nssError, RetryReason& retryReason,
uint32_t passwordLengthInBytes)
{
nsresult rv = NS_OK;
SECStatus srv = SECSuccess;
SEC_PKCS12DecoderContext *dcx = nullptr;
SECItem unicodePw = { siBuffer, nullptr, 0 };
if (nssError == SEC_ERROR_BAD_PASSWORD) {
// If the password is 2 bytes, it only consists of the wide character null
// terminator. In this case we want to retry with a zero-length password.
if (passwordLengthInBytes == 2) {
retryReason = nsPKCS12Blob::RetryReason::AutoRetryEmptyPassword;
} else {
retryReason = RetryReason::BadPassword;
handleError(PIP_PKCS12_NSS_ERROR, nssError);
}
} else {
handleError(PIP_PKCS12_NSS_ERROR, nssError);
}
}
aWantRetry = rr_do_not_retry;
// Returns a failing nsresult if some XPCOM operation failed, and NS_OK
// otherwise. Returns by reference whether or not we want to retry the operation
// immediately.
nsresult
nsPKCS12Blob::ImportFromFileHelper(nsIFile* file, ImportMode aImportMode,
RetryReason& aWantRetry)
{
aWantRetry = RetryReason::DoNotRetry;
UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
if (!slot) {
srv = SECFailure;
goto finish;
return NS_ERROR_FAILURE;
}
if (aImportMode == im_try_zero_length_secitem) {
unicodePw.len = 0;
uint32_t passwordBufferLength;
UniquePtr<uint8_t[]> passwordBuffer;
if (aImportMode == ImportMode::TryZeroLengthSecitem) {
passwordBufferLength = 0;
passwordBuffer = nullptr;
} else {
// get file password (unicode)
rv = getPKCS12FilePassword(&unicodePw);
if (NS_FAILED(rv)) goto finish;
if (!unicodePw.data) {
handleError(PIP_PKCS12_USER_CANCELED);
nsresult rv = getPKCS12FilePassword(passwordBufferLength, passwordBuffer);
if (NS_FAILED(rv)) {
return rv;
}
if (!passwordBuffer) {
return NS_OK;
}
}
// initialize the decoder
dcx = SEC_PKCS12DecoderStart(&unicodePw, slot.get(), nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr);
SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength };
UniqueSEC_PKCS12DecoderContext dcx(
SEC_PKCS12DecoderStart(&unicodePw, slot.get(), nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr));
if (!dcx) {
srv = SECFailure;
goto finish;
return NS_ERROR_FAILURE;
}
// read input file and feed it to the decoder
rv = inputToDecoder(dcx, file);
PRErrorCode nssError;
nsresult rv = inputToDecoder(dcx, file, nssError);
if (NS_FAILED(rv)) {
if (NS_ERROR_ABORT == rv) {
// inputToDecoder indicated a NSS error
srv = SECFailure;
}
goto finish;
return rv;
}
if (nssError != 0) {
handleImportError(nssError, aWantRetry, unicodePw.len);
return NS_OK;
}
// verify the blob
srv = SEC_PKCS12DecoderVerify(dcx);
if (srv) goto finish;
// validate bags
srv = SEC_PKCS12DecoderValidateBags(dcx, nickname_collision);
if (srv) goto finish;
// import cert and key
srv = SEC_PKCS12DecoderImportBags(dcx);
if (srv) goto finish;
// Later - check to see if this should become default email cert
finish:
// If srv != SECSuccess, NSS probably set a specific error code.
// We should use that error code instead of inventing a new one
// for every error possible.
SECStatus srv = SEC_PKCS12DecoderVerify(dcx.get());
if (srv != SECSuccess) {
if (SEC_ERROR_BAD_PASSWORD == PORT_GetError()) {
if (unicodePw.len == sizeof(char16_t))
{
// no password chars available,
// unicodeToItem allocated space for the trailing zero character only.
aWantRetry = rr_auto_retry_empty_password_flavors;
}
else
{
aWantRetry = rr_bad_password;
handleError(PIP_PKCS12_NSS_ERROR);
}
}
else
{
handleError(PIP_PKCS12_NSS_ERROR);
}
} else if (NS_FAILED(rv)) {
handleError(PIP_PKCS12_RESTORE_FAILED);
handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
return NS_OK;
}
// validate bags
srv = SEC_PKCS12DecoderValidateBags(dcx.get(), nicknameCollision);
if (srv != SECSuccess) {
handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
return NS_OK;
}
// import cert and key
srv = SEC_PKCS12DecoderImportBags(dcx.get());
if (srv != SECSuccess) {
handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
}
// finish the decoder
if (dcx)
SEC_PKCS12DecoderFinish(dcx);
SECITEM_ZfreeItem(&unicodePw, false);
return NS_OK;
}
static bool
isExtractable(SECKEYPrivateKey *privKey)
isExtractable(UniqueSECKEYPrivateKey& privKey)
{
ScopedAutoSECItem value;
SECStatus rv = PK11_ReadRawAttribute(PK11_TypePrivKey, privKey,
CKA_EXTRACTABLE, &value);
SECStatus rv = PK11_ReadRawAttribute(
PK11_TypePrivKey, privKey.get(), CKA_EXTRACTABLE, &value);
if (rv != SECSuccess) {
return false;
}
@ -170,252 +159,216 @@ isExtractable(SECKEYPrivateKey *privKey)
return isExtractable;
}
// nsPKCS12Blob::ExportToFile
//
// Having already loaded the certs, form them into a blob (loading the keys
// also), encode the blob, and stuff it into the file.
nsresult
nsPKCS12Blob::ExportToFile(nsIFile *file,
nsIX509Cert **certs, int numCerts)
nsPKCS12Blob::ExportToFile(nsIFile* file, nsIX509Cert** certs, int numCerts)
{
nsresult rv;
SECStatus srv = SECSuccess;
SEC_PKCS12ExportContext *ecx = nullptr;
SEC_PKCS12SafeInfo *certSafe = nullptr, *keySafe = nullptr;
SECItem unicodePw;
nsAutoString filePath;
int i;
nsCOMPtr<nsIFile> localFileRef;
// init slot
bool InformedUserNoSmartcardBackup = false;
int numCertsExported = 0;
bool informedUserNoSmartcardBackup = false;
// get file password (unicode)
unicodePw.data = nullptr;
rv = newPKCS12FilePassword(&unicodePw);
if (NS_FAILED(rv)) goto finish;
if (!unicodePw.data) {
handleError(PIP_PKCS12_USER_CANCELED);
uint32_t passwordBufferLength;
UniquePtr<uint8_t[]> passwordBuffer;
nsresult rv = newPKCS12FilePassword(passwordBufferLength, passwordBuffer);
if (NS_FAILED(rv)) {
return rv;
}
if (!passwordBuffer) {
return NS_OK;
}
// what about slotToUse in psm 1.x ???
// create export context
ecx = SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr /*slot*/, nullptr);
UniqueSEC_PKCS12ExportContext ecx(
SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr, nullptr));
if (!ecx) {
srv = SECFailure;
goto finish;
handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
return NS_ERROR_FAILURE;
}
// add password integrity
srv = SEC_PKCS12AddPasswordIntegrity(ecx, &unicodePw, SEC_OID_SHA1);
if (srv) goto finish;
for (i=0; i<numCerts; i++) {
nsNSSCertificate *cert = (nsNSSCertificate *)certs[i];
// get it as a CERTCertificate XXX
UniqueCERTCertificate nssCert(cert->GetCert());
SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength };
SECStatus srv = SEC_PKCS12AddPasswordIntegrity(ecx.get(), &unicodePw,
SEC_OID_SHA1);
if (srv != SECSuccess) {
handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
return NS_ERROR_FAILURE;
}
for (int i = 0; i < numCerts; i++) {
UniqueCERTCertificate nssCert(certs[i]->GetCert());
if (!nssCert) {
rv = NS_ERROR_FAILURE;
goto finish;
handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
return NS_ERROR_FAILURE;
}
// We can only successfully export certs that are on
// internal token. Most, if not all, smart card vendors
// won't let you extract the private key (in any way
// shape or form) from the card. So let's punt if
// We can probably only successfully export certs that are on the internal
// token. Most, if not all, smart card vendors won't let you extract the
// private key (in any way shape or form) from the card. So let's punt if
// the cert is not in the internal db.
if (nssCert->slot && !PK11_IsInternal(nssCert->slot)) {
// we aren't the internal token, see if the key is extractable.
SECKEYPrivateKey *privKey=PK11_FindKeyByDERCert(nssCert->slot,
nssCert.get(), this);
if (privKey) {
bool privKeyIsExtractable = isExtractable(privKey);
SECKEY_DestroyPrivateKey(privKey);
if (!privKeyIsExtractable) {
if (!InformedUserNoSmartcardBackup) {
InformedUserNoSmartcardBackup = true;
handleError(PIP_PKCS12_NOSMARTCARD_EXPORT);
}
continue;
// We aren't the internal token, see if the key is extractable.
UniqueSECKEYPrivateKey privKey(
PK11_FindKeyByDERCert(nssCert->slot, nssCert.get(), mUIContext));
if (privKey && !isExtractable(privKey)) {
if (!informedUserNoSmartcardBackup) {
informedUserNoSmartcardBackup = true;
handleError(PIP_PKCS12_NOSMARTCARD_EXPORT, PR_GetError());
}
continue;
}
}
// XXX this is why, to verify the slot is the same
// PK11_FindObjectForCert(nssCert, nullptr, slot);
// create the cert and key safes
keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx);
// certSafe and keySafe are owned by ecx.
SEC_PKCS12SafeInfo* certSafe;
SEC_PKCS12SafeInfo* keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx.get());
if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) {
certSafe = keySafe;
} else {
certSafe = SEC_PKCS12CreatePasswordPrivSafe(ecx, &unicodePw,
SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
certSafe = SEC_PKCS12CreatePasswordPrivSafe(
ecx.get(), &unicodePw,
SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
}
if (!certSafe || !keySafe) {
rv = NS_ERROR_FAILURE;
goto finish;
handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
return NS_ERROR_FAILURE;
}
// add the cert and key to the blob
srv = SEC_PKCS12AddCertAndKey(ecx, certSafe, nullptr, nssCert.get(),
CERT_GetDefaultCertDB(), // XXX
keySafe, nullptr, true, &unicodePw,
SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
if (srv) goto finish;
// cert was dup'ed, so release it
++numCertsExported;
srv = SEC_PKCS12AddCertAndKey(
ecx.get(),
certSafe,
nullptr,
nssCert.get(),
CERT_GetDefaultCertDB(),
keySafe,
nullptr,
true,
&unicodePw,
SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
if (srv != SECSuccess) {
handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
return NS_ERROR_FAILURE;
}
}
if (!numCertsExported) goto finish;
// prepare the instance to write to an export file
this->mTmpFile = nullptr;
file->GetPath(filePath);
// Use the nsCOMPtr var localFileRef so that
// the reference to the nsIFile we create gets released as soon as
// we're out of scope, ie when this function exits.
if (filePath.RFind(".p12", true, -1, 4) < 0) {
// We're going to add the .p12 extension to the file name just like
// Communicator used to. We create a new nsIFile and initialize
// it with the new patch.
filePath.AppendLiteral(".p12");
localFileRef = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
if (NS_FAILED(rv)) goto finish;
localFileRef->InitWithPath(filePath);
file = localFileRef;
UniquePRFileDesc prFile;
PRFileDesc* rawPRFile;
rv = file->OpenNSPRFileDesc(
PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0664, &rawPRFile);
if (NS_FAILED(rv) || !rawPRFile) {
handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
return NS_ERROR_FAILURE;
}
rv = file->OpenNSPRFileDesc(PR_RDWR|PR_CREATE_FILE|PR_TRUNCATE, 0664,
&mTmpFile);
if (NS_FAILED(rv) || !this->mTmpFile) goto finish;
prFile.reset(rawPRFile);
// encode and write
srv = SEC_PKCS12Encode(ecx, write_export_file, this);
if (srv) goto finish;
finish:
if (NS_FAILED(rv) || srv != SECSuccess) {
handleError(PIP_PKCS12_BACKUP_FAILED);
srv = SEC_PKCS12Encode(ecx.get(), writeExportFile, prFile.get());
if (srv != SECSuccess) {
handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
return NS_ERROR_FAILURE;
}
if (ecx)
SEC_PKCS12DestroyExportContext(ecx);
if (this->mTmpFile) {
PR_Close(this->mTmpFile);
this->mTmpFile = nullptr;
}
SECITEM_ZfreeItem(&unicodePw, false);
return rv;
return NS_OK;
}
///////////////////////////////////////////////////////////////////////
//
// private members
//
///////////////////////////////////////////////////////////////////////
// unicodeToItem
//
// For the NSS PKCS#12 library, must convert PRUnichars (shorts) to
// a buffer of octets. Must handle byte order correctly.
nsresult
nsPKCS12Blob::unicodeToItem(const nsString& uni, SECItem* item)
// For the NSS PKCS#12 library, must convert PRUnichars (shorts) to a buffer of
// octets. Must handle byte order correctly.
UniquePtr<uint8_t[]>
nsPKCS12Blob::stringToBigEndianBytes(const nsString& uni, uint32_t& bytesLength)
{
uint32_t len = uni.Length() + 1; // +1 for the null terminator.
if (!SECITEM_AllocItem(nullptr, item, sizeof(char16_t) * len)) {
return NS_ERROR_OUT_OF_MEMORY;
}
uint32_t wideLength = uni.Length() + 1; // +1 for the null terminator.
bytesLength = wideLength * 2;
auto buffer = MakeUnique<uint8_t[]>(bytesLength);
// We have to use a cast here because on Windows, uni.get() returns
// char16ptr_t instead of char16_t*.
mozilla::NativeEndian::copyAndSwapToBigEndian(
item->data,
static_cast<const char16_t*>(uni.get()),
len);
buffer.get(), static_cast<const char16_t*>(uni.get()), wideLength);
return NS_OK;
return buffer;
}
// newPKCS12FilePassword
//
// Launch a dialog requesting the user for a new PKCS#12 file passowrd.
// Handle user canceled by returning null password (caller must catch).
nsresult
nsPKCS12Blob::newPKCS12FilePassword(SECItem *unicodePw)
nsPKCS12Blob::newPKCS12FilePassword(uint32_t& passwordBufferLength,
UniquePtr<uint8_t[]>& passwordBuffer)
{
nsresult rv = NS_OK;
nsAutoString password;
nsCOMPtr<nsICertificateDialogs> certDialogs;
rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
NS_GET_IID(nsICertificateDialogs),
NS_CERTIFICATEDIALOGS_CONTRACTID);
if (NS_FAILED(rv)) return rv;
bool pressedOK;
nsresult rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
NS_GET_IID(nsICertificateDialogs),
NS_CERTIFICATEDIALOGS_CONTRACTID);
if (NS_FAILED(rv)) {
return rv;
}
bool pressedOK = false;
rv = certDialogs->SetPKCS12FilePassword(mUIContext, password, &pressedOK);
if (NS_FAILED(rv) || !pressedOK) return rv;
return unicodeToItem(password, unicodePw);
if (NS_FAILED(rv)) {
return rv;
}
if (!pressedOK) {
return NS_OK;
}
passwordBuffer = Move(stringToBigEndianBytes(password, passwordBufferLength));
return NS_OK;
}
// getPKCS12FilePassword
//
// Launch a dialog requesting the user for the password to a PKCS#12 file.
// Handle user canceled by returning null password (caller must catch).
nsresult
nsPKCS12Blob::getPKCS12FilePassword(SECItem *unicodePw)
nsPKCS12Blob::getPKCS12FilePassword(uint32_t& passwordBufferLength,
UniquePtr<uint8_t[]>& passwordBuffer)
{
nsresult rv = NS_OK;
nsAutoString password;
nsCOMPtr<nsICertificateDialogs> certDialogs;
rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
NS_GET_IID(nsICertificateDialogs),
NS_CERTIFICATEDIALOGS_CONTRACTID);
if (NS_FAILED(rv)) return rv;
bool pressedOK;
nsresult rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
NS_GET_IID(nsICertificateDialogs),
NS_CERTIFICATEDIALOGS_CONTRACTID);
if (NS_FAILED(rv)) {
return rv;
}
nsAutoString password;
bool pressedOK = false;
rv = certDialogs->GetPKCS12FilePassword(mUIContext, password, &pressedOK);
if (NS_FAILED(rv) || !pressedOK) return rv;
return unicodeToItem(password, unicodePw);
if (NS_FAILED(rv)) {
return rv;
}
if (!pressedOK) {
return NS_OK;
}
passwordBuffer = Move(stringToBigEndianBytes(password, passwordBufferLength));
return NS_OK;
}
// inputToDecoder
//
// Given a decoder, read bytes from file and input them to the decoder.
nsresult
nsPKCS12Blob::inputToDecoder(SEC_PKCS12DecoderContext *dcx, nsIFile *file)
nsPKCS12Blob::inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file,
PRErrorCode& nssError)
{
nsresult rv;
SECStatus srv;
uint32_t amount;
char buf[PIP_PKCS12_BUFFER_SIZE];
nssError = 0;
nsCOMPtr<nsIInputStream> fileStream;
rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
if (NS_FAILED(rv)) {
return rv;
}
char buf[PIP_PKCS12_BUFFER_SIZE];
uint32_t amount;
while (true) {
rv = fileStream->Read(buf, PIP_PKCS12_BUFFER_SIZE, &amount);
if (NS_FAILED(rv)) {
return rv;
}
// feed the file data into the decoder
srv = SEC_PKCS12DecoderUpdate(dcx,
(unsigned char*) buf,
amount);
if (srv) {
// don't allow the close call to overwrite our precious error code
int pr_err = PORT_GetError();
PORT_SetError(pr_err);
return NS_ERROR_ABORT;
SECStatus srv = SEC_PKCS12DecoderUpdate(
dcx.get(), (unsigned char*)buf, amount);
if (srv != SECSuccess) {
nssError = PR_GetError();
return NS_OK;
}
if (amount < PIP_PKCS12_BUFFER_SIZE)
if (amount < PIP_PKCS12_BUFFER_SIZE) {
break;
}
}
return NS_OK;
}
// nickname_collision
// what to do when the nickname collides with one already in the db.
// TODO: not handled, throw a dialog allowing the nick to be changed?
SECItem *
nsPKCS12Blob::nickname_collision(SECItem *oldNick, PRBool *cancel, void *wincx)
// What to do when the nickname collides with one already in the db.
SECItem*
nsPKCS12Blob::nicknameCollision(SECItem* oldNick, PRBool* cancel, void* wincx)
{
*cancel = false;
int count = 1;
@ -454,95 +407,83 @@ nsPKCS12Blob::nickname_collision(SECItem *oldNick, PRBool *cancel, void *wincx)
if (count > 1) {
nickname.AppendPrintf(" #%d", count);
}
UniqueCERTCertificate cert(CERT_FindCertByNickname(CERT_GetDefaultCertDB(),
nickname.get()));
UniqueCERTCertificate cert(
CERT_FindCertByNickname(CERT_GetDefaultCertDB(), nickname.get()));
if (!cert) {
break;
}
count++;
}
SECItem *newNick = new SECItem;
if (!newNick)
UniqueSECItem newNick(SECITEM_AllocItem(nullptr, nullptr,
nickname.Length() + 1));
if (!newNick) {
return nullptr;
}
memcpy(newNick->data, nickname.get(), nickname.Length());
newNick->data[nickname.Length()] = 0;
newNick->type = siAsciiString;
newNick->data = (unsigned char*) strdup(nickname.get());
newNick->len = strlen((char*)newNick->data);
return newNick;
return newNick.release();
}
// write_export_file
// write bytes to the exported PKCS#12 file
void
nsPKCS12Blob::write_export_file(void *arg, const char *buf, unsigned long len)
nsPKCS12Blob::writeExportFile(void* arg, const char* buf, unsigned long len)
{
nsPKCS12Blob *cx = (nsPKCS12Blob *)arg;
PR_Write(cx->mTmpFile, buf, len);
}
// pip_ucs2_ascii_conversion_fn
// required to be set by NSS (to do PKCS#12), but since we've already got
// unicode make this a no-op.
PRBool
pip_ucs2_ascii_conversion_fn(PRBool toUnicode,
unsigned char *inBuf,
unsigned int inBufLen,
unsigned char *outBuf,
unsigned int maxOutBufLen,
unsigned int *outBufLen,
PRBool swapBytes)
{
// do a no-op, since I've already got unicode. Hah!
*outBufLen = inBufLen;
memcpy(outBuf, inBuf, inBufLen);
return true;
PRFileDesc* file = static_cast<PRFileDesc*>(arg);
MOZ_RELEASE_ASSERT(file);
PR_Write(file, buf, len);
}
void
nsPKCS12Blob::handleError(int myerr)
nsPKCS12Blob::handleError(int myerr, PRErrorCode prerr)
{
MOZ_ASSERT(NS_IsMainThread());
if (!NS_IsMainThread()) {
return;
}
int prerr = PORT_GetError();
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PKCS12: NSS/NSPR error(%d)", prerr));
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PKCS12: I called(%d)", myerr));
const char * msgID = nullptr;
const char* msgID = nullptr;
switch (myerr) {
case PIP_PKCS12_USER_CANCELED:
return; /* Just ignore it for now */
case PIP_PKCS12_NOSMARTCARD_EXPORT: msgID = "PKCS12InfoNoSmartcardBackup"; break;
case PIP_PKCS12_RESTORE_FAILED: msgID = "PKCS12UnknownErrRestore"; break;
case PIP_PKCS12_BACKUP_FAILED: msgID = "PKCS12UnknownErrBackup"; break;
case PIP_PKCS12_NSS_ERROR:
switch (prerr) {
// The following errors have the potential to be "handled", by asking
// the user (via a dialog) whether s/he wishes to continue
case 0: break;
case SEC_ERROR_PKCS12_CERT_COLLISION:
/* pop a dialog saying the cert is already in the database */
/* ask to keep going? what happens if one collision but others ok? */
// The following errors cannot be "handled", notify the user (via an alert)
// that the operation failed.
case SEC_ERROR_BAD_PASSWORD: msgID = "PK11BadPassword"; break;
case SEC_ERROR_BAD_DER:
case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE:
case SEC_ERROR_PKCS12_INVALID_MAC:
msgID = "PKCS12DecodeErr";
case PIP_PKCS12_NOSMARTCARD_EXPORT:
msgID = "PKCS12InfoNoSmartcardBackup";
break;
case PIP_PKCS12_RESTORE_FAILED:
msgID = "PKCS12UnknownErrRestore";
break;
case PIP_PKCS12_BACKUP_FAILED:
msgID = "PKCS12UnknownErrBackup";
break;
case PIP_PKCS12_NSS_ERROR:
switch (prerr) {
case 0:
break;
case SEC_ERROR_PKCS12_CERT_COLLISION:
msgID = "PKCS12DupData";
break;
case SEC_ERROR_BAD_PASSWORD:
msgID = "PK11BadPassword";
break;
case SEC_ERROR_PKCS12_DUPLICATE_DATA: msgID = "PKCS12DupData"; break;
}
break;
case SEC_ERROR_BAD_DER:
case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE:
case SEC_ERROR_PKCS12_INVALID_MAC:
msgID = "PKCS12DecodeErr";
break;
case SEC_ERROR_PKCS12_DUPLICATE_DATA:
msgID = "PKCS12DupData";
break;
}
break;
}
if (!msgID)
if (!msgID) {
msgID = "PKCS12UnknownErr";
}
nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
if (!wwatch) {

View File

@ -1,3 +1,5 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -14,57 +16,65 @@
class nsIFile;
class nsIX509Cert;
//
// nsPKCS12Blob
//
// Class for importing/exporting PKCS#12 blobs
//
class nsPKCS12Blob
{
public:
nsPKCS12Blob();
virtual ~nsPKCS12Blob() {}
~nsPKCS12Blob() {}
// PKCS#12 Import
nsresult ImportFromFile(nsIFile *file);
nsresult ImportFromFile(nsIFile* file);
// PKCS#12 Export
nsresult ExportToFile(nsIFile *file, nsIX509Cert **certs, int numCerts);
nsresult ExportToFile(nsIFile* file, nsIX509Cert** certs, int numCerts);
private:
nsCOMPtr<nsIMutableArray> mCertArray;
nsCOMPtr<nsIInterfaceRequestor> mUIContext;
// local helper functions
nsresult getPKCS12FilePassword(SECItem *);
nsresult newPKCS12FilePassword(SECItem *);
nsresult inputToDecoder(SEC_PKCS12DecoderContext *, nsIFile *);
nsresult unicodeToItem(const nsString& uni, SECItem* item);
void handleError(int myerr = 0);
nsresult getPKCS12FilePassword(uint32_t& passwordBufferLength,
UniquePtr<uint8_t[]>& passwordBuffer);
nsresult newPKCS12FilePassword(uint32_t& passwordBufferLength,
UniquePtr<uint8_t[]>& passwordBuffer);
nsresult inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file,
PRErrorCode& nssError);
UniquePtr<uint8_t[]> stringToBigEndianBytes(const nsString& uni,
uint32_t& bytesLength);
void handleError(int myerr, PRErrorCode prerr);
// RetryReason and ImportMode are used when importing a PKCS12 file.
// There are two reasons that cause us to retry:
// - When the password entered by the user is incorrect.
// The user will be prompted to try again.
// - When the user entered a zero length password.
// An empty password should be represented as an empty
// string (a SECItem that contains a single terminating
// null UTF16 character), but some applications use a
// zero length SECItem.
// We try both variations, zero length item and empty string,
// without giving a user prompt when trying the different empty password flavors.
// An empty password should be represented as an empty string (a SECItem
// that contains a single terminating null UTF16 character), but some
// applications use a zero length SECItem. We try both variations, zero
// length item and empty string, without giving a user prompt when trying
// the different empty password flavors.
enum class RetryReason
{
DoNotRetry,
BadPassword,
AutoRetryEmptyPassword,
};
enum class ImportMode
{
StandardPrompt,
TryZeroLengthSecitem
};
enum RetryReason { rr_do_not_retry, rr_bad_password, rr_auto_retry_empty_password_flavors };
enum ImportMode { im_standard_prompt, im_try_zero_length_secitem };
void handleImportError(PRErrorCode nssError, RetryReason& retryReason,
uint32_t passwordLengthInBytes);
nsresult ImportFromFileHelper(nsIFile *file, ImportMode aImportMode, RetryReason &aWantRetry);
nsresult ImportFromFileHelper(nsIFile* file,
ImportMode aImportMode,
RetryReason& aWantRetry);
// NSPR file I/O for export file
PRFileDesc *mTmpFile;
static SECItem * nickname_collision(SECItem *, PRBool *, void *);
static void write_export_file(void *arg, const char *buf, unsigned long len);
static SECItem* nicknameCollision(SECItem* oldNick, PRBool* cancel,
void* wincx);
static void writeExportFile(void* arg, const char* buf, unsigned long len);
};
#endif // nsPKCS12Blob_h

View File

@ -0,0 +1,115 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";
// Tests exporting a certificate and key as a PKCS#12 blob and importing it
// again with a new password set.
do_get_profile();
const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
const PKCS12_FILE = "test_certDB_import/cert_from_windows.pfx";
const CERT_COMMON_NAME = "test_cert_from_windows";
const TEST_CERT_PASSWORD = "黒い";
const TEST_OUTPUT_PASSWORD = "other password";
let gPasswordToUse = TEST_CERT_PASSWORD;
// Mock implementation of nsICertificateDialogs.
const gCertificateDialogs = {
confirmDownloadCACert: () => {
// We don't test anything that calls this method.
ok(false, "confirmDownloadCACert() should not have been called");
},
setPKCS12FilePassword: (ctx, password) => {
password.value = gPasswordToUse;
return true;
},
getPKCS12FilePassword: (ctx, password) => {
password.value = gPasswordToUse;
return true;
},
viewCert: (ctx, cert) => {
// This shouldn't be called for import methods.
ok(false, "viewCert() should not have been called");
},
QueryInterface: ChromeUtils.generateQI([Ci.nsICertificateDialogs])
};
var gPrompt = {
clickOk: true,
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
// This intentionally does not use arrow function syntax to avoid an issue
// where in the context of the arrow function, |this != gPrompt| due to
// how objects get wrapped when going across xpcom boundaries.
alert(title, text) {
ok(false, "Not expecting alert to be called.");
},
promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
ok(false, "Not expecting a password prompt.");
return false;
},
};
const gPromptFactory = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptFactory]),
getPrompt: (aWindow, aIID) => gPrompt,
};
function findCertByCommonName(commonName) {
let certEnumerator = gCertDB.getCerts().getEnumerator();
while (certEnumerator.hasMoreElements()) {
let cert = certEnumerator.getNext().QueryInterface(Ci.nsIX509Cert);
if (cert.commonName == commonName) {
return cert;
}
}
return null;
}
function run_test() {
let certificateDialogsCID =
MockRegistrar.register("@mozilla.org/nsCertificateDialogs;1",
gCertificateDialogs);
let promptFactoryCID =
MockRegistrar.register("@mozilla.org/prompter;1", gPromptFactory);
registerCleanupFunction(() => {
MockRegistrar.unregister(certificateDialogsCID);
MockRegistrar.unregister(promptFactoryCID);
});
// Import the certificate and key so we have something to export.
let cert = findCertByCommonName(CERT_COMMON_NAME);
equal(cert, null, "cert should not be found before import");
let certFile = do_get_file(PKCS12_FILE);
ok(certFile, `${PKCS12_FILE} should exist`);
gPasswordToUse = TEST_CERT_PASSWORD;
gCertDB.importPKCS12File(certFile);
cert = findCertByCommonName(CERT_COMMON_NAME);
notEqual(cert, null, "cert should be found now");
// Export the certificate and key.
let output = do_get_tempdir();
output.append("output.p12");
ok(!output.exists(), "output shouldn't exist before exporting PKCS12 file");
gPasswordToUse = TEST_OUTPUT_PASSWORD;
gCertDB.exportPKCS12File(output, 1, [cert]);
ok(output.exists(), "output should exist after exporting PKCS12 file");
// We should be able to import the exported blob again using the new password.
gCertDB.importPKCS12File(output);
output.remove(false /* not a directory; recursive doesn't apply */);
// Ideally there would be some way to confirm that this actually did anything.
// Unfortunately, since deleting a certificate currently doesn't actually do
// anything until the platform is restarted, we can't confirm that we
// successfully re-imported the certificate.
}

View File

@ -0,0 +1,138 @@
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";
// Tests exporting a certificate and key as a PKCS#12 blob if the user has a
// master password set.
do_get_profile();
const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
const PKCS12_FILE = "test_certDB_import/cert_from_windows.pfx";
const CERT_COMMON_NAME = "test_cert_from_windows";
const TEST_CERT_PASSWORD = "黒い";
// Mock implementation of nsICertificateDialogs.
const gCertificateDialogs = {
confirmDownloadCACert: () => {
// We don't test anything that calls this method.
ok(false, "confirmDownloadCACert() should not have been called");
},
setPKCS12FilePassword: (ctx, password) => {
password.value = TEST_CERT_PASSWORD;
return true;
},
getPKCS12FilePassword: (ctx, password) => {
password.value = TEST_CERT_PASSWORD;
return true;
},
viewCert: (ctx, cert) => {
// This shouldn't be called for import methods.
ok(false, "viewCert() should not have been called");
},
QueryInterface: ChromeUtils.generateQI([Ci.nsICertificateDialogs])
};
var gPrompt = {
password: "password",
clickOk: true,
expectingAlert: false,
expectedAlertRegexp: null,
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
// This intentionally does not use arrow function syntax to avoid an issue
// where in the context of the arrow function, |this != gPrompt| due to
// how objects get wrapped when going across xpcom boundaries.
alert(title, text) {
info(`alert('${text}')`);
ok(this.expectingAlert,
"alert() should only be called if we're expecting it");
ok(this.expectedAlertRegexp.test(text),
"alert text should match expected message");
},
promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
equal(text,
"Please enter your master password.",
"password prompt text should be as expected");
equal(checkMsg, null, "checkMsg should be null");
password.value = this.password;
return this.clickOk;
},
};
const gPromptFactory = {
QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptFactory]),
getPrompt: (aWindow, aIID) => gPrompt,
};
function findCertByCommonName(commonName) {
let certEnumerator = gCertDB.getCerts().getEnumerator();
while (certEnumerator.hasMoreElements()) {
let cert = certEnumerator.getNext().QueryInterface(Ci.nsIX509Cert);
if (cert.commonName == commonName) {
return cert;
}
}
return null;
}
function run_test() {
let certificateDialogsCID =
MockRegistrar.register("@mozilla.org/nsCertificateDialogs;1",
gCertificateDialogs);
let promptFactoryCID =
MockRegistrar.register("@mozilla.org/prompter;1", gPromptFactory);
registerCleanupFunction(() => {
MockRegistrar.unregister(certificateDialogsCID);
MockRegistrar.unregister(promptFactoryCID);
});
// Set a master password.
let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
.getService(Ci.nsIPK11TokenDB);
let token = tokenDB.getInternalKeyToken();
token.initPassword("password");
token.logoutSimple();
// Import the certificate and key so we have something to export.
let cert = findCertByCommonName(CERT_COMMON_NAME);
equal(cert, null, "cert should not be found before import");
let certFile = do_get_file(PKCS12_FILE);
ok(certFile, `${PKCS12_FILE} should exist`);
gCertDB.importPKCS12File(certFile);
cert = findCertByCommonName(CERT_COMMON_NAME);
notEqual(cert, null, "cert should be found now");
// Log out so we're prompted for the password.
token.logoutSimple();
// Export the certificate and key (and don't cancel the password request
// dialog).
let output = do_get_tempdir();
output.append("output.p12");
ok(!output.exists(), "output shouldn't exist before exporting PKCS12 file");
gCertDB.exportPKCS12File(output, 1, [cert]);
ok(output.exists(), "output should exist after exporting PKCS12 file");
output.remove(false /* not a directory; recursive doesn't apply */);
// Log out again so we're prompted for the password.
token.logoutSimple();
// Attempt to export the certificate and key, but this time cancel the
// password request dialog. The export operation should also be canceled.
gPrompt.clickOk = false;
let output2 = do_get_tempdir();
output2.append("output2.p12");
ok(!output2.exists(), "output2 shouldn't exist before exporting PKCS12 file");
gPrompt.expectingAlert = true;
gPrompt.expectedAlertRegexp = /Failed to create the PKCS #12 backup file for unknown reasons\./;
throws(() => gCertDB.exportPKCS12File(output, 1, [cert]), /NS_ERROR_FAILURE/);
ok(!output2.exists(), "output2 shouldn't exist after failing to export");
}

View File

@ -63,6 +63,8 @@ run-sequentially = hardcoded ports
[test_cert_signatures.js]
[test_cert_trust.js]
[test_cert_version.js]
[test_certDB_export_pkcs12.js]
[test_certDB_export_pkcs12_with_master_password.js]
[test_certDB_import.js]
[test_certDB_import_pkcs12.js]
[test_certDB_import_with_master_password.js]

View File

@ -90,6 +90,18 @@ When an entry has a file attached to it, it has an ``attachment`` attribute, whi
}
});
Initial data
------------
For newly created user profiles, the list of entries returned by the ``.get()`` method will be empty until the first synchronization happens.
It is possible to package a dump of the server records that will be loaded into the local database when no synchronization has happened yet. It will thus serve as the default dataset and also reduce the amount of data to be downloaded on the first synchronization.
#. Place the JSON dump of the server records in the ``services/settings/dumps/main/`` folder
#. Add the filename to the ``FINAL_TARGET_FILES`` list in ``services/settings/dumps/main/moz.build``
Now, when ``RemoteSettings("some-key").get()`` is called from an empty profile, the ``some-key.json`` file is going to be loaded before the results are returned.
Uptake Telemetry
================

View File

@ -213,6 +213,21 @@ class RemoteSettingsClient {
// whose target is matched.
const { filters = {}, order } = options;
const c = await this.openCollection();
const timestamp = await c.db.getLastModified();
// If the local database was never synchronized, then we attempt to load
// a packaged JSON dump.
if (timestamp == null) {
try {
const { data } = await this._loadDumpFile();
await c.loadDump(data);
} catch (e) {
// Report but return an empty list since there will be no data anyway.
Cu.reportError(e);
return [];
}
}
const { data } = await c.list({ filters, order });
return this._filterEntries(data);
}
@ -402,7 +417,7 @@ class RemoteSettingsClient {
async _loadDumpFile() {
// Replace OS specific path separator by / for URI.
const { components: folderFile } = OS.Path.split(this.filename);
const fileURI = `resource://app/defaults/${folderFile.join("/")}`;
const fileURI = `resource://app/defaults/settings/${folderFile.join("/")}`;
const response = await fetch(fileURI);
if (!response.ok) {
throw new Error(`Could not read from '${fileURI}'`);

View File

@ -1,5 +1,6 @@
const { Constructor: CC } = Components;
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://testing-common/httpd.js");
const { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm", {});
@ -10,6 +11,8 @@ const BlocklistClients = ChromeUtils.import("resource://services-common/blocklis
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
const IS_ANDROID = AppConstants.platform == "android";
let gBlocklistClients;
let server;
@ -99,7 +102,14 @@ function run_test() {
}
add_task(async function test_initial_dump_is_loaded_as_synced_when_collection_is_empty() {
const november2016 = 1480000000000;
for (let {client} of gBlocklistClients) {
if (IS_ANDROID && client.collectionName != BlocklistClients.AddonBlocklistClient.collectionName) {
// On Android we don't ship the dumps of plugins and gfx.
continue;
}
// Test an empty db populates, but don't reach server (specified timestamp <= dump).
await client.maybeSync(1, Date.now());
@ -107,6 +117,28 @@ add_task(async function test_initial_dump_is_loaded_as_synced_when_collection_is
const collection = await client.openCollection();
const { data: list } = await collection.list();
equal(list[0]._status, "synced");
// Verify that the internal timestamp was updated.
const timestamp = await collection.db.getLastModified();
ok(timestamp > november2016, `Loaded dump of ${client.collectionName} has timestamp ${timestamp}`);
}
});
add_task(clear_state);
add_task(async function test_initial_dump_is_loaded_when_using_get_on_empty_collection() {
for (let {client} of gBlocklistClients) {
if (IS_ANDROID && client.collectionName != BlocklistClients.AddonBlocklistClient.collectionName) {
// On Android we don't ship the dumps of plugins and gfx.
continue;
}
// Internal database is empty.
const collection = await client.openCollection();
const { data: list } = await collection.list();
equal(list.length, 0);
// Calling .get() will load the dump.
const afterLoaded = await client.get();
ok(afterLoaded.length > 0, `Loaded dump of ${client.collectionName} has ${afterLoaded.length} records`);
}
});
add_task(clear_state);

View File

@ -16,6 +16,7 @@ async function createRecords(records) {
for (const record of records) {
await collection.create(record);
}
collection.db.saveLastModified(42); // Simulate sync (and prevent load dump).
}

View File

@ -103,6 +103,17 @@ add_task(async function test_records_changes_are_overwritten_by_server_changes()
});
add_task(clear_state);
add_task(async function test_default_records_come_from_a_local_dump_when_database_is_empty() {
// When collection is unknown, no dump is loaded, and there is no error.
let data = await RemoteSettings("some-unknown-key").get();
equal(data.length, 0);
// When collection has a dump in services/settings/dumps/{bucket}/{collection}.json
data = await RemoteSettings("certificates", { bucketName: "blocklists" }).get();
notEqual(data.length, 0);
});
add_task(clear_state);
add_task(async function test_sync_event_provides_information_about_records() {
const serverTime = Date.now();

View File

@ -9,8 +9,8 @@ support-files =
[test_load_modules.js]
[test_blocklist_certificates.js]
# Initial JSON data for blocklists are not shipped on Android.
skip-if = (os == "android" || appname == "thunderbird")
# Skip signature tests for Thunderbird (Bug 1341983).
skip-if = appname == "thunderbird"
tags = blocklist
[test_blocklist_clients.js]
tags = blocklist

View File

@ -10,12 +10,12 @@ with Files('moz.build'):
DIRS += [
'common',
'crypto',
'settings',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
DIRS += [
'fxaccounts',
'blocklists',
]
if CONFIG['MOZ_SERVICES_SYNC']:

View File

@ -7,12 +7,10 @@
with Files('**'):
BUG_COMPONENT = ('Toolkit', 'Blocklisting')
FINAL_TARGET_FILES.defaults.blocklists += ['addons.json',
'certificates.json',
'gfx.json',
'plugins.json']
FINAL_TARGET_FILES.defaults.pinning += ['pins.json']
FINAL_TARGET_FILES.defaults.settings.blocklists += ['addons.json',
'certificates.json',
'gfx.json',
'plugins.json']
if CONFIG['MOZ_BUILD_APP'] == 'browser':
DIST_SUBDIR = 'browser'

View File

@ -0,0 +1,10 @@
# 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/.
FINAL_TARGET_FILES.defaults.settings.main += [
'tippytop.json',
]
if CONFIG['MOZ_BUILD_APP'] == 'browser':
DIST_SUBDIR = 'browser'

View File

@ -0,0 +1,9 @@
# 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/.
DIRS += [
'blocklists',
'main',
'pinning',
]

View File

@ -0,0 +1,8 @@
# 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/.
FINAL_TARGET_FILES.defaults.settings.pinning += ['pins.json']
if CONFIG['MOZ_BUILD_APP'] == 'browser':
DIST_SUBDIR = 'browser'

View File

@ -0,0 +1 @@
{"data":[]}

View File

@ -0,0 +1,10 @@
# 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/.
with Files('**'):
BUG_COMPONENT = ('Firefox', 'Remote Settings Client')
DIRS += [
'dumps',
]

View File

@ -18,7 +18,7 @@ gcc_ext=xz
binutils_version=2.27
binutils_ext=bz2
binutils_configure_flags="--target=i686-w64-mingw32"
mingw_version=36d7b92bbcec1e72d3ce24013b01f7acc34be3b0
mingw_version=bcf1f29d6dc80b6025b416bef104d2314fa9be57
# GPG keys used to sign GCC (collected from 5.1.0, 5.4.0, 6.4.0)
$GPG --import $data_dir/33C235A34C46AA3FFB293709A328C3A2C3C45C06.key

View File

@ -1,4 +1,4 @@
#!/usr/bin/python2.7 -u
#!/usr/bin/python3 -u
# 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/.
@ -13,19 +13,27 @@ the requested process and prints its output, prefixing it with the
current time to improve log usefulness.
"""
from __future__ import absolute_import, print_function, unicode_literals
import sys
if sys.version_info[0:2] < (3, 5):
print('run-task requires Python 3.5+')
sys.exit(1)
import argparse
import datetime
import errno
import io
import json
import os
import re
import socket
import stat
import subprocess
import sys
import urllib2
import urllib.error
import urllib.request
FINGERPRINT_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgfingerprint'
@ -76,9 +84,11 @@ IS_WINDOWS = os.name == 'nt'
def print_line(prefix, m):
now = datetime.datetime.utcnow().isoformat()
now = now[:-3] if now[-7] == '.' else now # slice microseconds to 3 decimals
print(b'[%s %sZ] %s' % (prefix, now, m), end=b'')
now = datetime.datetime.utcnow().isoformat().encode('utf-8')
# slice microseconds to 3 decimals.
now = now[:-3] if now[-7:-6] == b'.' else now
sys.stdout.buffer.write(b'[%s %sZ] %s' % (prefix, now, m))
sys.stdout.buffer.flush()
def run_and_prefix_output(prefix, args, extra_env=None):
@ -86,7 +96,7 @@ def run_and_prefix_output(prefix, args, extra_env=None):
Returns the process exit code.
"""
print_line(prefix, b'executing %s\n' % args)
print_line(prefix, b'executing %r\n' % args)
env = dict(os.environ)
env.update(extra_env or {})
@ -95,6 +105,13 @@ def run_and_prefix_output(prefix, args, extra_env=None):
# when we pass sys.stdin to the invoked process. If we cared
# to preserve stdin as a TTY, we could make this work. But until
# someone needs it, don't bother.
# We want stdout to be bytes on Python 3. That means we can't use
# universal_newlines=True (because it implies text mode). But
# p.stdout.readline() won't work for bytes text streams. So, on Python 3,
# we manually install a latin1 stream wrapper. This allows us to readline()
# and preserves bytes, without losing any data.
p = subprocess.Popen(args,
# Disable buffering because we want to receive output
# as it is generated so timestamps in logs are
@ -104,13 +121,13 @@ def run_and_prefix_output(prefix, args, extra_env=None):
stderr=subprocess.STDOUT,
stdin=sys.stdin.fileno(),
cwd='/',
env=env,
# So \r in progress bars are rendered as multiple
# lines, preserving progress indicators.
universal_newlines=True)
env=env)
stdout = io.TextIOWrapper(p.stdout, encoding='latin1')
while True:
data = p.stdout.readline()
data = stdout.readline().encode('latin1')
if data == b'':
break
@ -154,10 +171,10 @@ def get_posix_user_group(user, group):
def write_audit_entry(path, msg):
now = datetime.datetime.utcnow().isoformat()
now = datetime.datetime.utcnow().isoformat().encode('utf-8')
with open(path, 'ab') as fh:
fh.write(b'[%sZ %s] %s\n' % (
now, os.environ.get('TASK_ID', 'UNKNOWN'), msg))
now, os.environb.get(b'TASK_ID', b'UNKNOWN'), msg))
WANTED_DIR_MODE = stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
@ -178,7 +195,8 @@ def set_dir_permissions(path, uid, gid):
def chown_recursive(path, user, group, uid, gid):
print_line(b'chown',
b'recursively changing ownership of %s to %s:%s\n' %
(path, user, group))
(path.encode('utf-8'), user.encode('utf-8'), group.encode(
'utf-8')))
set_dir_permissions(path, uid, gid)
@ -235,7 +253,7 @@ def configure_cache_posix(cache, user, group,
if not os.listdir(cache):
print_line(b'cache', b'cache %s is empty; writing requirements: '
b'%s\n' % (
cache, b' '.join(sorted(our_requirements))))
cache.encode('utf-8'), b' '.join(sorted(our_requirements))))
# We write a requirements file so future invocations know what the
# requirements are.
@ -246,8 +264,8 @@ def configure_cache_posix(cache, user, group,
os.chmod(requires_path, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
write_audit_entry(audit_path,
'created; requirements: %s' %
', '.join(sorted(our_requirements)))
b'created; requirements: %s' %
b', '.join(sorted(our_requirements)))
set_dir_permissions(cache, user.pw_uid, group.gr_gid)
return
@ -259,7 +277,7 @@ def configure_cache_posix(cache, user, group,
wanted_requirements = set(fh.read().splitlines())
print_line(b'cache', b'cache %s exists; requirements: %s\n' % (
cache, b' '.join(sorted(wanted_requirements))))
cache.encode('utf-8'), b' '.join(sorted(wanted_requirements))))
missing = wanted_requirements - our_requirements
@ -270,12 +288,12 @@ def configure_cache_posix(cache, user, group,
# environments like Try, this is a perfectly reasonable thing to
# allow.
if missing and untrusted_caches and running_as_root and \
all(s.startswith(('uid=', 'gid=')) for s in missing):
all(s.startswith((b'uid=', b'gid=')) for s in missing):
print_line(b'cache',
b'cache %s uid/gid mismatch; this is acceptable '
b'because caches for this task are untrusted; '
b'changing ownership to facilitate cache use\n' %
cache)
cache.encode('utf-8'))
chown_recursive(cache, user.pw_name, group.gr_name, user.pw_uid,
group.gr_gid)
@ -284,31 +302,31 @@ def configure_cache_posix(cache, user, group,
fh.write(b'\n'.join(sorted(our_requirements)))
write_audit_entry(audit_path,
'chown; requirements: %s' %
', '.join(sorted(our_requirements)))
b'chown; requirements: %s' %
b', '.join(sorted(our_requirements)))
elif missing:
print('error: requirements for populated cache %s differ from '
'this task' % cache)
print('cache requirements: %s' % ' '.join(sorted(
wanted_requirements)))
s.decode('utf-8') for s in wanted_requirements)))
print('our requirements: %s' % ' '.join(sorted(
our_requirements)))
if any(s.startswith(('uid=', 'gid=')) for s in missing):
s.decode('utf-8') for s in our_requirements)))
if any(s.startswith((b'uid=', b'gid=')) for s in missing):
print(CACHE_UID_GID_MISMATCH)
write_audit_entry(audit_path,
'requirements mismatch; wanted: %s' %
', '.join(sorted(our_requirements)))
b'requirements mismatch; wanted: %s' %
b', '.join(sorted(our_requirements)))
print('')
print('audit log:')
with open(audit_path, 'rb') as fh:
with open(audit_path, 'r') as fh:
print(fh.read())
return True
else:
write_audit_entry(audit_path, 'used')
write_audit_entry(audit_path, b'used')
# We don't need to adjust permissions here because the cache is
# associated with a uid/gid and the first task should have set
@ -324,7 +342,7 @@ def configure_cache_posix(cache, user, group,
'likely mis-configured or TASKCLUSTER_CACHES is not set '
'properly' % cache)
write_audit_entry(audit_path, 'missing .cacherequires')
write_audit_entry(audit_path, b'missing .cacherequires')
return True
@ -349,7 +367,8 @@ def configure_volume_posix(volume, user, group, running_as_root):
if running_as_root:
print_line(b'volume', b'changing ownership of volume %s '
b'to %d:%d\n' % (volume, user.pw_uid,
b'to %d:%d\n' % (volume.encode('utf-8'),
user.pw_uid,
group.gr_gid))
set_dir_permissions(volume, user.pw_uid, group.gr_gid)
@ -361,60 +380,60 @@ def vcs_checkout(source_repo, dest, store_path,
# SHA-1 strings, but also supports symbolic revisions like `tip` via the
# branch flag.
if revision:
revision_flag = b'--revision'
revision_flag = '--revision'
revision_value = revision
elif branch:
revision_flag = b'--branch'
revision_flag = '--branch'
revision_value = branch
else:
print('revision is not specified for checkout')
sys.exit(1)
if IS_POSIX:
hg_bin = b'hg'
hg_bin = 'hg'
elif IS_WINDOWS:
# This is where OCC installs it in the AMIs.
hg_bin = br'C:\Program Files\Mercurial\hg.exe'
hg_bin = r'C:\Program Files\Mercurial\hg.exe'
if not os.path.exists(hg_bin):
print('could not find Mercurial executable: %s' % hg_bin)
sys.exit(1)
args = [
hg_bin,
b'robustcheckout',
b'--sharebase', store_path,
b'--purge',
'robustcheckout',
'--sharebase', store_path,
'--purge',
]
# Obtain certificate fingerprints. Without this, the checkout will use the fingerprint
# on the system, which is managed some other way (such as puppet)
if fetch_hgfingerprint:
try:
print_line(b'vcs', 'fetching hg.mozilla.org fingerprint from %s\n' %
FINGERPRINT_URL)
res = urllib2.urlopen(FINGERPRINT_URL, timeout=10)
print_line(b'vcs', b'fetching hg.mozilla.org fingerprint from %s\n' %
FINGERPRINT_URL.encode('utf-8'))
res = urllib.request.urlopen(FINGERPRINT_URL, timeout=10)
secret = res.read()
try:
secret = json.loads(secret, encoding='utf-8')
except ValueError:
print_line(b'vcs', 'invalid JSON in hg fingerprint secret')
print_line(b'vcs', b'invalid JSON in hg fingerprint secret')
sys.exit(1)
except (urllib2.URLError, socket.timeout):
print_line(b'vcs', 'Unable to retrieve current hg.mozilla.org fingerprint'
'using the secret service, using fallback instead.')
except (urllib.error.URLError, socket.timeout):
print_line(b'vcs', b'Unable to retrieve current hg.mozilla.org fingerprint'
b'using the secret service, using fallback instead.')
# XXX This fingerprint will not be accurate if running on an old
# revision after the server fingerprint has changed.
secret = {'secret': FALLBACK_FINGERPRINT}
hgmo_fingerprint = secret['secret']['fingerprints'].encode('ascii')
hgmo_fingerprint = secret['secret']['fingerprints']
args.extend([
b'--config', b'hostsecurity.hg.mozilla.org:fingerprints=%s' % hgmo_fingerprint,
'--config', 'hostsecurity.hg.mozilla.org:fingerprints=%s' % hgmo_fingerprint,
])
if base_repo:
args.extend([b'--upstream', base_repo])
args.extend(['--upstream', base_repo])
if sparse_profile:
args.extend([b'--sparseprofile', sparse_profile])
args.extend(['--sparseprofile', sparse_profile])
args.extend([
revision_flag, revision_value,
@ -428,21 +447,22 @@ def vcs_checkout(source_repo, dest, store_path,
# Update the current revision hash and ensure that it is well formed.
revision = subprocess.check_output(
[hg_bin, b'log',
b'--rev', b'.',
b'--template', b'{node}'],
cwd=dest)
[hg_bin, 'log',
'--rev', '.',
'--template', '{node}'],
cwd=dest,
# Triggers text mode on Python 3.
universal_newlines=True)
assert re.match('^[a-f0-9]{40}$', revision)
repo_name = source_repo.split('/')[-1]
print_line(b'vcs', b"TinderboxPrint:<a href={source_repo}/rev/{revision} "
b"title='Built from {repo_name} revision {revision}'>"
b"{revision}</a>\n".format(
revision=revision,
source_repo=source_repo,
repo_name=repo_name,
))
msg = ("TinderboxPrint:<a href={source_repo}/rev/{revision} "
"title='Built from {repo_name} revision {revision}'>"
"{revision}</a>\n".format(revision=revision,
source_repo=source_repo,
repo_name=source_repo.split('/')[-1]))
print_line(b'vcs', msg.encode('utf-8'))
return revision
@ -500,7 +520,7 @@ def main(args):
if os.path.exists("/dev/kvm"):
# Ensure kvm permissions for worker, required for Android x86
st = os.stat("/dev/kvm")
os.chmod("/dev/kvm", st.st_mode | 0666)
os.chmod("/dev/kvm", st.st_mode | 0o666)
# Validate caches.
#
@ -546,7 +566,8 @@ def main(args):
for volume in volumes:
# If a volume is a cache, it was dealt with above.
if volume in caches:
print_line(b'volume', b'volume %s is a cache\n' % volume)
print_line(b'volume', b'volume %s is a cache\n' %
volume.encode('utf-8'))
continue
configure_volume_posix(volume, user, group, running_as_root)
@ -584,7 +605,7 @@ def main(args):
if not path_in_cache_or_volume(checkout):
print_line(b'vcs', b'WARNING: vcs checkout path (%s) not in cache '
b'or volume; performance will likely suffer\n' %
checkout)
checkout.encode('utf-8'))
# Ensure the directory for the source checkout exists.
try:
@ -608,7 +629,7 @@ def main(args):
if not path_in_cache_or_volume(store_path):
print_line(b'vcs', b'WARNING: HG_STORE_PATH (%s) not in cache or '
b'volume; performance will likely suffer\n' %
store_path)
store_path.encode('utf-8'))
try:
os.makedirs(store_path)
@ -629,9 +650,11 @@ def main(args):
# This code is modeled after what `sudo` was observed to do in a Docker
# container. We do not bother calling setrlimit() because containers have
# their own limits.
print_line(b'setup', b'running as %s:%s\n' % (args.user, args.group))
print_line(b'setup', b'running as %s:%s\n' % (
args.user.encode('utf-8'), args.group.encode('utf-8')))
os.setgroups(gids)
os.umask(022)
os.umask(0o22)
os.setresgid(gid, gid, gid)
os.setresuid(uid, uid, uid)
@ -645,7 +668,7 @@ def main(args):
# reasons. Switch to mozilla-unified because robustcheckout works best
# with it.
if base_repo == 'https://hg.mozilla.org/mozilla-central':
base_repo = b'https://hg.mozilla.org/mozilla-unified'
base_repo = 'https://hg.mozilla.org/mozilla-unified'
os.environ['GECKO_HEAD_REV'] = vcs_checkout(
os.environ['GECKO_HEAD_REPOSITORY'],
@ -662,12 +685,12 @@ def main(args):
return 1
if args.tools_checkout:
vcs_checkout(b'https://hg.mozilla.org/build/tools',
vcs_checkout('https://hg.mozilla.org/build/tools',
args.tools_checkout,
os.environ['HG_STORE_PATH'],
# Always check out the latest commit on default branch.
# This is non-deterministic!
branch=b'default')
branch='default')
# Checkout the repository, setting the COMM_HEAD_REV to the current
# revision hash. Revision hashes have priority over symbolic revisions. We
@ -693,8 +716,4 @@ def main(args):
if __name__ == '__main__':
# Unbuffer stdio.
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0)
sys.exit(main(sys.argv[1:]))

View File

@ -40,7 +40,8 @@ class OutputHandler(object):
self.process_output(json.dumps(data))
def process_output(self, line):
LOG.process_output(self.proc.pid, line)
if "error" in line or "warning" in line or "raptor" in line:
LOG.process_output(self.proc.pid, line)
def wait_for_quit(self, timeout=5):
"""Wait timeout seconds for the process to exit. If it hasn't

View File

@ -14,6 +14,13 @@
// to serve out the pages that we want to prototype with. Also
// update the manifest content 'matches' accordingly
// when the browser starts this webext runner will start automatically; we
// want to give the browser some time (ms) to settle before starting tests
var postStartupDelay = 30000;
// have an optional delay (ms) between pageload cycles
var pageloadDelay = 1000;
var browserName;
var ext;
var testName = null;
@ -136,7 +143,7 @@ function getBrowserInfo() {
function testTabCreated(tab) {
testTabID = tab.id;
console.log("opened new empty tab " + testTabID);
nextCycle();
setTimeout(nextCycle, pageloadDelay);
}
async function testTabUpdated(tab) {
@ -144,7 +151,7 @@ async function testTabUpdated(tab) {
// wait for pageload test result from content
await waitForResult();
// move on to next cycle (or test complete)
nextCycle();
setTimeout(nextCycle, pageloadDelay);
}
function waitForResult() {
@ -366,8 +373,12 @@ function runner() {
ext.tabs.onCreated.addListener(testTabCreated);
// timeout alarm listener
ext.alarms.onAlarm.addListener(timeoutAlarmListener);
// create new empty tab, which starts the test
ext.tabs.create({url: "about:blank"});
// create new empty tab, which starts the test; we want to
// wait some time for the browser to settle before beginning
var text = "* pausing " + postStartupDelay / 1000 + " seconds to let browser settle... *";
postToControlServer("status", text);
setTimeout(function() { ext.tabs.create({url: "about:blank"}); }, postStartupDelay);
});
});
}

View File

@ -42,6 +42,7 @@ Structure:
// UNIX epoch.
"defaultSearch": <string>, // Identifier of the default search engine,
// e.g. "yahoo".
"displayVersion": <string>, // Version displayed to user, e.g. 57.0b3 (optional)
"distributionId": <string>, // Distribution identifier (optional)
"campaignId": <string>, // Adjust's campaign identifier (optional)
"created": <string>, // date the ping was created
@ -196,8 +197,12 @@ et al (e.g. "Tue, 01 Feb 2011 14:00:00 GMT").
Version history
---------------
* v10: added ``defaultBrowser`` to know if the user has set Firefox as default browser
* v9: changed ``arch`` to contain device arch rather than the one we built against & ``accessibilityServices``
* v9:
- Apr 2017: changed ``arch`` to contain device arch rather than the one we
built against & ``accessibilityServices``
- Dec 2017: added ``defaultBrowser`` to know if the user has set Firefox as
default browser (Dec 2017)
- May 2018: added (optional) ``displayVersion`` to distinguish Firefox beta versions easily
* v8: added ``flashUsage``
* v7: added ``sessionCount`` & ``sessionDuration`` & ``campaignId``
* v6: added ``searches``

View File

@ -179,14 +179,14 @@ endif
# and places it in dist/bin/res - it should be used when packaging a build.
multilocale.txt: LOCALES?=$(MOZ_CHROME_MULTILOCALE)
multilocale.txt:
$(call py_action,file_generate,$(MOZILLA_DIR)/toolkit/locales/gen_multilocale.py main '$(MULTILOCALE_DIR)/multilocale.txt' $(MDDEPDIR)/multilocale.txt.pp $(ALL_LOCALES))
$(call py_action,file_generate,$(MOZILLA_DIR)/toolkit/locales/gen_multilocale.py main '$(MULTILOCALE_DIR)/multilocale.txt' $(MDDEPDIR)/multilocale.txt.pp '$(MULTILOCALE_DIR)/multilocale.txt' $(ALL_LOCALES))
# This version of the target uses AB_CD to build multilocale.txt and places it
# in the $(XPI_NAME)/res dir - it should be used when repackaging a build.
multilocale.txt-%: LOCALES?=$(AB_CD)
multilocale.txt-%: MULTILOCALE_DIR=$(DIST)/xpi-stage/$(XPI_NAME)/res
multilocale.txt-%:
$(call py_action,file_generate,$(MOZILLA_DIR)/toolkit/locales/gen_multilocale.py main '$(MULTILOCALE_DIR)/multilocale.txt' $(MDDEPDIR)/multilocale.txt.pp $(ALL_LOCALES))
$(call py_action,file_generate,$(MOZILLA_DIR)/toolkit/locales/gen_multilocale.py main '$(MULTILOCALE_DIR)/multilocale.txt' $(MDDEPDIR)/multilocale.txt.pp '$(MULTILOCALE_DIR)/multilocale.txt' $(ALL_LOCALES))
locale-manifest.in: LOCALES?=$(MOZ_CHROME_MULTILOCALE)
locale-manifest.in: $(GLOBAL_DEPS) FORCE

View File

@ -1662,38 +1662,42 @@ StreamMetaJSCustomObject(PSLockRef aLock, SpliceableJSONWriter& aWriter,
aWriter.StringProperty("appBuildID", string.Data());
}
aWriter.StartObjectProperty("extensions");
{
// We should avoid collecting extension metadata for profiler while XPCOM is
// shutting down since it cannot create a new ExtensionPolicyService.
if (!gXPCOMShuttingDown) {
aWriter.StartObjectProperty("extensions");
{
JSONSchemaWriter schema(aWriter);
schema.WriteField("id");
schema.WriteField("name");
schema.WriteField("baseURL");
}
aWriter.StartArrayProperty("data");
{
nsTArray<RefPtr<WebExtensionPolicy>> exts;
ExtensionPolicyService::GetSingleton().GetAll(exts);
for (auto& ext : exts) {
aWriter.StartArrayElement(JSONWriter::SingleLineStyle);
nsAutoString id;
ext->GetId(id);
aWriter.StringElement(NS_ConvertUTF16toUTF8(id).get());
aWriter.StringElement(NS_ConvertUTF16toUTF8(ext->Name()).get());
auto url = ext->GetURL(NS_LITERAL_STRING(""));
if (url.isOk()) {
aWriter.StringElement(NS_ConvertUTF16toUTF8(url.unwrap()).get());
}
aWriter.EndArray();
{
JSONSchemaWriter schema(aWriter);
schema.WriteField("id");
schema.WriteField("name");
schema.WriteField("baseURL");
}
aWriter.StartArrayProperty("data");
{
nsTArray<RefPtr<WebExtensionPolicy>> exts;
ExtensionPolicyService::GetSingleton().GetAll(exts);
for (auto& ext : exts) {
aWriter.StartArrayElement(JSONWriter::SingleLineStyle);
nsAutoString id;
ext->GetId(id);
aWriter.StringElement(NS_ConvertUTF16toUTF8(id).get());
aWriter.StringElement(NS_ConvertUTF16toUTF8(ext->Name()).get());
auto url = ext->GetURL(NS_LITERAL_STRING(""));
if (url.isOk()) {
aWriter.StringElement(NS_ConvertUTF16toUTF8(url.unwrap()).get());
}
aWriter.EndArray();
}
}
aWriter.EndArray();
}
aWriter.EndArray();
}
aWriter.EndObject();
}