Merge mozilla-central to autoland. a=merge CLOSED TREE

This commit is contained in:
Ciure Andrei 2018-07-15 12:54:42 +03:00
Родитель aaaba0f931 14976cd6b3
Коммит 35a115b001
104 изменённых файлов: 4201 добавлений и 1029 удалений

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

@ -89,6 +89,9 @@ add_task(async function startup() {
min: 45,
max: 75,
},
"network.loadinfo.skip_type_assertion": {
max: 650,
},
"extensions.getAddons.cache.enabled": {
min: 9,
max: 55,
@ -138,8 +141,11 @@ add_task(async function open_10_tabs() {
"browser.startup.record": {
max: 20,
},
"dom.max_chrome_script_run_time": {
max: 20,
"browser.tabs.remote.logSwitchTiming": {
max: 25,
},
"network.loadinfo.skip_type_assertion": {
max: 70,
},
"toolkit.cosmeticAnimations.enabled": {
min: 5,
@ -170,6 +176,9 @@ add_task(async function navigate_around() {
min: 100,
max: 110,
},
"network.loadinfo.skip_type_assertion": {
max: 130,
},
"security.insecure_connection_icon.pbmode.enabled": {
min: 20,
max: 30,

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

@ -282,6 +282,13 @@ BasePrincipal::GetIsSystemPrincipal(bool* aResult)
return NS_OK;
}
NS_IMETHODIMP
BasePrincipal::GetIsAddonOrExpandedAddonPrincipal(bool* aResult)
{
*aResult = AddonPolicy() || ContentScriptAddonPolicy();
return NS_OK;
}
NS_IMETHODIMP
BasePrincipal::GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal)
{

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

@ -79,6 +79,7 @@ public:
NS_IMETHOD GetIsCodebasePrincipal(bool* aResult) override;
NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override;
NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override;
NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override;
NS_IMETHOD GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) final;
NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
NS_IMETHOD GetAppId(uint32_t* aAppId) final;

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

@ -320,6 +320,12 @@ interface nsIPrincipal : nsISerializable
* Returns true iff this is the system principal.
*/
[infallible] readonly attribute boolean isSystemPrincipal;
/**
* Returns true iff the principal is either an addon principal or
* an expanded principal, which contains at least one addon principal.
*/
[infallible] readonly attribute boolean isAddonOrExpandedAddonPrincipal;
};
/**

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

@ -5,7 +5,7 @@ code, and optionally help with indentation.
# Upgrade
Currently used version is 5.38.0. To upgrade: download a new version of
Currently used version is 5.39.0. To upgrade: download a new version of
CodeMirror from the project's page [1] and replace all JavaScript and
CSS files inside the codemirror directory [2].

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@ -6577,8 +6577,6 @@ function registerGlobalHandlers() {
// Called when the window resizes
function onResize(cm) {
var d = cm.display
if (d.lastWrapHeight == d.wrapper.clientHeight && d.lastWrapWidth == d.wrapper.clientWidth)
{ return }
// Might be a text scaling operation, clear size caches.
d.cachedCharWidth = d.cachedTextHeight = d.cachedPaddingH = null
d.scrollbarsClipped = false
@ -9678,7 +9676,7 @@ CodeMirror.fromTextArea = fromTextArea
addLegacyProps(CodeMirror)
CodeMirror.version = "5.38.0"
CodeMirror.version = "5.39.0"
return CodeMirror;

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

@ -344,7 +344,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp);
};
return exp;

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

@ -163,8 +163,9 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
stream.next();
}
return style;
};
}
}
function doctype(depth) {
return function(stream, state) {
var ch;

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

@ -1218,7 +1218,7 @@ ChromeTooltipListener::MouseMove(Event* aMouseEvent)
if (!mShowingTooltip && !mTooltipShownOnce) {
nsIEventTarget* target = nullptr;
nsCOMPtr<EventTarget> eventTarget = aMouseEvent->GetTarget();
nsCOMPtr<EventTarget> eventTarget = aMouseEvent->GetComposedTarget();
if (eventTarget) {
mPossibleTooltipNode = do_QueryInterface(eventTarget);
nsCOMPtr<nsIGlobalObject> global(eventTarget->GetOwnerGlobal());
@ -1317,6 +1317,12 @@ ChromeTooltipListener::sTooltipCallback(nsITimer* aTimer,
{
auto self = static_cast<ChromeTooltipListener*>(aChromeTooltipListener);
if (self && self->mPossibleTooltipNode) {
if (!self->mPossibleTooltipNode->IsInComposedDoc()) {
// release tooltip target if there is one, NO MATTER WHAT
self->mPossibleTooltipNode = nullptr;
return;
}
// The actual coordinates we want to put the tooltip at are relative to the
// toplevel docshell of our mWebBrowser. We know what the screen
// coordinates of the mouse event were, which means we just need the screen

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

@ -2612,6 +2612,21 @@ nsDocument::IsShadowDOMEnabled(JSContext* aCx, JSObject* aGlobal)
return doc->IsShadowDOMEnabled();
}
// static
bool
nsDocument::IsShadowDOMEnabledAndCallerIsChromeOrAddon(JSContext* aCx,
JSObject* aObject)
{
if (IsShadowDOMEnabled(aCx, aObject)) {
nsIPrincipal* principal = nsContentUtils::SubjectPrincipal(aCx);
return principal &&
(nsContentUtils::IsSystemPrincipal(principal) ||
principal->GetIsAddonOrExpandedAddonPrincipal());
}
return false;
}
bool
nsDocument::IsShadowDOMEnabled(const nsINode* aNode)
{

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

@ -196,6 +196,8 @@ public:
// Check whether shadow DOM is enabled for aGlobal.
static bool IsShadowDOMEnabled(JSContext* aCx, JSObject* aGlobal);
// Check whether shadow DOM is enabled for the document this node belongs to.
// Same as above, but also checks that the caller is either chrome or some addon.
static bool IsShadowDOMEnabledAndCallerIsChromeOrAddon(JSContext* aCx, JSObject* aObject);
static bool IsShadowDOMEnabled(const nsINode* aNode);
public:

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

@ -111,7 +111,6 @@
#include "nsIDocShell.h"
#include "nsIDocument.h"
#include "Crypto.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsDOMString.h"
#include "nsIEmbeddingSiteWindow.h"
#include "nsThreadUtils.h"
@ -3134,7 +3133,7 @@ nsGlobalWindowInner::DeviceSensorsEnabled(JSContext* aCx, JSObject* aObj)
return Preferences::GetBool("device.sensors.enabled");
}
nsIDOMOfflineResourceList*
nsDOMOfflineResourceList*
nsGlobalWindowInner::GetApplicationCache(ErrorResult& aError)
{
if (!mApplicationCache) {
@ -3165,14 +3164,10 @@ nsGlobalWindowInner::GetApplicationCache(ErrorResult& aError)
return mApplicationCache;
}
already_AddRefed<nsIDOMOfflineResourceList>
nsDOMOfflineResourceList*
nsGlobalWindowInner::GetApplicationCache()
{
ErrorResult dummy;
nsCOMPtr<nsIDOMOfflineResourceList> applicationCache =
GetApplicationCache(dummy);
dummy.SuppressException();
return applicationCache.forget();
return GetApplicationCache(IgnoreErrors());
}
Crypto*
@ -5886,8 +5881,7 @@ nsGlobalWindowInner::Observe(nsISupports* aSubject, const char* aTopic,
// Instantiate the application object now. It observes update belonging to
// this window's document and correctly updates the applicationCache object
// state.
nsCOMPtr<nsIDOMOfflineResourceList> applicationCache = GetApplicationCache();
nsCOMPtr<nsIObserver> observer = do_QueryInterface(applicationCache);
nsCOMPtr<nsIObserver> observer = GetApplicationCache();
if (observer)
observer->Observe(aSubject, aTopic, aData);

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

@ -64,7 +64,7 @@ class nsIBaseWindow;
class nsIContent;
class nsICSSDeclaration;
class nsIDocShellTreeOwner;
class nsIDOMOfflineResourceList;
class nsDOMOfflineResourceList;
class nsIScrollableFrame;
class nsIControllers;
class nsIJSID;
@ -684,8 +684,8 @@ public:
const nsAString& aName,
const nsAString& aOptions,
mozilla::ErrorResult& aError);
nsIDOMOfflineResourceList* GetApplicationCache(mozilla::ErrorResult& aError);
already_AddRefed<nsIDOMOfflineResourceList> GetApplicationCache() override;
nsDOMOfflineResourceList* GetApplicationCache(mozilla::ErrorResult& aError);
nsDOMOfflineResourceList* GetApplicationCache() override;
#if defined(MOZ_WIDGET_ANDROID)
int16_t Orientation(mozilla::dom::CallerType aCallerType) const;
@ -1420,7 +1420,7 @@ protected:
nsCOMPtr<nsIURI> mLastOpenedURI;
#endif
nsCOMPtr<nsIDOMOfflineResourceList> mApplicationCache;
RefPtr<nsDOMOfflineResourceList> mApplicationCache;
using XBLPrototypeHandlerTable = nsJSThingHashtable<nsPtrHashKey<nsXBLPrototypeHandler>, JSObject*>;
mozilla::UniquePtr<XBLPrototypeHandlerTable> mCachedXBLPrototypeHandlers;

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

@ -34,7 +34,6 @@
#if defined(MOZ_WIDGET_ANDROID)
#include "mozilla/dom/WindowOrientationObserver.h"
#endif
#include "nsDOMOfflineResourceList.h"
#include "nsError.h"
#include "nsIIdleService.h"
#include "nsISizeOfEventTarget.h"
@ -108,7 +107,6 @@
#include "nsIDocShell.h"
#include "nsIDocument.h"
#include "Crypto.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsDOMString.h"
#include "nsIEmbeddingSiteWindow.h"
#include "nsThreadUtils.h"

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

@ -61,7 +61,6 @@ class nsIBaseWindow;
class nsIContent;
class nsICSSDeclaration;
class nsIDocShellTreeOwner;
class nsIDOMOfflineResourceList;
class nsIScrollableFrame;
class nsIControllers;
class nsIJSID;

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

@ -22,6 +22,7 @@
#define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
#define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
class nsDOMOfflineResourceList;
class nsDOMWindowList;
class nsGlobalWindowInner;
class nsGlobalWindowOuter;
@ -614,7 +615,7 @@ public:
virtual mozilla::dom::Element* GetFrameElement() = 0;
virtual already_AddRefed<nsIDOMOfflineResourceList> GetApplicationCache() = 0;
virtual nsDOMOfflineResourceList* GetApplicationCache() = 0;
virtual bool GetFullScreen() = 0;

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

@ -1817,7 +1817,6 @@ def addExternalIface(iface, nativeType=None, headerFile=None,
domInterface['notflattened'] = notflattened
DOMInterfaces[iface] = domInterface
addExternalIface('ApplicationCache', nativeType='nsIDOMOfflineResourceList')
addExternalIface('Cookie', nativeType='nsICookie2',
headerFile='nsICookie2.h', notflattened=True)
addExternalIface('HitRegionOptions', nativeType='nsISupports')

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

@ -6,7 +6,6 @@
#include "domstubs.idl"
interface nsIControllers;
interface nsIDOMOfflineResourceList;
interface nsIPrompt;
interface nsIVariant;

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

@ -1,15 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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 = ("Core", "DOM")
XPIDL_SOURCES += [
'nsIDOMOfflineResourceList.idl',
]
XPIDL_MODULE = 'dom_offline'

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

@ -1,91 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "domstubs.idl"
[shim(OfflineResourceList), uuid(6044702d-e4a9-420c-b711-558b7d6a3b9f)]
interface nsIDOMOfflineResourceList : nsISupports
{
/**
* Get the list of dynamically-managed entries.
*/
readonly attribute nsISupports mozItems;
/**
* Check that an entry exists in the list of dynamically-managed entries.
*
* @param uri
* The resource to check.
*/
boolean mozHasItem(in DOMString uri);
/**
* Get the number of dynamically-managed entries.
* @status DEPRECATED
* Clients should use the "items" attribute.
*/
readonly attribute unsigned long mozLength;
/**
* Get the URI of a dynamically-managed entry.
* @status DEPRECATED
* Clients should use the "items" attribute.
*/
DOMString mozItem(in unsigned long index);
/**
* Add an item to the list of dynamically-managed entries. The resource
* will be fetched into the application cache.
*
* @param uri
* The resource to add.
*/
void mozAdd(in DOMString uri);
/**
* Remove an item from the list of dynamically-managed entries. If this
* was the last reference to a URI in the application cache, the cache
* entry will be removed.
*
* @param uri
* The resource to remove.
*/
void mozRemove(in DOMString uri);
/**
* State of the application cache this object is associated with.
*/
/* This object is not associated with an application cache. */
const unsigned short UNCACHED = 0;
/* The application cache is not being updated. */
const unsigned short IDLE = 1;
/* The manifest is being fetched and checked for updates */
const unsigned short CHECKING = 2;
/* Resources are being downloaded to be added to the cache */
const unsigned short DOWNLOADING = 3;
/* There is a new version of the application cache available */
const unsigned short UPDATEREADY = 4;
/* The application cache group is now obsolete. */
const unsigned short OBSOLETE = 5;
readonly attribute unsigned short status;
/**
* Begin the application update process on the associated application cache.
*/
void update();
/**
* Swap in the newest version of the application cache, or disassociate
* from the cache if the cache group is obsolete.
*/
void swapCache();
};

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

@ -2026,6 +2026,9 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
// Prefs information is passed via anonymous shared memory to avoid bloating
// the command line.
size_t prefMapSize;
auto prefMapHandle = Preferences::EnsureSnapshot(&prefMapSize).ClonePlatformHandle();
// Serialize the early prefs.
nsAutoCStringN<1024> prefs;
Preferences::SerializePreferences(prefs);
@ -2046,14 +2049,22 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
// Copy the serialized prefs into the shared memory.
memcpy(static_cast<char*>(shm.memory()), prefs.get(), prefs.Length());
// Formats a pointer or pointer-sized-integer as a string suitable for passing
// in an arguments list.
auto formatPtrArg = [] (auto arg) {
return nsPrintfCString("%zu", uintptr_t(arg));
};
#if defined(XP_WIN)
// Record the handle as to-be-shared, and pass it via a command flag. This
// works because Windows handles are system-wide.
HANDLE prefsHandle = shm.handle();
mSubprocess->AddHandleToShare(prefsHandle);
mSubprocess->AddHandleToShare(prefMapHandle.get());
extraArgs.push_back("-prefsHandle");
extraArgs.push_back(
nsPrintfCString("%zu", reinterpret_cast<uintptr_t>(prefsHandle)).get());
extraArgs.push_back(formatPtrArg(prefsHandle).get());
extraArgs.push_back("-prefMapHandle");
extraArgs.push_back(formatPtrArg(prefMapHandle.get()).get());
#else
// In contrast, Unix fds are per-process. So remap the fd to a fixed one that
// will be used in the child.
@ -2063,11 +2074,15 @@ ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PR
// and the fixed fd isn't used. However, we still need to mark it for
// remapping so it doesn't get closed in the child.
mSubprocess->AddFdToRemap(shm.handle().fd, kPrefsFileDescriptor);
mSubprocess->AddFdToRemap(prefMapHandle.get(), kPrefMapFileDescriptor);
#endif
// Pass the length via a command flag.
// Pass the lengths via command line flags.
extraArgs.push_back("-prefsLen");
extraArgs.push_back(nsPrintfCString("%zu", uintptr_t(prefs.Length())).get());
extraArgs.push_back(formatPtrArg(prefs.Length()).get());
extraArgs.push_back("-prefMapSize");
extraArgs.push_back(formatPtrArg(prefMapSize).get());
// Scheduler prefs need to be handled differently because the scheduler needs
// to start up in the content process before the normal preferences service.
@ -2906,9 +2921,12 @@ ContentParent::Observe(nsISupports* aSubject,
BLACKLIST_ENTRY(u"app.update.lastUpdateTime."),
BLACKLIST_ENTRY(u"datareporting.policy."),
BLACKLIST_ENTRY(u"browser.safebrowsing.provider."),
BLACKLIST_ENTRY(u"browser.shell."),
BLACKLIST_ENTRY(u"browser.slowstartup."),
BLACKLIST_ENTRY(u"extensions.getAddons.cache."),
BLACKLIST_ENTRY(u"media.gmp-manager."),
BLACKLIST_ENTRY(u"media.gmp-gmpopenh264."),
BLACKLIST_ENTRY(u"privacy.sanitize."),
};
#undef BLACKLIST_ENTRY

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

@ -83,12 +83,19 @@ SetUpSandboxEnvironment()
#ifdef ANDROID
static int gPrefsFd = -1;
static int gPrefMapFd = -1;
void
SetPrefsFd(int aFd)
{
gPrefsFd = aFd;
}
void
SetPrefMapFd(int aFd)
{
gPrefMapFd = aFd;
}
#endif
bool
@ -97,13 +104,29 @@ ContentProcess::Init(int aArgc, char* aArgv[])
Maybe<uint64_t> childID;
Maybe<bool> isForBrowser;
Maybe<base::SharedMemoryHandle> prefsHandle;
Maybe<FileDescriptor> prefMapHandle;
Maybe<size_t> prefsLen;
Maybe<size_t> prefMapSize;
Maybe<const char*> schedulerPrefs;
Maybe<const char*> parentBuildID;
#if defined(XP_MACOSX) && defined(MOZ_CONTENT_SANDBOX)
nsCOMPtr<nsIFile> profileDir;
#endif
// Parses an arg containing a pointer-sized-integer.
auto parseUIntPtrArg = [] (char*& aArg) {
// ContentParent uses %zu to print a word-sized unsigned integer. So
// even though strtoull() returns a long long int, it will fit in a
// uintptr_t.
return uintptr_t(strtoull(aArg, &aArg, 10));
};
#ifdef XP_WIN
auto parseHandleArg = [&] (char*& aArg) {
return HANDLE(parseUIntPtrArg(aArg));
};
#endif
for (int i = 1; i < aArgc; i++) {
if (!aArgv[i]) {
continue;
@ -137,11 +160,22 @@ ContentProcess::Init(int aArgc, char* aArgv[])
if (++i == aArgc) {
return false;
}
// ContentParent uses %zu to print a word-sized unsigned integer. So
// even though strtoull() returns a long long int, it will fit in a
// uintptr_t.
char* str = aArgv[i];
prefsHandle = Some(reinterpret_cast<HANDLE>(strtoull(str, &str, 10)));
prefsHandle = Some(parseHandleArg(str));
if (str[0] != '\0') {
return false;
}
} else if (strcmp(aArgv[i], "-prefMapHandle") == 0) {
if (++i == aArgc) {
return false;
}
char* str = aArgv[i];
// The FileDescriptor constructor will clone this handle when constructed,
// so store it in a UniquePlatformHandle to make sure the original gets
// closed.
FileDescriptor::UniquePlatformHandle handle(parseHandleArg(str));
prefMapHandle.emplace(handle.get());
if (str[0] != '\0') {
return false;
}
@ -151,11 +185,18 @@ ContentProcess::Init(int aArgc, char* aArgv[])
if (++i == aArgc) {
return false;
}
// ContentParent uses %zu to print a word-sized unsigned integer. So
// even though strtoull() returns a long long int, it will fit in a
// uintptr_t.
char* str = aArgv[i];
prefsLen = Some(strtoull(str, &str, 10));
prefsLen = Some(parseUIntPtrArg(str));
if (str[0] != '\0') {
return false;
}
} else if (strcmp(aArgv[i], "-prefMapSize") == 0) {
if (++i == aArgc) {
return false;
}
char* str = aArgv[i];
prefMapSize = Some(parseUIntPtrArg(str));
if (str[0] != '\0') {
return false;
}
@ -194,9 +235,18 @@ ContentProcess::Init(int aArgc, char* aArgv[])
// Android is different; get the FD via gPrefsFd instead of a fixed fd.
MOZ_RELEASE_ASSERT(gPrefsFd != -1);
prefsHandle = Some(base::FileDescriptor(gPrefsFd, /* auto_close */ true));
FileDescriptor::UniquePlatformHandle handle(gPrefMapFd);
prefMapHandle.emplace(handle.get());
#elif XP_UNIX
prefsHandle = Some(base::FileDescriptor(kPrefsFileDescriptor,
/* auto_close */ true));
// The FileDescriptor constructor will clone this handle when constructed,
// so store it in a UniquePlatformHandle to make sure the original gets
// closed.
FileDescriptor::UniquePlatformHandle handle(kPrefMapFileDescriptor);
prefMapHandle.emplace(handle.get());
#endif
// Did we find all the mandatory flags?
@ -204,11 +254,17 @@ ContentProcess::Init(int aArgc, char* aArgv[])
isForBrowser.isNothing() ||
prefsHandle.isNothing() ||
prefsLen.isNothing() ||
prefMapHandle.isNothing() ||
prefMapSize.isNothing() ||
schedulerPrefs.isNothing() ||
parentBuildID.isNothing()) {
return false;
}
// Init the shared-memory base preference mapping first, so that only changed
// preferences wind up in heap memory.
Preferences::InitSnapshot(prefMapHandle.ref(), *prefMapSize);
// Set up early prefs from the shared memory.
base::SharedMemory shm;
if (!shm.SetHandle(*prefsHandle, /* read_only */ true)) {

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

@ -50,8 +50,10 @@ private:
};
#ifdef ANDROID
// Android doesn't use -prefsHandle, it gets that FD another way.
// Android doesn't use -prefsHandle or -prefMapHandle. It gets those FDs
// another way.
void SetPrefsFd(int aFd);
void SetPrefMapFd(int aFd);
#endif
} // namespace dom

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

@ -7,7 +7,7 @@
#ifndef dom_ipc_MemMapSnapshot_h
#define dom_ipc_MemMapSnapshot_h
#include "AutoMemMap.h"
#include "mozilla/AutoMemMap.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/RangedPtr.h"

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

@ -90,8 +90,7 @@ SharedStringMap::Find(const nsCString& aKey, size_t* aIndex)
void
SharedStringMapBuilder::Add(const nsCString& aKey, const nsString& aValue)
{
mEntries.Put(aKey, {{mKeyTable.Add(aKey), aKey.Length()},
{mValueTable.Add(aValue), aValue.Length()}});
mEntries.Put(aKey, {mKeyTable.Add(aKey), mValueTable.Add(aValue)});
}
Result<Ok, nsresult>

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

@ -9,7 +9,7 @@
#include "mozilla/AutoMemMap.h"
#include "mozilla/Result.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/dom/ipc/StringTable.h"
#include "nsDataHashtable.h"
namespace mozilla {
@ -75,25 +75,15 @@ public:
size_t mValueStringsSize;
};
/**
* Contains the character offset and character length of an entry in a string
* table. This may be used for either 8-bit or 16-bit strings, and is required
* to retrieve an entry from a string table.
*/
struct StringEntry {
uint32_t mOffset;
uint32_t mLength;
};
/**
* Describes a value in the string map, as offsets into the key and value
* string tables.
*/
struct Entry {
// The offset and size of the entry's UTF-8 key in the key string table.
StringEntry mKey;
StringTableEntry mKey;
// The offset and size of the entry's UTF-16 value in the value string table.
StringEntry mValue;
StringTableEntry mValue;
};
NS_INLINE_DECL_REFCOUNTING(SharedStringMap)
@ -174,35 +164,6 @@ protected:
~SharedStringMap() = default;
private:
template <typename StringType>
class StringTable
{
using ElemType = decltype(DeclVal<StringType>()[0]);
public:
MOZ_IMPLICIT StringTable(const RangedPtr<uint8_t>& aBuffer)
: mBuffer(aBuffer.ReinterpretCast<ElemType>())
{
MOZ_ASSERT(uintptr_t(aBuffer.get()) % alignof(ElemType) == 0,
"Got misalinged buffer");
}
StringType Get(const StringEntry& aEntry) const
{
StringType res;
res.AssignLiteral(GetBare(aEntry), aEntry.mLength);
return res;
}
const ElemType* GetBare(const StringEntry& aEntry) const
{
return &mBuffer[aEntry.mOffset];
}
private:
RangedPtr<ElemType> mBuffer;
};
// Type-safe getters for values in the shared memory region:
const Header& GetHeader() const
@ -262,52 +223,6 @@ public:
Result<Ok, nsresult> Finalize(loader::AutoMemMap& aMap);
private:
template <typename KeyType, typename StringType>
class StringTableBuilder
{
public:
using ElemType = typename StringType::char_type;
uint32_t Add(const StringType& aKey)
{
auto entry = mEntries.LookupForAdd(aKey).OrInsert([&] () {
Entry newEntry { mSize, aKey };
mSize += aKey.Length() + 1;
return newEntry;
});
return entry.mOffset;
}
void Write(const RangedPtr<uint8_t>& aBuffer)
{
auto buffer = aBuffer.ReinterpretCast<ElemType>();
for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) {
auto& entry = iter.Data();
memcpy(&buffer[entry.mOffset], entry.mValue.BeginReading(),
sizeof(ElemType) * (entry.mValue.Length() + 1));
}
}
uint32_t Count() const { return mEntries.Count(); }
uint32_t Size() const { return mSize * sizeof(ElemType); }
void Clear() { mEntries.Clear(); }
private:
struct Entry
{
uint32_t mOffset;
StringType mValue;
};
nsDataHashtable<KeyType, Entry> mEntries;
uint32_t mSize = 0;
};
using Entry = SharedStringMap::Entry;
StringTableBuilder<nsCStringHashKey, nsCString> mKeyTable;

127
dom/ipc/StringTable.h Normal file
Просмотреть файл

@ -0,0 +1,127 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef dom_ipc_StringTable_h
#define dom_ipc_StringTable_h
#include "mozilla/RangedPtr.h"
#include "nsDataHashtable.h"
/**
* This file contains helper classes for creating and accessing compact string
* tables, which can be used as the building blocks of shared memory databases.
* Each string table a de-duplicated set of strings which can be referenced
* using their character offsets within a data block and their lengths. The
* string tables, once created, cannot be modified, and are primarily useful in
* read-only shared memory or memory mapped files.
*/
namespace mozilla {
namespace dom {
namespace ipc {
/**
* Contains the character offset and character length of an entry in a string
* table. This may be used for either 8-bit or 16-bit strings, and is required
* to retrieve an entry from a string table.
*/
struct StringTableEntry
{
uint32_t mOffset;
uint32_t mLength;
// Ignore mLength. It must be the same for any two strings with the same
// offset.
uint32_t Hash() const { return mOffset; }
bool operator==(const StringTableEntry& aOther) const
{
return mOffset == aOther.mOffset;
}
};
template <typename StringType>
class StringTable
{
using ElemType = typename StringType::char_type;
public:
MOZ_IMPLICIT StringTable(const RangedPtr<uint8_t>& aBuffer)
: mBuffer(aBuffer.ReinterpretCast<ElemType>())
{
MOZ_ASSERT(uintptr_t(aBuffer.get()) % alignof(ElemType) == 0,
"Got misalinged buffer");
}
StringType Get(const StringTableEntry& aEntry) const
{
StringType res;
res.AssignLiteral(GetBare(aEntry), aEntry.mLength);
return res;
}
const ElemType* GetBare(const StringTableEntry& aEntry) const
{
return &mBuffer[aEntry.mOffset];
}
private:
RangedPtr<ElemType> mBuffer;
};
template <typename KeyType, typename StringType>
class StringTableBuilder
{
public:
using ElemType = typename StringType::char_type;
StringTableEntry Add(const StringType& aKey)
{
const auto& entry = mEntries.LookupForAdd(aKey).OrInsert([&] () {
Entry newEntry { mSize, aKey };
mSize += aKey.Length() + 1;
return newEntry;
});
return { entry.mOffset, aKey.Length() };
}
void Write(const RangedPtr<uint8_t>& aBuffer)
{
auto buffer = aBuffer.ReinterpretCast<ElemType>();
for (auto iter = mEntries.Iter(); !iter.Done(); iter.Next()) {
auto& entry = iter.Data();
memcpy(&buffer[entry.mOffset], entry.mValue.BeginReading(),
sizeof(ElemType) * (entry.mValue.Length() + 1));
}
}
uint32_t Count() const { return mEntries.Count(); }
uint32_t Size() const { return mSize * sizeof(ElemType); }
void Clear() { mEntries.Clear(); }
static constexpr size_t Alignment() { return alignof(ElemType); }
private:
struct Entry
{
uint32_t mOffset;
StringType mValue;
};
nsDataHashtable<KeyType, Entry> mEntries;
uint32_t mSize = 0;
};
} // namespace ipc
} // namespace dom
} // namespace mozilla
#endif

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

@ -15,9 +15,11 @@ XPIDL_MODULE = 'dom'
EXPORTS.mozilla.dom.ipc += [
'IdType.h',
'MemMapSnapshot.h',
'SharedMap.h',
'SharedMapChangeEvent.h',
'SharedStringMap.h',
'StringTable.h',
'StructuredCloneData.h',
]

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

@ -21,7 +21,6 @@ interfaces = [
'xul',
'security',
'storage',
'offline',
'geolocation',
'notification',
'push',

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

@ -62,7 +62,6 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(nsDOMOfflineResourceList,
mPendingEvents)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMOfflineResourceList)
NS_INTERFACE_MAP_ENTRY(nsIDOMOfflineResourceList)
NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdateObserver)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
@ -81,7 +80,7 @@ nsDOMOfflineResourceList::nsDOMOfflineResourceList(nsIURI *aManifestURI,
, mDocumentURI(aDocumentURI)
, mLoadingPrincipal(aLoadingPrincipal)
, mExposeCacheUpdateStatus(true)
, mStatus(nsIDOMOfflineResourceList::IDLE)
, mStatus(OfflineResourceList_Binding::IDLE)
, mCachedKeys(nullptr)
, mCachedKeysCount(0)
{
@ -173,10 +172,6 @@ nsDOMOfflineResourceList::Disconnect()
}
}
//
// nsDOMOfflineResourceList::nsIDOMOfflineResourceList
//
already_AddRefed<DOMStringList>
nsDOMOfflineResourceList::GetMozItems(ErrorResult& aRv)
{
@ -216,177 +211,250 @@ nsDOMOfflineResourceList::GetMozItems(ErrorResult& aRv)
return items.forget();
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetMozItems(nsISupports** aItems)
bool
nsDOMOfflineResourceList::MozHasItem(const nsAString& aURI, ErrorResult& aRv)
{
ErrorResult rv;
RefPtr<DOMStringList> items = GetMozItems(rv);
items.forget(aItems);
return rv.StealNSResult();
}
NS_IMETHODIMP
nsDOMOfflineResourceList::MozHasItem(const nsAString& aURI, bool* aExists)
{
if (IS_CHILD_PROCESS())
return NS_ERROR_NOT_IMPLEMENTED;
if (IS_CHILD_PROCESS()) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return false;
}
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return false;
}
nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache();
if (!appCache) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return false;
}
nsAutoCString key;
rv = GetCacheKey(aURI, key);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return false;
}
uint32_t types;
rv = appCache->GetTypes(key, &types);
if (rv == NS_ERROR_CACHE_KEY_NOT_FOUND) {
*aExists = false;
return NS_OK;
return false;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return false;
}
NS_ENSURE_SUCCESS(rv, rv);
*aExists = ((types & nsIApplicationCache::ITEM_DYNAMIC) != 0);
return NS_OK;
return types & nsIApplicationCache::ITEM_DYNAMIC;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetMozLength(uint32_t *aLength)
uint32_t
nsDOMOfflineResourceList::GetMozLength(ErrorResult& aRv)
{
if (IS_CHILD_PROCESS())
return NS_ERROR_NOT_IMPLEMENTED;
if (IS_CHILD_PROCESS()) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return 0;
}
if (!mManifestURI) {
*aLength = 0;
return NS_OK;
return 0;
}
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return 0;
}
rv = CacheKeys();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return 0;
}
*aLength = mCachedKeysCount;
return NS_OK;
return mCachedKeysCount;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::MozItem(uint32_t aIndex, nsAString& aURI)
void
nsDOMOfflineResourceList::MozItem(uint32_t aIndex, nsAString& aURI,
ErrorResult& aRv)
{
if (IS_CHILD_PROCESS())
return NS_ERROR_NOT_IMPLEMENTED;
bool found;
IndexedGetter(aIndex, found, aURI, aRv);
if (!aRv.Failed() && !found) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
}
}
void
nsDOMOfflineResourceList::IndexedGetter(uint32_t aIndex, bool& aFound,
nsAString& aURI, ErrorResult& aRv)
{
if (IS_CHILD_PROCESS()) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return;
}
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
SetDOMStringToNull(aURI);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
rv = CacheKeys();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
if (aIndex >= mCachedKeysCount)
return NS_ERROR_NOT_AVAILABLE;
if (aIndex >= mCachedKeysCount) {
aFound = false;
return;
}
aFound = true;
CopyUTF8toUTF16(mCachedKeys[aIndex], aURI);
return NS_OK;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::MozAdd(const nsAString& aURI)
void
nsDOMOfflineResourceList::MozAdd(const nsAString& aURI, ErrorResult& aRv)
{
if (IS_CHILD_PROCESS())
return NS_ERROR_NOT_IMPLEMENTED;
if (IS_CHILD_PROCESS()) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return;
}
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache();
if (!appCache) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (aURI.Length() > MAX_URI_LENGTH) return NS_ERROR_DOM_BAD_URI;
if (aURI.Length() > MAX_URI_LENGTH) {
aRv.Throw(NS_ERROR_DOM_BAD_URI);
return;
}
// this will fail if the URI is not absolute
nsCOMPtr<nsIURI> requestedURI;
rv = NS_NewURI(getter_AddRefs(requestedURI), aURI);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
nsAutoCString scheme;
rv = requestedURI->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
bool match;
rv = mManifestURI->SchemeIs(scheme.get(), &match);
NS_ENSURE_SUCCESS(rv, rv);
if (!match) {
return NS_ERROR_DOM_SECURITY_ERR;
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
uint32_t length;
rv = GetMozLength(&length);
NS_ENSURE_SUCCESS(rv, rv);
if (!match) {
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
uint32_t length = GetMozLength(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
uint32_t maxEntries =
Preferences::GetUint(kMaxEntriesPref, DEFAULT_MAX_ENTRIES);
if (length > maxEntries) return NS_ERROR_NOT_AVAILABLE;
if (length > maxEntries) {
aRv.Throw(NS_ERROR_NOT_AVAILABLE);
return;
}
ClearCachedKeys();
nsCOMPtr<nsIOfflineCacheUpdate> update =
do_CreateInstance(NS_OFFLINECACHEUPDATE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
nsAutoCString clientID;
rv = appCache->GetClientID(clientID);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
rv = update->InitPartial(mManifestURI, clientID,
mDocumentURI, mLoadingPrincipal);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
rv = update->AddDynamicURI(requestedURI);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
rv = update->Schedule();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
}
NS_IMETHODIMP
nsDOMOfflineResourceList::MozRemove(const nsAString& aURI)
void
nsDOMOfflineResourceList::MozRemove(const nsAString& aURI, ErrorResult& aRv)
{
if (IS_CHILD_PROCESS())
return NS_ERROR_NOT_IMPLEMENTED;
if (IS_CHILD_PROCESS()) {
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
return;
}
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache();
if (!appCache) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
nsAutoCString key;
rv = GetCacheKey(aURI, key);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
ClearCachedKeys();
@ -397,13 +465,14 @@ nsDOMOfflineResourceList::MozRemove(const nsAString& aURI)
// finished. Need to bring this issue up.
rv = appCache->UnmarkEntry(key, nsIApplicationCache::ITEM_DYNAMIC);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
}
NS_IMETHODIMP
nsDOMOfflineResourceList::GetStatus(uint16_t *aStatus)
uint16_t
nsDOMOfflineResourceList::GetStatus(ErrorResult& aRv)
{
nsresult rv = Init();
@ -412,89 +481,106 @@ nsDOMOfflineResourceList::GetStatus(uint16_t *aStatus)
// to an UNCACHED.
if (rv == NS_ERROR_DOM_INVALID_STATE_ERR ||
!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
*aStatus = nsIDOMOfflineResourceList::UNCACHED;
return NS_OK;
return OfflineResourceList_Binding::UNCACHED;
}
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return 0;
}
// If this object is not associated with a cache, return UNCACHED
nsCOMPtr<nsIApplicationCache> appCache = GetDocumentAppCache();
if (!appCache) {
*aStatus = nsIDOMOfflineResourceList::UNCACHED;
return NS_OK;
return OfflineResourceList_Binding::UNCACHED;
}
// If there is an update in process, use its status.
if (mCacheUpdate && mExposeCacheUpdateStatus) {
rv = mCacheUpdate->GetStatus(aStatus);
if (NS_SUCCEEDED(rv) && *aStatus != nsIDOMOfflineResourceList::IDLE) {
return NS_OK;
uint16_t status;
rv = mCacheUpdate->GetStatus(&status);
if (NS_SUCCEEDED(rv) && status != OfflineResourceList_Binding::IDLE) {
return status;
}
}
if (mAvailableApplicationCache) {
*aStatus = nsIDOMOfflineResourceList::UPDATEREADY;
return NS_OK;
return OfflineResourceList_Binding::UPDATEREADY;
}
*aStatus = mStatus;
return NS_OK;
return mStatus;
}
NS_IMETHODIMP
nsDOMOfflineResourceList::Update()
void
nsDOMOfflineResourceList::Update(ErrorResult& aRv)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<nsIOfflineCacheUpdateService> updateService =
do_GetService(NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
nsCOMPtr<nsPIDOMWindowInner> window = GetOwner();
nsCOMPtr<nsIOfflineCacheUpdate> update;
rv = updateService->ScheduleUpdate(mManifestURI, mDocumentURI, mLoadingPrincipal,
window, getter_AddRefs(update));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
}
NS_IMETHODIMP
nsDOMOfflineResourceList::SwapCache()
void
nsDOMOfflineResourceList::SwapCache(ErrorResult& aRv)
{
nsresult rv = Init();
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
if (!nsContentUtils::OfflineAppAllowed(mDocumentURI)) {
return NS_ERROR_DOM_SECURITY_ERR;
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return;
}
nsCOMPtr<nsIApplicationCache> currentAppCache = GetDocumentAppCache();
if (!currentAppCache) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
// Check the current and potentially newly available cache are not identical.
if (mAvailableApplicationCache == currentAppCache) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (mAvailableApplicationCache) {
nsCString currClientId, availClientId;
currentAppCache->GetClientID(currClientId);
mAvailableApplicationCache->GetClientID(availClientId);
if (availClientId == currClientId)
return NS_ERROR_DOM_INVALID_STATE_ERR;
} else if (mStatus != OBSOLETE) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
if (availClientId == currClientId) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
} else if (mStatus != OfflineResourceList_Binding::OBSOLETE) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
ClearCachedKeys();
@ -506,13 +592,14 @@ nsDOMOfflineResourceList::SwapCache()
// We will disassociate from the cache in that case.
if (appCacheContainer) {
rv = appCacheContainer->SetApplicationCache(mAvailableApplicationCache);
NS_ENSURE_SUCCESS(rv, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv);
return;
}
}
mAvailableApplicationCache = nullptr;
mStatus = nsIDOMOfflineResourceList::IDLE;
return NS_OK;
mStatus = OfflineResourceList_Binding::IDLE;
}
void
@ -605,7 +692,7 @@ nsDOMOfflineResourceList::UpdateStateChanged(nsIOfflineCacheUpdate *aUpdate,
SendEvent(NS_LITERAL_STRING(NOUPDATE_STR));
break;
case STATE_OBSOLETE:
mStatus = nsIDOMOfflineResourceList::OBSOLETE;
mStatus = OfflineResourceList_Binding::OBSOLETE;
mAvailableApplicationCache = nullptr;
SendEvent(NS_LITERAL_STRING(OBSOLETE_STR));
break;
@ -756,7 +843,7 @@ nsDOMOfflineResourceList::UpdateCompleted(nsIOfflineCacheUpdate *aUpdate)
mCacheUpdate = nullptr;
if (NS_SUCCEEDED(rv) && succeeded && !partial) {
mStatus = nsIDOMOfflineResourceList::IDLE;
mStatus = OfflineResourceList_Binding::IDLE;
if (isUpgrade) {
SendEvent(NS_LITERAL_STRING(UPDATEREADY_STR));
} else {

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

@ -8,7 +8,6 @@
#define nsDOMOfflineResourceList_h___
#include "nscore.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsIApplicationCache.h"
#include "nsIApplicationCacheContainer.h"
#include "nsIApplicationCacheService.h"
@ -35,7 +34,6 @@ class Event;
} // namespace mozilla
class nsDOMOfflineResourceList final : public mozilla::DOMEventTargetHelper,
public nsIDOMOfflineResourceList,
public nsIObserver,
public nsIOfflineCacheUpdateObserver,
public nsSupportsWeakReference
@ -44,7 +42,6 @@ class nsDOMOfflineResourceList final : public mozilla::DOMEventTargetHelper,
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIDOMOFFLINERESOURCELIST
NS_DECL_NSIOBSERVER
NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER
@ -68,20 +65,11 @@ public:
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
uint16_t GetStatus(ErrorResult& aRv)
{
uint16_t status = 0;
aRv = GetStatus(&status);
return status;
}
void Update(ErrorResult& aRv)
{
aRv = Update();
}
void SwapCache(ErrorResult& aRv)
{
aRv = SwapCache();
}
uint16_t GetStatus(ErrorResult& aRv);
void Update(ErrorResult& aRv);
void SwapCache(ErrorResult& aRv);
IMPL_EVENT_HANDLER(checking)
IMPL_EVENT_HANDLER(error)
@ -93,42 +81,19 @@ public:
IMPL_EVENT_HANDLER(obsolete)
already_AddRefed<mozilla::dom::DOMStringList> GetMozItems(ErrorResult& aRv);
bool MozHasItem(const nsAString& aURI, ErrorResult& aRv)
{
bool hasItem = false;
aRv = MozHasItem(aURI, &hasItem);
return hasItem;
}
uint32_t GetMozLength(ErrorResult& aRv)
{
uint32_t length = 0;
aRv = GetMozLength(&length);
return length;
}
void MozItem(uint32_t aIndex, nsAString& aURI, ErrorResult& aRv)
{
aRv = MozItem(aIndex, aURI);
}
bool MozHasItem(const nsAString& aURI, ErrorResult& aRv);
uint32_t GetMozLength(ErrorResult& aRv);
void MozItem(uint32_t aIndex, nsAString& aURI, ErrorResult& aRv);
void IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aURI,
ErrorResult& aRv)
{
MozItem(aIndex, aURI, aRv);
aFound = !aURI.IsVoid();
}
ErrorResult& aRv);
uint32_t Length()
{
mozilla::IgnoredErrorResult rv;
uint32_t length = GetMozLength(rv);
return rv.Failed() ? 0 : length;
}
void MozAdd(const nsAString& aURI, ErrorResult& aRv)
{
aRv = MozAdd(aURI);
}
void MozRemove(const nsAString& aURI, ErrorResult& aRv)
{
aRv = MozRemove(aURI);
}
void MozAdd(const nsAString& aURI, ErrorResult& aRv);
void MozRemove(const nsAString& aURI, ErrorResult& aRv);
protected:
virtual ~nsDOMOfflineResourceList();

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

@ -134,7 +134,7 @@ LocalStorage::SetItem(const nsAString& aKey, const nsAString& aData,
}
if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
BroadcastChangeNotification(aKey, old, aData);
OnChange(aKey, old, aData);
}
}
@ -154,7 +154,7 @@ LocalStorage::RemoveItem(const nsAString& aKey, nsIPrincipal& aSubjectPrincipal,
}
if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
BroadcastChangeNotification(aKey, old, VoidString());
OnChange(aKey, old, VoidString());
}
}
@ -172,47 +172,24 @@ LocalStorage::Clear(nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv)
}
if (!aRv.ErrorCodeIs(NS_SUCCESS_DOM_NO_OPERATION)) {
BroadcastChangeNotification(VoidString(), VoidString(), VoidString());
OnChange(VoidString(), VoidString(), VoidString());
}
}
void
LocalStorage::BroadcastChangeNotification(const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue)
LocalStorage::OnChange(const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue)
{
if (Principal()) {
// We want to send a message to the parent in order to broadcast the
// StorageEvent correctly to any child process.
PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
MOZ_ASSERT(actor);
PrincipalInfo principalInfo;
nsresult rv = PrincipalToPrincipalInfo(Principal(), &principalInfo);
if (!NS_WARN_IF(NS_FAILED(rv))) {
Unused << NS_WARN_IF(!actor->SendBroadcastLocalStorageChange(
mDocumentURI, nsString(aKey), nsString(aOldValue), nsString(aNewValue),
principalInfo, mIsPrivate));
}
}
DispatchStorageEvent(mDocumentURI, aKey, aOldValue, aNewValue,
Principal(), mIsPrivate, this, false);
}
/* static */ void
LocalStorage::DispatchStorageEvent(const nsAString& aDocumentURI,
const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue,
nsIPrincipal* aPrincipal,
bool aIsPrivate,
Storage* aStorage,
bool aImmediateDispatch)
{
NotifyChange(aStorage, aPrincipal, aKey, aOldValue, aNewValue,
u"localStorage", aDocumentURI, aIsPrivate, aImmediateDispatch);
NotifyChange(/* aStorage */ this,
Principal(),
aKey,
aOldValue,
aNewValue,
/* aStorageType */ u"localStorage",
mDocumentURI,
mIsPrivate,
/* aImmediateDispatch */ false);
}
void

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

@ -36,6 +36,12 @@ public:
return mCache;
}
const nsString&
DocumentURI() const
{
return mDocumentURI;
}
bool PrincipalEquals(nsIPrincipal* aPrincipal);
LocalStorage(nsPIDOMWindowInner* aWindow,
@ -77,24 +83,6 @@ public:
bool IsPrivate() const { return mIsPrivate; }
// aStorage can be null if this method is called by ContentChild.
//
// aImmediateDispatch is for use by (main-thread) IPC code so that PContent
// ordering can be maintained. Without this, the event would be enqueued and
// run in a future turn of the event loop, potentially allowing other PContent
// Recv* methods to trigger script that wants to assume our localstorage
// changes have already been applied. This is the case for message manager
// messages which are used by ContentTask testing logic and webextensions.
static void
DispatchStorageEvent(const nsAString& aDocumentURI,
const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue,
nsIPrincipal* aPrincipal,
bool aIsPrivate,
Storage* aStorage,
bool aImmediateDispatch);
void
ApplyEvent(StorageEvent* aStorageEvent);
@ -115,9 +103,9 @@ private:
// Whether this storage is running in private-browsing window.
bool mIsPrivate : 1;
void BroadcastChangeNotification(const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue);
void OnChange(const nsAString& aKey,
const nsAString& aOldValue,
const nsAString& aNewValue);
};
} // namespace dom

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

@ -75,7 +75,8 @@ NS_IMETHODIMP_(void) LocalStorageCacheBridge::Release(void)
// LocalStorageCache
LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
: mOriginNoSuffix(*aOriginNoSuffix)
: mActor(nullptr)
, mOriginNoSuffix(*aOriginNoSuffix)
, mMonitor("LocalStorageCache")
, mLoaded(false)
, mLoadResult(NS_OK)
@ -89,6 +90,11 @@ LocalStorageCache::LocalStorageCache(const nsACString* aOriginNoSuffix)
LocalStorageCache::~LocalStorageCache()
{
if (mActor) {
mActor->SendDeleteMeInternal();
MOZ_ASSERT(!mActor, "SendDeleteMeInternal should have cleared!");
}
if (mManager) {
mManager->DropCache(this);
}
@ -96,6 +102,16 @@ LocalStorageCache::~LocalStorageCache()
MOZ_COUNT_DTOR(LocalStorageCache);
}
void
LocalStorageCache::SetActor(LocalStorageCacheChild* aActor)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aActor);
MOZ_ASSERT(!mActor);
mActor = aActor;
}
NS_IMETHODIMP_(void)
LocalStorageCache::Release(void)
{
@ -152,6 +168,28 @@ LocalStorageCache::Init(LocalStorageManager* aManager,
mUsage = aManager->GetOriginUsage(mQuotaOriginScope);
}
void
LocalStorageCache::NotifyObservers(const LocalStorage* aStorage,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aStorage);
if (!mActor) {
return;
}
// We want to send a message to the parent in order to broadcast the
// StorageEvent correctly to any child process.
Unused << mActor->SendNotify(aStorage->DocumentURI(),
aKey,
aOldValue,
aNewValue);
}
inline bool
LocalStorageCache::Persist(const LocalStorage* aStorage) const
{
@ -401,7 +439,13 @@ LocalStorageCache::SetItem(const LocalStorage* aStorage, const nsAString& aKey,
data.mKeys.Put(aKey, aValue);
if (aSource == ContentMutation && Persist(aStorage)) {
if (aSource != ContentMutation) {
return NS_OK;
}
NotifyObservers(aStorage, nsString(aKey), aOld, aValue);
if (Persist(aStorage)) {
StorageDBChild* storageChild = StorageDBChild::Get();
if (!storageChild) {
NS_ERROR("Writing to localStorage after the database has been shut down"
@ -443,7 +487,13 @@ LocalStorageCache::RemoveItem(const LocalStorage* aStorage,
Unused << ProcessUsageDelta(aStorage, delta, aSource);
data.mKeys.Remove(aKey);
if (aSource == ContentMutation && Persist(aStorage)) {
if (aSource != ContentMutation) {
return NS_OK;
}
NotifyObservers(aStorage, nsString(aKey), aOld, VoidString());
if (Persist(aStorage)) {
StorageDBChild* storageChild = StorageDBChild::Get();
if (!storageChild) {
NS_ERROR("Writing to localStorage after the database has been shut down"
@ -485,7 +535,15 @@ LocalStorageCache::Clear(const LocalStorage* aStorage,
data.mKeys.Clear();
}
if (aSource == ContentMutation && Persist(aStorage) && (refresh || hadData)) {
if (aSource != ContentMutation) {
return hadData ? NS_OK : NS_SUCCESS_DOM_NO_OPERATION;
}
if (hadData) {
NotifyObservers(aStorage, VoidString(), VoidString(), VoidString());
}
if (Persist(aStorage) && (refresh || hadData)) {
StorageDBChild* storageChild = StorageDBChild::Get();
if (!storageChild) {
NS_ERROR("Writing to localStorage after the database has been shut down"

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

@ -21,6 +21,7 @@ namespace mozilla {
namespace dom {
class LocalStorage;
class LocalStorageCacheChild;
class LocalStorageManager;
class StorageUsage;
class StorageDBBridge;
@ -76,6 +77,23 @@ protected:
class LocalStorageCache : public LocalStorageCacheBridge
{
public:
void
AssertIsOnOwningThread() const
{
NS_ASSERT_OWNINGTHREAD(LocalStorage);
}
void
SetActor(LocalStorageCacheChild* aActor);
void
ClearActor()
{
AssertIsOnOwningThread();
mActor = nullptr;
}
NS_IMETHOD_(void) Release(void) override;
enum MutationSource {
@ -181,6 +199,13 @@ private:
// Helper to get one of the 3 data sets (regular, private, session)
Data& DataSet(const LocalStorage* aStorage);
// Used for firing storage events and synchronization of caches in other
// content processes.
void NotifyObservers(const LocalStorage* aStorage,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue);
// Whether the storage change is about to persist
bool Persist(const LocalStorage* aStorage) const;
@ -210,6 +235,14 @@ private:
// Obtained from the manager during initialization (Init method).
RefPtr<StorageUsage> mUsage;
// The LocalStorageCacheChild is created at the same time of this class.
// In normal operation, the actor will be synchronously cleared in our
// destructor when we tell it to delete itself. In a shutdown-related edge
// case in the parent process for JSM's, it is possible for the actor to be
// destroyed while this class remains alive, in which case it will be nulled
// out.
LocalStorageCacheChild* mActor;
// The origin this cache belongs to in the "DB format", i.e. reversed
nsCString mOriginNoSuffix;

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

@ -246,9 +246,38 @@ LocalStorageManager::GetStorageInternal(CreateMode aCreateMode,
}
}
PBackgroundChild* backgroundActor =
BackgroundChild::GetOrCreateForCurrentThread();
if (NS_WARN_IF(!backgroundActor)) {
return NS_ERROR_FAILURE;
}
PrincipalInfo principalInfo;
rv = mozilla::ipc::PrincipalToPrincipalInfo(aPrincipal, &principalInfo);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
uint32_t privateBrowsingId;
rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// There is always a single instance of a cache per scope
// in a single instance of a DOM storage manager.
cache = PutCache(originAttrSuffix, originKey, aPrincipal);
LocalStorageCacheChild* actor = new LocalStorageCacheChild(cache);
MOZ_ALWAYS_TRUE(
backgroundActor->SendPBackgroundLocalStorageCacheConstructor(
actor,
principalInfo,
originKey,
privateBrowsingId));
cache->SetActor(actor);
}
if (aRetval) {

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

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
include protocol PBackground;
include PBackgroundSharedTypes;
namespace mozilla {
namespace dom {
async protocol PBackgroundLocalStorageCache
{
manager PBackground;
parent:
async DeleteMe();
async Notify(nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue);
child:
// The principalInfo and privateBrowsingId could instead be retained by the
// LocalStorageCacheChild/LocalStorageCache instead of being re-transmitted.
// However, these changes are a temporary optimization intended for uplift,
// and this constant factor overhead is very small compared to the upside of
// filtering.
async Observe(PrincipalInfo principalInfo,
uint32_t privateBrowsingId,
nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue);
async __delete__();
};
} // namespace dom
} // namespace mozilla

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

@ -111,6 +111,15 @@ public:
bool IsSessionOnly() const { return mIsSessionOnly; }
// aStorage can be null if this method is called by LocalStorageCacheChild.
//
// aImmediateDispatch is for use by child IPC code (LocalStorageCacheChild)
// so that PBackground ordering can be maintained. Without this, the event
// would be/ enqueued and run in a future turn of the event loop, potentially
// allowing other PBackground Recv* methods to trigger script that wants to
// assume our localstorage changes have already been applied. This is the
// case for message manager messages which are used by ContentTask testing
// logic and webextensions.
static void
NotifyChange(Storage* aStorage, nsIPrincipal* aPrincipal,
const nsAString& aKey, const nsAString& aOldValue,

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

@ -23,6 +23,11 @@ namespace dom {
namespace {
typedef nsClassHashtable<nsCStringHashKey, nsTArray<LocalStorageCacheParent*>>
LocalStorageCacheParentHashtable;
StaticAutoPtr<LocalStorageCacheParentHashtable> gLocalStorageCacheParents;
StorageDBChild* sStorageChild = nullptr;
// False until we shut the storage child down.
@ -30,6 +35,77 @@ bool sStorageChildDown = false;
}
LocalStorageCacheChild::LocalStorageCacheChild(LocalStorageCache* aCache)
: mCache(aCache)
{
AssertIsOnOwningThread();
MOZ_ASSERT(aCache);
aCache->AssertIsOnOwningThread();
MOZ_COUNT_CTOR(LocalStorageCacheChild);
}
LocalStorageCacheChild::~LocalStorageCacheChild()
{
AssertIsOnOwningThread();
MOZ_COUNT_DTOR(LocalStorageCacheChild);
}
void
LocalStorageCacheChild::SendDeleteMeInternal()
{
AssertIsOnOwningThread();
if (mCache) {
mCache->ClearActor();
mCache = nullptr;
MOZ_ALWAYS_TRUE(PBackgroundLocalStorageCacheChild::SendDeleteMe());
}
}
void
LocalStorageCacheChild::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnOwningThread();
if (mCache) {
mCache->ClearActor();
mCache = nullptr;
}
}
mozilla::ipc::IPCResult
LocalStorageCacheChild::RecvObserve(const PrincipalInfo& aPrincipalInfo,
const uint32_t& aPrivateBrowsingId,
const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue)
{
AssertIsOnOwningThread();
nsresult rv;
nsCOMPtr<nsIPrincipal> principal =
PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return IPC_FAIL_NO_REASON(this);
}
Storage::NotifyChange(/* aStorage */ nullptr,
principal,
aKey,
aOldValue,
aNewValue,
/* aStorageType */ u"localStorage",
aDocumentURI,
/* aIsPrivate */ !!aPrivateBrowsingId,
/* aImmediateDispatch */ true);
return IPC_OK();
}
// ----------------------------------------------------------------------------
// Child
// ----------------------------------------------------------------------------
@ -396,6 +472,88 @@ ShutdownObserver::Observe(nsISupports* aSubject,
return NS_OK;
}
LocalStorageCacheParent::LocalStorageCacheParent(
const PrincipalInfo& aPrincipalInfo,
const nsACString& aOriginKey,
uint32_t aPrivateBrowsingId)
: mPrincipalInfo(aPrincipalInfo)
, mOriginKey(aOriginKey)
, mPrivateBrowsingId(aPrivateBrowsingId)
, mActorDestroyed(false)
{
AssertIsOnBackgroundThread();
}
LocalStorageCacheParent::~LocalStorageCacheParent()
{
MOZ_ASSERT(mActorDestroyed);
}
void
LocalStorageCacheParent::ActorDestroy(ActorDestroyReason aWhy)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mActorDestroyed);
mActorDestroyed = true;
MOZ_ASSERT(gLocalStorageCacheParents);
nsTArray<LocalStorageCacheParent*>* array;
gLocalStorageCacheParents->Get(mOriginKey, &array);
MOZ_ASSERT(array);
array->RemoveElement(this);
if (array->IsEmpty()) {
gLocalStorageCacheParents->Remove(mOriginKey);
}
if (!gLocalStorageCacheParents->Count()) {
gLocalStorageCacheParents = nullptr;
}
}
mozilla::ipc::IPCResult
LocalStorageCacheParent::RecvDeleteMe()
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(!mActorDestroyed);
IProtocol* mgr = Manager();
if (!PBackgroundLocalStorageCacheParent::Send__delete__(this)) {
return IPC_FAIL_NO_REASON(mgr);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
LocalStorageCacheParent::RecvNotify(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(gLocalStorageCacheParents);
nsTArray<LocalStorageCacheParent*>* array;
gLocalStorageCacheParents->Get(mOriginKey, &array);
MOZ_ASSERT(array);
for (LocalStorageCacheParent* localStorageCacheParent : *array) {
if (localStorageCacheParent != this) {
Unused << localStorageCacheParent->SendObserve(mPrincipalInfo,
mPrivateBrowsingId,
aDocumentURI,
aKey,
aOldValue,
aNewValue);
}
}
return IPC_OK();
}
// ----------------------------------------------------------------------------
// Parent
// ----------------------------------------------------------------------------
@ -1228,6 +1386,66 @@ ObserverSink::Observe(const char* aTopic,
* Exported functions
******************************************************************************/
PBackgroundLocalStorageCacheParent*
AllocPBackgroundLocalStorageCacheParent(
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
{
AssertIsOnBackgroundThread();
RefPtr<LocalStorageCacheParent> actor =
new LocalStorageCacheParent(aPrincipalInfo, aOriginKey, aPrivateBrowsingId);
// Transfer ownership to IPDL.
return actor.forget().take();
}
mozilla::ipc::IPCResult
RecvPBackgroundLocalStorageCacheConstructor(
mozilla::ipc::PBackgroundParent* aBackgroundActor,
PBackgroundLocalStorageCacheParent* aActor,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
auto* actor = static_cast<LocalStorageCacheParent*>(aActor);
if (!gLocalStorageCacheParents) {
gLocalStorageCacheParents = new LocalStorageCacheParentHashtable();
}
nsTArray<LocalStorageCacheParent*>* array;
if (!gLocalStorageCacheParents->Get(aOriginKey, &array)) {
array = new nsTArray<LocalStorageCacheParent*>();
gLocalStorageCacheParents->Put(aOriginKey, array);
}
array->AppendElement(actor);
// We are currently trusting the content process not to lie to us. It is
// future work to consult the ClientManager to determine whether this is a
// legitimate origin for the content process.
return IPC_OK();
}
bool
DeallocPBackgroundLocalStorageCacheParent(
PBackgroundLocalStorageCacheParent* aActor)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
// Transfer ownership back from IPDL.
RefPtr<LocalStorageCacheParent> actor =
dont_AddRef(static_cast<LocalStorageCacheParent*>(aActor));
return true;
}
PBackgroundStorageParent*
AllocPBackgroundStorageParent(const nsString& aProfilePath)
{

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

@ -7,6 +7,8 @@
#ifndef mozilla_dom_StorageIPC_h
#define mozilla_dom_StorageIPC_h
#include "mozilla/dom/PBackgroundLocalStorageCacheChild.h"
#include "mozilla/dom/PBackgroundLocalStorageCacheParent.h"
#include "mozilla/dom/PBackgroundStorageChild.h"
#include "mozilla/dom/PBackgroundStorageParent.h"
#include "StorageDBThread.h"
@ -19,11 +21,65 @@ namespace mozilla {
class OriginAttributesPattern;
namespace ipc {
class BackgroundChildImpl;
class PrincipalInfo;
} // namespace ipc
namespace dom {
class LocalStorageManager;
class PBackgroundStorageParent;
class LocalStorageCacheChild final
: public PBackgroundLocalStorageCacheChild
{
friend class mozilla::ipc::BackgroundChildImpl;
friend class LocalStorageCache;
friend class LocalStorageManager;
// LocalStorageCache effectively owns this instance, although IPC handles its
// allocation/deallocation. When the LocalStorageCache destructor runs, it
// will invoke SendDeleteMeInternal() which will trigger both instances to
// drop their mutual references and cause IPC to destroy the actor after the
// DeleteMe round-trip.
LocalStorageCache* MOZ_NON_OWNING_REF mCache;
NS_DECL_OWNINGTHREAD
public:
void
AssertIsOnOwningThread() const
{
NS_ASSERT_OWNINGTHREAD(LocalStorageCacheChild);
}
private:
// Only created by LocalStorageManager.
explicit LocalStorageCacheChild(LocalStorageCache* aCache);
// Only destroyed by mozilla::ipc::BackgroundChildImpl.
~LocalStorageCacheChild();
// Only called by LocalStorageCache.
void
SendDeleteMeInternal();
// IPDL methods are only called by IPDL.
void
ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult
RecvObserve(const PrincipalInfo& aPrincipalInfo,
const uint32_t& aPrivateBrowsingId,
const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue) override;
};
// Child side of the IPC protocol, exposes as DB interface but
// is responsible to send all requests to the parent process
// and expects asynchronous answers. Those are then transparently
@ -126,6 +182,39 @@ private:
bool mIPCOpen;
};
class LocalStorageCacheParent final
: public PBackgroundLocalStorageCacheParent
{
const PrincipalInfo mPrincipalInfo;
const nsCString mOriginKey;
uint32_t mPrivateBrowsingId;
bool mActorDestroyed;
public:
// Created in AllocPBackgroundLocalStorageCacheParent.
LocalStorageCacheParent(const PrincipalInfo& aPrincipalInfo,
const nsACString& aOriginKey,
uint32_t aPrivateBrowsingId);
NS_INLINE_DECL_REFCOUNTING(mozilla::dom::LocalStorageCacheParent)
private:
// Reference counted.
~LocalStorageCacheParent();
// IPDL methods are only called by IPDL.
void
ActorDestroy(ActorDestroyReason aWhy) override;
mozilla::ipc::IPCResult
RecvDeleteMe() override;
mozilla::ipc::IPCResult
RecvNotify(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue) override;
};
// Receives async requests from child processes and is responsible
// to send back responses from the DB thread. Exposes as a fake
@ -283,6 +372,24 @@ private:
bool mIPCOpen;
};
PBackgroundLocalStorageCacheParent*
AllocPBackgroundLocalStorageCacheParent(
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId);
mozilla::ipc::IPCResult
RecvPBackgroundLocalStorageCacheConstructor(
mozilla::ipc::PBackgroundParent* aBackgroundActor,
PBackgroundLocalStorageCacheParent* aActor,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId);
bool
DeallocPBackgroundLocalStorageCacheParent(
PBackgroundLocalStorageCacheParent* aActor);
PBackgroundStorageParent*
AllocPBackgroundStorageParent(const nsString& aProfilePath);

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

@ -36,6 +36,7 @@ UNIFIED_SOURCES += [
]
IPDL_SOURCES += [
'PBackgroundLocalStorageCache.ipdl',
'PBackgroundStorage.ipdl',
]

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

@ -46,7 +46,7 @@ function onLoaded()
try
{
window.opener.OfflineTest.ok(applicationCache.status == SpecialPowers.Ci.nsIDOMOfflineResourceList.UNCACHED,
window.opener.OfflineTest.ok(applicationCache.status == OfflineResourceList.UNCACHED,
"there is no associated application cache");
}
catch (ex)

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

@ -156,7 +156,7 @@ function implicitCached()
"https://example.com/tests/dom/tests/mochitest/ajax/offline/fallback.html", false);
// Cache object status
OfflineTest.is(applicationCache.status, SpecialPowers.Ci.nsIDOMOfflineResourceList.IDLE,
OfflineTest.is(applicationCache.status, OfflineResourceList.IDLE,
"we have associated application cache (1)");
OfflineTest.is(gGotFrameVersion, 1, "IFrame version 1");
@ -210,7 +210,7 @@ function manifestUpdated()
"https://example.com/tests/dom/tests/mochitest/ajax/offline/fallback2.html", false);
// Cache object status
OfflineTest.is(applicationCache.status, SpecialPowers.Ci.nsIDOMOfflineResourceList.UPDATEREADY,
OfflineTest.is(applicationCache.status, OfflineResourceList.UPDATEREADY,
"we have associated application cache and update is pending (2)");
var entries = [
@ -259,7 +259,7 @@ function manifestUpdated()
"https://example.com/tests/dom/tests/mochitest/ajax/offline/fallback2.html", false);
// Cache object status
OfflineTest.is(applicationCache.status, SpecialPowers.Ci.nsIDOMOfflineResourceList.UPDATEREADY,
OfflineTest.is(applicationCache.status, OfflineResourceList.UPDATEREADY,
"we have associated application cache and update is pending (3)");
OfflineTest.is(gGotFrameVersion, 1, "IFrame version 1 because cache was not swapped");
@ -303,10 +303,10 @@ function manifestNoUpdate()
OfflineTest.ok(gStep == 3, "Got manifestNoUpdate in step 3, gStep=" + gStep);
++gStep;
OfflineTest.is(applicationCache.status, SpecialPowers.Ci.nsIDOMOfflineResourceList.UPDATEREADY,
OfflineTest.is(applicationCache.status, OfflineResourceList.UPDATEREADY,
"we have associated application cache and update is pending (4)");
applicationCache.swapCache();
OfflineTest.is(applicationCache.status, SpecialPowers.Ci.nsIDOMOfflineResourceList.IDLE,
OfflineTest.is(applicationCache.status, OfflineResourceList.IDLE,
"we have associated application cache (4)");
gGotFrameVersion = 0;

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

@ -261,11 +261,15 @@ partial interface Element {
[BinaryName="shadowRootByMode", Func="nsDocument::IsShadowDOMEnabled"]
readonly attribute ShadowRoot? shadowRoot;
[ChromeOnly, Func="nsDocument::IsShadowDOMEnabled", BinaryName="shadowRoot"]
[Func="nsDocument::IsShadowDOMEnabledAndCallerIsChromeOrAddon", BinaryName="shadowRoot"]
readonly attribute ShadowRoot? openOrClosedShadowRoot;
[BinaryName="assignedSlotByMode", Func="nsDocument::IsShadowDOMEnabled"]
readonly attribute HTMLSlotElement? assignedSlot;
[ChromeOnly, BinaryName="assignedSlot", Func="nsDocument::IsShadowDOMEnabled"]
readonly attribute HTMLSlotElement? openOrClosedAssignedSlot;
[CEReactions, Unscopable, SetterThrows, Func="nsDocument::IsShadowDOMEnabled"]
attribute DOMString slot;
};

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

@ -21,6 +21,9 @@ interface Text : CharacterData {
partial interface Text {
[BinaryName="assignedSlotByMode", Func="nsTextNode::IsShadowDOMEnabled"]
readonly attribute HTMLSlotElement? assignedSlot;
[ChromeOnly, BinaryName="assignedSlot", Func="nsTextNode::IsShadowDOMEnabled"]
readonly attribute HTMLSlotElement? openOrClosedAssignedSlot;
};
Text implements GeometryUtils;

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

@ -18,11 +18,12 @@
* https://drafts.css-houdini.org/css-paint-api-1/#dom-window-paintworklet
*/
interface ApplicationCache;
interface IID;
interface nsIBrowserDOMWindow;
interface XULControllers;
typedef OfflineResourceList ApplicationCache;
// http://www.whatwg.org/specs/web-apps/current-work/
[PrimaryGlobal, LegacyUnenumerableNamedProperties, NeedResolve]
/*sealed*/ interface Window : EventTarget {

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

@ -219,6 +219,26 @@ BackgroundChildImpl::DeallocPBackgroundIndexedDBUtilsChild(
return true;
}
BackgroundChildImpl::PBackgroundLocalStorageCacheChild*
BackgroundChildImpl::AllocPBackgroundLocalStorageCacheChild(
const PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
{
MOZ_CRASH("PBackgroundLocalStorageChild actors should be manually "
"constructed!");
}
bool
BackgroundChildImpl::DeallocPBackgroundLocalStorageCacheChild(
PBackgroundLocalStorageCacheChild* aActor)
{
MOZ_ASSERT(aActor);
delete aActor;
return true;
}
BackgroundChildImpl::PBackgroundStorageChild*
BackgroundChildImpl::AllocPBackgroundStorageChild(const nsString& aProfilePath)
{
@ -715,32 +735,6 @@ BackgroundChildImpl::DeallocPServiceWorkerRegistrationChild(PServiceWorkerRegist
return dom::DeallocServiceWorkerRegistrationChild(aActor);
}
mozilla::ipc::IPCResult
BackgroundChildImpl::RecvDispatchLocalStorageChange(
const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const PrincipalInfo& aPrincipalInfo,
const bool& aIsPrivate)
{
if (!NS_IsMainThread()) {
return IPC_OK();
}
nsresult rv;
nsCOMPtr<nsIPrincipal> principal =
PrincipalInfoToPrincipal(aPrincipalInfo, &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return IPC_FAIL_NO_REASON(this);
}
LocalStorage::DispatchStorageEvent(aDocumentURI, aKey, aOldValue, aNewValue,
principal, aIsPrivate, nullptr, true);
return IPC_OK();
}
bool
BackgroundChildImpl::GetMessageSchedulerGroups(const Message& aMsg, SchedulerGroupSet& aGroups)
{

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

@ -70,6 +70,17 @@ protected:
DeallocPBackgroundIndexedDBUtilsChild(PBackgroundIndexedDBUtilsChild* aActor)
override;
virtual PBackgroundLocalStorageCacheChild*
AllocPBackgroundLocalStorageCacheChild(const PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
override;
virtual bool
DeallocPBackgroundLocalStorageCacheChild(
PBackgroundLocalStorageCacheChild* aActor)
override;
virtual PBackgroundStorageChild*
AllocPBackgroundStorageChild(const nsString& aProfilePath) override;
@ -227,14 +238,6 @@ protected:
virtual bool
DeallocPHttpBackgroundChannelChild(PHttpBackgroundChannelChild* aActor) override;
virtual mozilla::ipc::IPCResult
RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const PrincipalInfo& aPrincipalInfo,
const bool& aIsPrivate) override;
bool
GetMessageSchedulerGroups(const Message& aMsg, SchedulerGroupSet& aGroups) override;

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

@ -252,6 +252,52 @@ BackgroundParentImpl::RecvFlushPendingFileDeletions()
return IPC_OK();
}
BackgroundParentImpl::PBackgroundLocalStorageCacheParent*
BackgroundParentImpl::AllocPBackgroundLocalStorageCacheParent(
const PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
return
mozilla::dom::AllocPBackgroundLocalStorageCacheParent(aPrincipalInfo,
aOriginKey,
aPrivateBrowsingId);
}
mozilla::ipc::IPCResult
BackgroundParentImpl::RecvPBackgroundLocalStorageCacheConstructor(
PBackgroundLocalStorageCacheParent* aActor,
const PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
return
mozilla::dom::RecvPBackgroundLocalStorageCacheConstructor(
this,
aActor,
aPrincipalInfo,
aOriginKey,
aPrivateBrowsingId);
}
bool
BackgroundParentImpl::DeallocPBackgroundLocalStorageCacheParent(
PBackgroundLocalStorageCacheParent* aActor)
{
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
MOZ_ASSERT(aActor);
return mozilla::dom::DeallocPBackgroundLocalStorageCacheParent(aActor);
}
auto
BackgroundParentImpl::AllocPBackgroundStorageParent(const nsString& aProfilePath)
-> PBackgroundStorageParent*
@ -285,34 +331,6 @@ BackgroundParentImpl::DeallocPBackgroundStorageParent(
return mozilla::dom::DeallocPBackgroundStorageParent(aActor);
}
mozilla::ipc::IPCResult
BackgroundParentImpl::RecvBroadcastLocalStorageChange(
const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const PrincipalInfo& aPrincipalInfo,
const bool& aIsPrivate)
{
// Let's inform the StorageActivityService about this change.
dom::StorageActivityService::SendActivity(aPrincipalInfo);
nsTArray<PBackgroundParent*> liveActorArray;
if (NS_WARN_IF(!BackgroundParent::GetLiveActorArray(this, liveActorArray))) {
return IPC_FAIL_NO_REASON(this);
}
for (auto* liveActor : liveActorArray) {
if (liveActor != this) {
Unused << liveActor->SendDispatchLocalStorageChange(
nsString(aDocumentURI), nsString(aKey), nsString(aOldValue),
nsString(aNewValue), aPrincipalInfo, aIsPrivate);
}
}
return IPC_OK();
}
PPendingIPCBlobParent*
BackgroundParentImpl::AllocPPendingIPCBlobParent(const IPCBlob& aBlob)
{

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

@ -63,6 +63,25 @@ protected:
virtual mozilla::ipc::IPCResult
RecvFlushPendingFileDeletions() override;
virtual PBackgroundLocalStorageCacheParent*
AllocPBackgroundLocalStorageCacheParent(const PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
override;
virtual mozilla::ipc::IPCResult
RecvPBackgroundLocalStorageCacheConstructor(
PBackgroundLocalStorageCacheParent* aActor,
const PrincipalInfo& aPrincipalInfo,
const nsCString& aOriginKey,
const uint32_t& aPrivateBrowsingId)
override;
virtual bool
DeallocPBackgroundLocalStorageCacheParent(
PBackgroundLocalStorageCacheParent* aActor)
override;
virtual PBackgroundStorageParent*
AllocPBackgroundStorageParent(const nsString& aProfilePath) override;
@ -73,14 +92,6 @@ protected:
virtual bool
DeallocPBackgroundStorageParent(PBackgroundStorageParent* aActor) override;
virtual mozilla::ipc::IPCResult
RecvBroadcastLocalStorageChange(const nsString& aDocumentURI,
const nsString& aKey,
const nsString& aOldValue,
const nsString& aNewValue,
const PrincipalInfo& aPrincipalInfo,
const bool& aIsPrivate) override;
virtual PPendingIPCBlobParent*
AllocPPendingIPCBlobParent(const IPCBlob& aBlob) override;

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

@ -1242,7 +1242,7 @@ GeckoChildProcessHost::LaunchAndroidService(const char* type,
const base::file_handle_mapping_vector& fds_to_remap,
ProcessHandle* process_handle)
{
MOZ_RELEASE_ASSERT((2 <= fds_to_remap.size()) && (fds_to_remap.size() <= 4));
MOZ_RELEASE_ASSERT((2 <= fds_to_remap.size()) && (fds_to_remap.size() <= 5));
JNIEnv* const env = mozilla::jni::GetEnvForThread();
MOZ_ASSERT(env);
@ -1258,18 +1258,19 @@ GeckoChildProcessHost::LaunchAndroidService(const char* type,
// which they append to fds_to_remap. There must be a better way to do it.
// See bug 1440207.
int32_t prefsFd = fds_to_remap[0].first;
int32_t ipcFd = fds_to_remap[1].first;
int32_t prefMapFd = fds_to_remap[1].first;
int32_t ipcFd = fds_to_remap[2].first;
int32_t crashFd = -1;
int32_t crashAnnotationFd = -1;
if (fds_to_remap.size() == 3) {
crashAnnotationFd = fds_to_remap[2].first;
}
if (fds_to_remap.size() == 4) {
crashFd = fds_to_remap[2].first;
crashAnnotationFd = fds_to_remap[3].first;
}
if (fds_to_remap.size() == 5) {
crashFd = fds_to_remap[3].first;
crashAnnotationFd = fds_to_remap[4].first;
}
int32_t handle = java::GeckoProcessManager::Start(type, jargs, prefsFd, ipcFd, crashFd, crashAnnotationFd);
int32_t handle = java::GeckoProcessManager::Start(type, jargs, prefsFd, prefMapFd, ipcFd, crashFd, crashAnnotationFd);
if (process_handle) {
*process_handle = handle;

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

@ -5,6 +5,7 @@
include protocol PAsmJSCacheEntry;
include protocol PBackgroundIDBFactory;
include protocol PBackgroundIndexedDBUtils;
include protocol PBackgroundLocalStorageCache;
include protocol PBackgroundStorage;
include protocol PBackgroundTest;
include protocol PBroadcastChannel;
@ -65,6 +66,7 @@ sync protocol PBackground
manages PAsmJSCacheEntry;
manages PBackgroundIDBFactory;
manages PBackgroundIndexedDBUtils;
manages PBackgroundLocalStorageCache;
manages PBackgroundStorage;
manages PBackgroundTest;
manages PBroadcastChannel;
@ -106,14 +108,11 @@ parent:
// Use only for testing!
async FlushPendingFileDeletions();
async PBackgroundStorage(nsString profilePath);
async PBackgroundLocalStorageCache(PrincipalInfo principalInfo,
nsCString originKey,
uint32_t privateBrowsingId);
async BroadcastLocalStorageChange(nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue,
PrincipalInfo principalInfo,
bool isPrivate);
async PBackgroundStorage(nsString profilePath);
async PVsync();
@ -175,13 +174,6 @@ child:
async PPendingIPCBlob(IPCBlob blob);
async DispatchLocalStorageChange(nsString documentURI,
nsString key,
nsString oldValue,
nsString newValue,
PrincipalInfo principalInfo,
bool isPrivate);
both:
// PIPCBlobInputStream is created on the parent side only if the child starts
// a migration.

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

@ -48,7 +48,7 @@ class AutoLockMonitor : public LockGuard<Mutex>
{ }
bool isFor(Monitor& other) const {
return monitor.lock_ == other.lock_;
return &monitor.lock_ == &other.lock_;
}
void wait(ConditionVariable& condVar) {
@ -93,7 +93,7 @@ class AutoUnlockMonitor
}
bool isFor(Monitor& other) const {
return monitor.lock_ == other.lock_;
return &monitor.lock_ == &other.lock_;
}
};

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

@ -102,7 +102,13 @@ nsXULTooltipListener::MouseOut(Event* aEvent)
// hide the tooltip
if (currentTooltip) {
// which node did the mouse leave?
nsCOMPtr<nsINode> targetNode = do_QueryInterface(aEvent->GetTarget());
EventTarget* eventTarget = aEvent->GetComposedTarget();
nsCOMPtr<nsIContent> content = do_QueryInterface(eventTarget);
if (content && !content->GetContainingShadow()) {
eventTarget = aEvent->GetTarget();
}
nsCOMPtr<nsINode> targetNode = do_QueryInterface(eventTarget);
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
if (pm) {
@ -173,7 +179,11 @@ nsXULTooltipListener::MouseMove(Event* aEvent)
// showing and the tooltip hasn't been displayed since the mouse entered
// the node, then start the timer to show the tooltip.
if (!currentTooltip && !mTooltipShownOnce) {
nsCOMPtr<EventTarget> eventTarget = aEvent->GetTarget();
nsCOMPtr<EventTarget> eventTarget = aEvent->GetComposedTarget();
nsCOMPtr<nsIContent> content = do_QueryInterface(eventTarget);
if (content && !content->GetContainingShadow()) {
eventTarget = aEvent->GetTarget();
}
// don't show tooltips attached to elements outside of a menu popup
// when hovering over an element inside it. The popupsinherittooltip

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

@ -59,6 +59,7 @@ import org.mozilla.gecko.webapps.WebApps;
import org.mozilla.gecko.widget.ActionModePresenter;
import org.mozilla.gecko.widget.GeckoPopupMenu;
import org.mozilla.geckoview.GeckoResponse;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
@ -600,21 +601,18 @@ public class CustomTabsActivity extends AppCompatActivity
}
@Override
public void onLoadRequest(final GeckoSession session, final String urlStr,
final int target,
final int flags,
final GeckoResponse<Boolean> response) {
public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String urlStr,
final int target,
final int flags) {
if (target != GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW) {
response.respond(false);
return;
return GeckoResult.fromValue(false);
}
final Uri uri = Uri.parse(urlStr);
if (uri == null) {
// We can't handle this, so deny it.
Log.w(LOGTAG, "Failed to parse URL for navigation: " + urlStr);
response.respond(true);
return;
return GeckoResult.fromValue(true);
}
// Always use Fennec for these schemes.
@ -639,12 +637,11 @@ public class CustomTabsActivity extends AppCompatActivity
}
}
response.respond(true);
return GeckoResult.fromValue(true);
}
@Override
public void onNewSession(final GeckoSession session, final String uri,
final GeckoResponse<GeckoSession> response) {
public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
// We should never get here because we abort loads that need a new session in onLoadRequest()
throw new IllegalStateException("Unexpected new session");
}

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

@ -39,6 +39,7 @@ import org.mozilla.gecko.util.ActivityUtils;
import org.mozilla.gecko.util.ColorUtil;
import org.mozilla.gecko.widget.ActionModePresenter;
import org.mozilla.geckoview.GeckoResponse;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
@ -383,29 +384,25 @@ public class WebAppActivity extends AppCompatActivity
}
@Override
public void onLoadRequest(final GeckoSession session, final String urlStr,
final int target,
final int flags,
final GeckoResponse<Boolean> response) {
public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String urlStr,
final int target,
final int flags) {
final Uri uri = Uri.parse(urlStr);
if (uri == null) {
// We can't really handle this, so deny it?
Log.w(LOGTAG, "Failed to parse URL for navigation: " + urlStr);
response.respond(true);
return;
return GeckoResult.fromValue(true);
}
if (mManifest.isInScope(uri) && target != TARGET_WINDOW_NEW) {
// This is in scope and wants to load in the same frame, so
// let Gecko handle it.
response.respond(false);
return;
return GeckoResult.fromValue(false);
}
if ("javascript".equals(uri.getScheme())) {
// These URIs will fail the scope check but should still be loaded in the PWA.
response.respond(false);
return;
return GeckoResult.fromValue(false);
}
if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme()) ||
@ -433,12 +430,12 @@ public class WebAppActivity extends AppCompatActivity
Log.w(LOGTAG, "No activity handler found for: " + urlStr);
}
}
response.respond(true);
return GeckoResult.fromValue(true);
}
@Override
public void onNewSession(final GeckoSession session, final String uri,
final GeckoResponse<GeckoSession> response) {
public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
// We should never get here because we abort loads that need a new session in onLoadRequest()
throw new IllegalStateException("Unexpected new session");
}

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

@ -158,8 +158,18 @@ class GeckoViewContent extends GeckoViewContentModule {
// Short circuit and return the pending state if we're in the process of restoring
sendAsyncMessage("GeckoView:SaveStateFinish", {state: JSON.stringify(this._savedState), id: aMsg.data.id});
} else {
let state = this.collectSessionState();
sendAsyncMessage("GeckoView:SaveStateFinish", {state: JSON.stringify(state), id: aMsg.data.id});
try {
let state = this.collectSessionState();
sendAsyncMessage("GeckoView:SaveStateFinish", {
state: state ? JSON.stringify(state) : null,
id: aMsg.data.id
});
} catch (e) {
sendAsyncMessage("GeckoView:SaveStateFinish", {
error: e.message,
id: aMsg.data.id
});
}
}
break;

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

@ -4,7 +4,7 @@
package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoResponse
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
@ -12,7 +12,9 @@ import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import org.mozilla.geckoview.test.util.Callbacks
import org.mozilla.geckoview.test.util.UiThreadUtils
import android.os.Looper
import android.support.test.filters.MediumTest
import android.support.test.runner.AndroidJUnit4
import org.hamcrest.Matchers.*
@ -20,6 +22,8 @@ import org.junit.Assume.assumeThat
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.concurrent.thread
@RunWith(AndroidJUnit4::class)
@MediumTest
class ContentDelegateTest : BaseSessionTest() {
@ -43,13 +47,13 @@ class ContentDelegateTest : BaseSessionTest() {
@AssertCalled(count = 2)
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int, flags: Int,
response: GeckoResponse<Boolean>) {
response.respond(false)
where: Int, flags: Int): GeckoResult<Boolean>? {
return null
}
@AssertCalled(false)
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
return null
}
@AssertCalled(count = 1)
@ -155,4 +159,29 @@ class ContentDelegateTest : BaseSessionTest() {
mainSession.evaluateJS("window.scrollY") as Double,
closeTo(100.0, .5))
}
@Test fun saveStateSync() {
val startUri = createTestUrl(SAVE_STATE_PATH)
mainSession.loadUri(startUri)
sessionRule.waitForPageStop()
var worker = thread {
Looper.prepare()
var thread = Thread.currentThread()
mainSession.saveState().then<Void> { _: GeckoSession.SessionState? ->
assertThat("We should be on the worker thread", Thread.currentThread(),
equalTo(thread))
Looper.myLooper().quit()
null
}
Looper.loop()
}
worker.join(sessionRule.timeoutMillis)
if (worker.isAlive) {
throw UiThreadUtils.TimeoutException("Timed out")
}
}
}

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

@ -56,6 +56,13 @@ public class GeckoResultTest {
mDone = false;
}
@Test(expected = RuntimeException.class)
public void createWithoutLooper() {
// Without @UiThreadTest this will be run in a worker
// thread that does not have a Looper.
new GeckoResult<Integer>();
}
@Test
@UiThreadTest
public void thenWithResult() {
@ -95,12 +102,18 @@ public class GeckoResultTest {
@Test
@UiThreadTest
public void testEquals() {
final GeckoResult<Integer> result = GeckoResult.fromValue(42);
final GeckoResult<Integer> result2 = new GeckoResult<>(result);
public void testCopy() {
final GeckoResult<Integer> result = new GeckoResult<>(GeckoResult.fromValue(42));
result.then(new OnValueListener<Integer, Void>() {
@Override
public GeckoResult<Void> onValue(Integer value) throws Throwable {
assertThat("Value should match", value, equalTo(42));
done();
return null;
}
});
assertThat("Results should be equal", result, equalTo(result2));
assertThat("Hashcode should be equal", result.hashCode(), equalTo(result2.hashCode()));
waitUntilDone();
}
@Test(expected = IllegalStateException.class)
@ -159,6 +172,33 @@ public class GeckoResultTest {
waitUntilDone();
}
@Test
@UiThreadTest
public void dispatchOnInitialThread() throws InterruptedException {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
final Thread dispatchThread = Thread.currentThread();
GeckoResult.fromValue(42).then(new OnValueListener<Integer, Void>() {
@Override
public GeckoResult<Void> onValue(Integer value) throws Throwable {
assertThat("Thread should match", Thread.currentThread(),
equalTo(dispatchThread));
Looper.myLooper().quit();
return null;
}
});
Looper.loop();
}
});
thread.start();
thread.join();
}
@Test
@UiThreadTest
public void completeExceptionallyThreaded() {

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

@ -4,7 +4,7 @@
package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoResponse
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSessionSettings
import org.mozilla.geckoview.GeckoSession.TrackingProtectionDelegate;
@ -18,6 +18,7 @@ import org.mozilla.geckoview.test.util.Callbacks
import android.support.test.filters.MediumTest
import android.support.test.runner.AndroidJUnit4
import org.hamcrest.Matchers.*
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@ -101,16 +102,14 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1, order = [1])
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int,
flags: Int,
response: GeckoResponse<Boolean>) {
where: Int, flags: Int): GeckoResult<Boolean>? {
assertThat("Session should not be null", session, notNullValue())
assertThat("URI should not be null", uri, notNullValue())
assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
assertThat("Where should not be null", where, notNullValue())
assertThat("Where should match", where,
equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
response.respond(false)
return null
}
@AssertCalled(count = 1, order = [2])
@ -133,8 +132,8 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(false)
override fun onNewSession(session: GeckoSession, uri: String,
response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
return null
}
})
}
@ -296,13 +295,11 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1, order = [1])
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int,
flags: Int,
response: GeckoResponse<Boolean>) {
where: Int, flags: Int): GeckoResult<Boolean>? {
assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
assertThat("Where should match", where,
equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
response.respond(false)
return null
}
@AssertCalled(count = 1, order = [2])
@ -321,8 +318,8 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(false)
override fun onNewSession(session: GeckoSession, uri: String,
response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
return null
}
})
}
@ -347,13 +344,11 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1, order = [1])
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int,
flags: Int,
response: GeckoResponse<Boolean>) {
where: Int, flags: Int): GeckoResult<Boolean>? {
assertThat("URI should match", uri, endsWith(HELLO_HTML_PATH))
assertThat("Where should match", where,
equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
response.respond(false)
return null
}
@AssertCalled(count = 1, order = [2])
@ -372,8 +367,8 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(false)
override fun onNewSession(session: GeckoSession, uri: String,
response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
return null
}
})
@ -383,13 +378,11 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1, order = [1])
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int,
flags: Int,
response: GeckoResponse<Boolean>) {
where: Int, flags: Int): GeckoResult<Boolean>? {
assertThat("URI should match", uri, endsWith(HELLO2_HTML_PATH))
assertThat("Where should match", where,
equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_CURRENT))
response.respond(false)
return null
}
@AssertCalled(count = 1, order = [2])
@ -408,8 +401,8 @@ class NavigationDelegateTest : BaseSessionTest() {
}
@AssertCalled(false)
override fun onNewSession(session: GeckoSession, uri: String,
response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
return null
}
})
}
@ -418,10 +411,8 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 2)
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int,
flags: Int,
response: GeckoResponse<Boolean>) {
response.respond(uri.endsWith(HELLO_HTML_PATH))
where: Int, flags: Int): GeckoResult<Boolean> {
return GeckoResult.fromValue(uri.endsWith(HELLO_HTML_PATH))
}
})
@ -454,15 +445,18 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.session.waitUntilCalled(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1, order = [1])
override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int, flags: Int): GeckoResult<Boolean>? {
assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
assertThat("Where should be correct", where,
equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW))
return null
}
@AssertCalled(count = 1, order = [2])
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
return null
}
})
}
@ -481,15 +475,18 @@ class NavigationDelegateTest : BaseSessionTest() {
// We get two onLoadRequest calls for the link click,
// one when loading the URL and one when opening a new window.
@AssertCalled(count = 2, order = [1])
override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int, flags: Int): GeckoResult<Boolean>? {
assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
assertThat("Where should be correct", where,
equalTo(GeckoSession.NavigationDelegate.TARGET_WINDOW_NEW))
return null
}
@AssertCalled(count = 1, order = [2])
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
return null
}
})
}
@ -499,8 +496,8 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.session.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1)
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
response.respond(newSession)
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession> {
return GeckoResult.fromValue(newSession)
}
})
@ -574,9 +571,10 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.session.waitForPageStop()
sessionRule.session.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int, flags: Int): GeckoResult<Boolean> {
// Pretend we handled the target="_blank" link click.
response.respond(uri.endsWith(NEW_SESSION_CHILD_HTML_PATH))
return GeckoResult.fromValue(uri.endsWith(NEW_SESSION_CHILD_HTML_PATH))
}
})
@ -588,19 +586,22 @@ class NavigationDelegateTest : BaseSessionTest() {
// Assert that onNewSession was not called for the link click.
sessionRule.session.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 2)
override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int, flags: Int): GeckoResult<Boolean>? {
assertThat("URI must match", uri,
endsWith(forEachCall(NEW_SESSION_CHILD_HTML_PATH, NEW_SESSION_HTML_PATH)))
return null
}
@AssertCalled(count = 0)
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
return null
}
})
}
@WithDevToolsAPI
@Test(expected = IllegalArgumentException::class)
@Test(expected = GeckoResult.UncaughtException::class)
fun onNewSession_doesNotAllowOpened() {
// Disable popup blocker.
sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
@ -610,8 +611,8 @@ class NavigationDelegateTest : BaseSessionTest() {
sessionRule.session.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
@AssertCalled(count = 1)
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
response.respond(sessionRule.createOpenSession())
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession> {
return GeckoResult.fromValue(sessionRule.createOpenSession())
}
})

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

@ -4,7 +4,7 @@
package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoResponse
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.util.Callbacks
@ -62,15 +62,13 @@ class ProgressDelegateTest : BaseSessionTest() {
sessionRule.forCallbacksDuringWait(object : Callbacks.ProgressDelegate, Callbacks.NavigationDelegate {
@AssertCalled(count = 2)
override fun onLoadRequest(session: GeckoSession, uri: String,
where: Int,
flags: Int,
response: GeckoResponse<Boolean>) {
where: Int, flags: Int): GeckoResult<Boolean>? {
if (sessionRule.currentCall.counter == 1) {
assertThat("URI should be " + testUri, uri, equalTo(testUri));
} else {
assertThat("URI should be about:neterror", uri, startsWith("about:neterror"));
}
response.respond(false)
return null
}
@AssertCalled(count = 1)

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

@ -7,6 +7,7 @@ package org.mozilla.geckoview.test;
import org.mozilla.gecko.gfx.GeckoDisplay;
import org.mozilla.geckoview.GeckoResponse;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoSession;
import org.mozilla.geckoview.GeckoSessionSettings;
import org.mozilla.geckoview.GeckoView;
@ -50,16 +51,15 @@ public class TestRunnerActivity extends Activity {
}
@Override
public void onLoadRequest(GeckoSession session, String uri, int target,
int flags,
GeckoResponse<Boolean> response) {
public GeckoResult<Boolean> onLoadRequest(GeckoSession session, String uri, int target,
int flags) {
// Allow Gecko to load all URIs
response.respond(false);
return GeckoResult.fromValue(false);
}
@Override
public void onNewSession(GeckoSession session, String uri, GeckoResponse<GeckoSession> response) {
response.respond(createBackgroundSession(session.getSettings()));
public GeckoResult<GeckoSession> onNewSession(GeckoSession session, String uri) {
return GeckoResult.fromValue(createBackgroundSession(session.getSettings()));
}
};

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

@ -112,7 +112,7 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
sOnPageStop = GeckoSession.ProgressDelegate.class.getMethod(
"onPageStop", GeckoSession.class, boolean.class);
sOnNewSession = GeckoSession.NavigationDelegate.class.getMethod(
"onNewSession", GeckoSession.class, String.class, GeckoResponse.class);
"onNewSession", GeckoSession.class, String.class);
sOnCrash = GeckoSession.ContentDelegate.class.getMethod(
"onCrash", GeckoSession.class);
} catch (final NoSuchMethodException e) {
@ -924,6 +924,15 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
return mErrorCollector;
}
/**
* Get the current timeout value in milliseconds.
*
* @return The current timeout value in milliseconds.
*/
public long getTimeoutMillis() {
return mTimeoutMillis;
}
/**
* Assert a condition with junit.Assert or an error collector.
*
@ -1182,34 +1191,50 @@ public class GeckoSessionTestRule extends UiThreadTestRule {
}
}
if (call != null && sOnNewSession.equals(method)) {
// We're delegating an onNewSession call.
// Make sure we wait on the newly opened session, if any.
final GeckoSession oldSession = (GeckoSession) args[0];
@SuppressWarnings("unchecked")
final GeckoResponse<GeckoSession> realResponse =
(GeckoResponse<GeckoSession>) args[2];
args[2] = new GeckoResponse<GeckoSession>() {
@Override
public void respond(final GeckoSession newSession) {
realResponse.respond(newSession);
// `realResponse` has opened the session at this point, so wait on it.
if (oldSession.isOpen() && newSession != null) {
GeckoSessionTestRule.this.waitForOpenSession(newSession);
}
}
};
}
Object returnValue = null;
try {
mCurrentMethodCall = call;
return method.invoke((call != null) ? call.target
returnValue = method.invoke((call != null) ? call.target
: Callbacks.Default.INSTANCE, args);
} catch (final IllegalAccessException | InvocationTargetException e) {
throw unwrapRuntimeException(e);
} finally {
mCurrentMethodCall = null;
}
if (call == null || returnValue == null || !sOnNewSession.equals(method)) {
return returnValue;
}
// We're delegating an onNewSession call.
// Make sure we wait on the newly opened session, if any.
final GeckoSession oldSession = (GeckoSession) args[0];
@SuppressWarnings("unchecked")
final GeckoResult<GeckoSession> result = (GeckoResult<GeckoSession>)returnValue;
final GeckoResult<GeckoSession> tmpResult = new GeckoResult<>();
result.then(new OnValueListener<GeckoSession, Void>() {
@Override
public GeckoResult<Void> onValue(final GeckoSession newSession) throws Throwable {
tmpResult.complete(newSession);
// GeckoSession has already hooked up its then() listener earlier,
// so ours will run after. We can wait for the session to
// open here.
tmpResult.then(new OnValueListener<GeckoSession, Void>() {
@Override
public GeckoResult<Void> onValue(GeckoSession newSession) throws Throwable {
if (oldSession.isOpen() && newSession != null) {
GeckoSessionTestRule.this.waitForOpenSession(newSession);
}
return null;
}
});
return null;
}
});
return tmpResult;
}
};

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

@ -6,6 +6,7 @@
package org.mozilla.geckoview.test.util
import org.mozilla.geckoview.GeckoResponse
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import android.view.inputmethod.CursorAnchorInfo
@ -53,13 +54,12 @@ class Callbacks private constructor() {
}
override fun onLoadRequest(session: GeckoSession, uri: String, where: Int,
flags: Int,
response: GeckoResponse<Boolean>) {
response.respond(false)
flags: Int): GeckoResult<Boolean>? {
return null
}
override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
response.respond(null)
override fun onNewSession(session: GeckoSession, uri: String): GeckoResult<GeckoSession>? {
return null;
}
}

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

@ -12,7 +12,8 @@ import android.os.ParcelFileDescriptor;
interface IChildProcess {
int getPid();
boolean start(in IProcessManager procMan, in String[] args, in Bundle extras, int flags,
in ParcelFileDescriptor prefsPfd, in ParcelFileDescriptor ipcPfd,
in ParcelFileDescriptor prefsPfd, in ParcelFileDescriptor prefMapPfd,
in ParcelFileDescriptor ipcPfd,
in ParcelFileDescriptor crashReporterPfd,
in ParcelFileDescriptor crashAnnotationPfd);

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

@ -308,6 +308,10 @@ public class CrashReporterService extends IntentService {
OutputStream os = new GZIPOutputStream(conn.getOutputStream());
for (String key : extras.keySet()) {
if (key.equals(PAGE_URL_KEY)) {
continue;
}
if (!key.equals(SERVER_URL_KEY) && !key.equals(NOTES_KEY)) {
sendPart(os, boundary, key, extras.get(key));
}

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

@ -132,6 +132,7 @@ public class GeckoThread extends Thread {
/* package */ static final String EXTRA_ARGS = "args";
private static final String EXTRA_PREFS_FD = "prefsFd";
private static final String EXTRA_PREF_MAP_FD = "prefMapFd";
private static final String EXTRA_IPC_FD = "ipcFd";
private static final String EXTRA_CRASH_FD = "crashFd";
private static final String EXTRA_CRASH_ANNOTATION_FD = "crashAnnotationFd";
@ -153,7 +154,8 @@ public class GeckoThread extends Thread {
private synchronized boolean init(final GeckoProfile profile, final String[] args,
final Bundle extras, final int flags,
final int prefsFd, final int ipcFd,
final int prefsFd, final int prefMapFd,
final int ipcFd,
final int crashFd,
final int crashAnnotationFd) {
ThreadUtils.assertOnUiThread();
@ -169,6 +171,7 @@ public class GeckoThread extends Thread {
mExtras = (extras != null) ? new Bundle(extras) : new Bundle(3);
mExtras.putInt(EXTRA_PREFS_FD, prefsFd);
mExtras.putInt(EXTRA_PREF_MAP_FD, prefMapFd);
mExtras.putInt(EXTRA_IPC_FD, ipcFd);
mExtras.putInt(EXTRA_CRASH_FD, crashFd);
mExtras.putInt(EXTRA_CRASH_ANNOTATION_FD, crashAnnotationFd);
@ -181,18 +184,20 @@ public class GeckoThread extends Thread {
public static boolean initMainProcess(final GeckoProfile profile, final String[] args,
final Bundle extras, final int flags) {
return INSTANCE.init(profile, args, extras, flags, /* fd */ -1,
/* fd */ -1, /* fd */ -1, /* fd */ -1);
/* fd */ -1, /* fd */ -1, /* fd */ -1,
/* fd */ -1);
}
public static boolean initChildProcess(final String[] args,
final Bundle extras,
final int flags,
final int prefsFd,
final int prefMapFd,
final int ipcFd,
final int crashFd,
final int crashAnnotationFd) {
return INSTANCE.init(/* profile */ null, args, extras, flags,
prefsFd, ipcFd, crashFd, crashAnnotationFd);
prefsFd, prefMapFd, ipcFd, crashFd, crashAnnotationFd);
}
private static boolean canUseProfile(final Context context, final GeckoProfile profile,
@ -497,6 +502,7 @@ public class GeckoThread extends Thread {
// And go.
GeckoLoader.nativeRun(args,
mExtras.getInt(EXTRA_PREFS_FD, -1),
mExtras.getInt(EXTRA_PREF_MAP_FD, -1),
mExtras.getInt(EXTRA_IPC_FD, -1),
mExtras.getInt(EXTRA_CRASH_FD, -1),
mExtras.getInt(EXTRA_CRASH_ANNOTATION_FD, -1));

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

@ -456,7 +456,7 @@ public final class GeckoLoader {
public static native boolean verifyCRCs(String apkName);
// These methods are implemented in mozglue/android/APKOpen.cpp
public static native void nativeRun(String[] args, int prefsFd, int ipcFd, int crashFd, int crashAnnotationFd);
public static native void nativeRun(String[] args, int prefsFd, int prefMapFd, int ipcFd, int crashFd, int crashAnnotationFd);
private static native void loadGeckoLibsNative(String apkName);
private static native void loadSQLiteLibsNative(String apkName);
private static native void loadNSSLibsNative(String apkName);

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

@ -182,9 +182,10 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
@WrapForJNI
private static int start(final String type, final String[] args,
final int prefsFd, final int ipcFd,
final int prefsFd, final int prefMapFd,
final int ipcFd,
final int crashFd, final int crashAnnotationFd) {
return INSTANCE.start(type, args, prefsFd, ipcFd, crashFd, crashAnnotationFd, /* retry */ false);
return INSTANCE.start(type, args, prefsFd, prefMapFd, ipcFd, crashFd, crashAnnotationFd, /* retry */ false);
}
private int filterFlagsForChild(int flags) {
@ -192,7 +193,8 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
GeckoThread.FLAG_ENABLE_NATIVE_CRASHREPORTER);
}
private int start(final String type, final String[] args, final int prefsFd,
private int start(final String type, final String[] args,
final int prefsFd, final int prefMapFd,
final int ipcFd, final int crashFd,
final int crashAnnotationFd, final boolean retry) {
final ChildConnection connection = getConnection(type);
@ -203,11 +205,13 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
final Bundle extras = GeckoThread.getActiveExtras();
final ParcelFileDescriptor prefsPfd;
final ParcelFileDescriptor prefMapPfd;
final ParcelFileDescriptor ipcPfd;
final ParcelFileDescriptor crashPfd;
final ParcelFileDescriptor crashAnnotationPfd;
try {
prefsPfd = ParcelFileDescriptor.fromFd(prefsFd);
prefMapPfd = ParcelFileDescriptor.fromFd(prefMapFd);
ipcPfd = ParcelFileDescriptor.fromFd(ipcFd);
crashPfd = (crashFd >= 0) ? ParcelFileDescriptor.fromFd(crashFd) : null;
crashAnnotationPfd = (crashAnnotationFd >= 0) ? ParcelFileDescriptor.fromFd(crashAnnotationFd) : null;
@ -220,8 +224,8 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
boolean started = false;
try {
started = child.start(this, args, extras, flags, prefsPfd, ipcPfd, crashPfd,
crashAnnotationPfd);
started = child.start(this, args, extras, flags, prefsPfd, prefMapPfd,
ipcPfd, crashPfd, crashAnnotationPfd);
} catch (final RemoteException e) {
}
@ -232,7 +236,7 @@ public final class GeckoProcessManager extends IProcessManager.Stub {
}
Log.w(LOGTAG, "Attempting to kill running child " + type);
connection.unbind();
return start(type, args, prefsFd, ipcFd, crashFd, crashAnnotationFd, /* retry */ true);
return start(type, args, prefsFd, prefMapFd, ipcFd, crashFd, crashAnnotationFd, /* retry */ true);
}
try {

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

@ -62,6 +62,7 @@ public class GeckoServiceChildProcess extends Service {
final Bundle extras,
final int flags,
final ParcelFileDescriptor prefsPfd,
final ParcelFileDescriptor prefMapPfd,
final ParcelFileDescriptor ipcPfd,
final ParcelFileDescriptor crashReporterPfd,
final ParcelFileDescriptor crashAnnotationPfd) {
@ -74,6 +75,7 @@ public class GeckoServiceChildProcess extends Service {
}
final int prefsFd = prefsPfd.detachFd();
final int prefMapFd = prefMapPfd.detachFd();
final int ipcFd = ipcPfd.detachFd();
final int crashReporterFd = crashReporterPfd != null ?
crashReporterPfd.detachFd() : -1;
@ -83,8 +85,8 @@ public class GeckoServiceChildProcess extends Service {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
if (GeckoThread.initChildProcess(args, extras, flags, prefsFd, ipcFd, crashReporterFd,
crashAnnotationFd)) {
if (GeckoThread.initChildProcess(args, extras, flags, prefsFd, prefMapFd, ipcFd,
crashReporterFd, crashAnnotationFd)) {
GeckoThread.launch();
}
}

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

@ -2,6 +2,7 @@ package org.mozilla.geckoview;
import org.mozilla.gecko.util.ThreadUtils;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -148,6 +149,7 @@ public class GeckoResult<T> {
}
}
private Handler mHandler;
private boolean mComplete;
private T mValue;
private Throwable mError;
@ -159,18 +161,21 @@ public class GeckoResult<T> {
* {@link #completeExceptionally(Throwable)} in order to fulfill the result.
*/
public GeckoResult() {
if (ThreadUtils.isOnUiThread()) {
mHandler = ThreadUtils.getUiHandler();
} else {
mHandler = new Handler();
}
}
/**
* Construct a result from another result. Listeners are not copied.
* This constructs a result that is chained to the specified result.
*
* @param from The {@link GeckoResult} to copy.
* @param from The {@link GeckoResult} to copy.
*/
public GeckoResult(GeckoResult<T> from) {
this();
mComplete = from.mComplete;
mValue = from.mValue;
mError = from.mError;
completeFrom(from);
}
/**
@ -252,8 +257,10 @@ public class GeckoResult<T> {
/**
* Adds listeners to be called when the {@link GeckoResult} is completed either with
* a value or {@link Throwable}. Listeners will be invoked on the main thread. If the
* result is already complete when this method is called, listeners will be invoked in
* a value or {@link Throwable}. Listeners will be invoked on the thread where the
* {@link GeckoResult} was created, which must have a {@link Looper} installed.
*
* If the result is already complete when this method is called, listeners will be invoked in
* a future {@link Looper} iteration.
*
* @param valueListener An instance of {@link OnValueListener}, called when the
@ -317,7 +324,7 @@ public class GeckoResult<T> {
return;
}
ThreadUtils.getUiHandler().post(new Runnable() {
mHandler.post(new Runnable() {
@Override
public void run() {
if (mListeners != null) {
@ -338,7 +345,7 @@ public class GeckoResult<T> {
throw new IllegalStateException("Cannot dispatch unless result is complete");
}
ThreadUtils.getUiHandler().post(new Runnable() {
mHandler.post(new Runnable() {
@Override
public void run() {
runnable.run();

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

@ -202,36 +202,65 @@ public class GeckoSession extends LayerSession
final int where = convertGeckoTarget(message.getInt("where"));
final int flags = filterFlags(message.getInt("flags"));
delegate.onLoadRequest(GeckoSession.this, uri, where, flags,
new GeckoResponse<Boolean>() {
@Override
public void respond(Boolean handled) {
callback.sendSuccess(handled);
}
});
final GeckoResult<Boolean> result = delegate.onLoadRequest(GeckoSession.this,
uri, where, flags);
if (result == null) {
callback.sendSuccess(null);
return;
}
result.then(new GeckoResult.OnValueListener<Boolean, Void>() {
@Override
public GeckoResult<Void> onValue(Boolean value) throws Throwable {
ThreadUtils.assertOnUiThread();
callback.sendSuccess(value);
return null;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable exception) throws Throwable {
callback.sendError(exception.getMessage());
return null;
}
});
} else if ("GeckoView:OnNewSession".equals(event)) {
final String uri = message.getString("uri");
delegate.onNewSession(GeckoSession.this, uri,
new GeckoResponse<GeckoSession>() {
@Override
public void respond(GeckoSession session) {
if (session == null) {
callback.sendSuccess(null);
return;
}
final GeckoResult<GeckoSession> result = delegate.onNewSession(GeckoSession.this, uri);
if (result == null) {
callback.sendSuccess(null);
return;
}
if (session.isOpen()) {
throw new IllegalArgumentException("Must use an unopened GeckoSession instance");
}
if (GeckoSession.this.mWindow == null) {
callback.sendError("Session is not attached to a window");
} else {
session.open(GeckoSession.this.mWindow.runtime);
callback.sendSuccess(session.getId());
}
result.then(new GeckoResult.OnValueListener<GeckoSession, Void>() {
@Override
public GeckoResult<Void> onValue(GeckoSession session) throws Throwable {
ThreadUtils.assertOnUiThread();
if (session == null) {
callback.sendSuccess(null);
return null;
}
});
if (session.isOpen()) {
throw new IllegalArgumentException("Must use an unopened GeckoSession instance");
}
if (GeckoSession.this.mWindow == null) {
callback.sendError("Session is not attached to a window");
} else {
session.open(GeckoSession.this.mWindow.runtime);
callback.sendSuccess(session.getId());
}
return null;
}
}, new GeckoResult.OnExceptionListener<Void>() {
@Override
public GeckoResult<Void> onException(Throwable exception) throws Throwable {
callback.sendError(exception.getMessage());
return null;
}
});
}
}
};
@ -2223,14 +2252,16 @@ public class GeckoSession extends LayerSession
* @param flags The load request flags.
* One or more of {@link #LOAD_REQUEST_IS_USER_TRIGGERED
* LOAD_REQUEST_*}.
* @param response A response which will state whether or not the load
* was handled. If unhandled, Gecko will continue the
* load as normal.
*
* @return A {@link GeckoResult} with a boolean value which indicates whether or
* not the load was handled. If unhandled, Gecko will continue the
* load as normal. If handled (true value), Gecko will abandon the load.
* A null return value is interpreted as false (unhandled).
*/
void onLoadRequest(GeckoSession session, String uri,
@TargetWindow int target,
@LoadRequestFlags int flags,
GeckoResponse<Boolean> response);
@Nullable GeckoResult<Boolean> onLoadRequest(@NonNull GeckoSession session,
@NonNull String uri,
@TargetWindow int target,
@LoadRequestFlags int flags);
/**
* A request has been made to open a new session. The URI is provided only for
@ -2240,9 +2271,11 @@ public class GeckoSession extends LayerSession
* @param session The GeckoSession that initiated the callback.
* @param uri The URI to be loaded.
*
* @param response A Response which will hold the returned GeckoSession
* @return A {@link GeckoResult} which holds the returned GeckoSession. May be null, in
* which case the request for a new window by web content will fail. e.g.,
* <code>window.open()</code> will return null.
*/
void onNewSession(GeckoSession session, String uri, GeckoResponse<GeckoSession> response);
@Nullable GeckoResult<GeckoSession> onNewSession(@NonNull GeckoSession session, @NonNull String uri);
}
/**

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

@ -7,6 +7,7 @@ package org.mozilla.geckoview_example;
import org.mozilla.geckoview.BasicSelectionActionDelegate;
import org.mozilla.geckoview.GeckoResponse;
import org.mozilla.geckoview.GeckoResult;
import org.mozilla.geckoview.GeckoRuntime;
import org.mozilla.geckoview.GeckoRuntimeSettings;
import org.mozilla.geckoview.GeckoSession;
@ -561,18 +562,16 @@ public class GeckoViewActivity extends AppCompatActivity {
}
@Override
public void onLoadRequest(final GeckoSession session, final String uri,
final int target, final int flags,
GeckoResponse<Boolean> response) {
public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String uri,
final int target, final int flags) {
Log.d(LOGTAG, "onLoadRequest=" + uri + " where=" + target +
" flags=" + flags);
response.respond(false);
return GeckoResult.fromValue(false);
}
@Override
public void onNewSession(final GeckoSession session, final String uri, GeckoResponse<GeckoSession> response) {
public GeckoResult<GeckoSession> onNewSession(final GeckoSession session, final String uri) {
GeckoSession newSession = new GeckoSession(session.getSettings());
response.respond(newSession);
Intent intent = new Intent(GeckoViewActivity.this, SessionActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
@ -581,6 +580,8 @@ public class GeckoViewActivity extends AppCompatActivity {
intent.putExtra("session", newSession);
startActivity(intent);
return GeckoResult.fromValue(newSession);
}
}

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

@ -139,7 +139,13 @@ class GeckoViewContent extends GeckoViewModule {
warn `Failed to save state due to missing callback`;
return;
}
this._saveStateCallbacks.get(aMsg.data.id).onSuccess(aMsg.data.state);
const callback = this._saveStateCallbacks.get(aMsg.data.id);
if (aMsg.data.error) {
callback.onError(aMsg.data.error);
} else {
callback.onSuccess(aMsg.data.state);
}
this._saveStateCallbacks.delete(aMsg.data.id);
break;
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -39,11 +39,44 @@ class Pref;
class PrefValue;
} // namespace dom
namespace ipc {
class FileDescriptor;
} // namespace ipc
struct PrefsSizes;
// Xlib.h defines Bool as a macro constant. Don't try to define this enum if
// it's already been included.
#ifndef Bool
// Keep this in sync with PrefType in parser/src/lib.rs.
enum class PrefType : uint8_t
{
None = 0, // only used when neither the default nor user value is set
String = 1,
Int = 2,
Bool = 3,
};
#endif
#ifdef XP_UNIX
// We need to send two shared memory descriptors to every child process:
//
// 1) A read-only/write-protected snapshot of the initial state of the
// preference database. This memory is shared between all processes, and
// therefore cannot be modified once it has been created.
//
// 2) A set of changes on top of the snapshot, containing the current values of
// all preferences which have changed since it was created.
//
// Since the second set will be different for every process, and the first set
// cannot be modified, it is unfortunately not possible to combine them into a
// single file descriptor.
//
// XXX: bug 1440207 is about improving how fixed fds such as this are used.
static const int kPrefsFileDescriptor = 8;
static const int kPrefMapFileDescriptor = 9;
#endif
// Keep this in sync with PrefType in parser/src/lib.rs.
@ -481,6 +514,9 @@ public:
static void SerializePreferences(nsCString& aStr);
static void DeserializePreferences(char* aStr, size_t aPrefsLen);
static mozilla::ipc::FileDescriptor EnsureSnapshot(size_t* aSize);
static void InitSnapshot(const mozilla::ipc::FileDescriptor&, size_t aSize);
// When a single pref is changed in the parent process, these methods are
// used to pass the update to content processes.
static void GetPreference(dom::Pref* aPref);

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

@ -0,0 +1,252 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SharedPrefMap.h"
#include "mozilla/dom/ipc/MemMapSnapshot.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/ipc/FileDescriptor.h"
using namespace mozilla::loader;
namespace mozilla {
using namespace ipc;
static inline size_t
GetAlignmentOffset(size_t aOffset, size_t aAlign)
{
auto mod = aOffset % aAlign;
return mod ? aAlign - mod : 0;
}
SharedPrefMap::SharedPrefMap(const FileDescriptor& aMapFile, size_t aMapSize)
{
auto result = mMap.initWithHandle(aMapFile, aMapSize);
MOZ_RELEASE_ASSERT(result.isOk());
// We return literal nsCStrings pointing to the mapped data for preference
// names and string values, which means that we may still have references to
// the mapped data even after this instance is destroyed. That means that we
// need to keep the mapping alive until process shutdown, in order to be safe.
mMap.setPersistent();
}
SharedPrefMap::SharedPrefMap(SharedPrefMapBuilder&& aBuilder)
{
auto result = aBuilder.Finalize(mMap);
MOZ_RELEASE_ASSERT(result.isOk());
mMap.setPersistent();
}
mozilla::ipc::FileDescriptor
SharedPrefMap::CloneFileDescriptor() const
{
return mMap.cloneHandle();
}
bool
SharedPrefMap::Has(const char* aKey) const
{
size_t index;
return Find(aKey, &index);
}
Maybe<const SharedPrefMap::Pref>
SharedPrefMap::Get(const char* aKey) const
{
Maybe<const Pref> result;
size_t index;
if (Find(aKey, &index)) {
result.emplace(Pref{ this, &Entries()[index] });
}
return result;
}
bool
SharedPrefMap::Find(const char* aKey, size_t* aIndex) const
{
const auto& keys = KeyTable();
return BinarySearchIf(Entries(),
0,
EntryCount(),
[&](const Entry& aEntry) {
return strcmp(aKey, keys.GetBare(aEntry.mKey));
},
aIndex);
}
void
SharedPrefMapBuilder::Add(const char* aKey,
const Flags& aFlags,
bool aDefaultValue,
bool aUserValue)
{
mEntries.AppendElement(Entry{
aKey,
mKeyTable.Add(aKey),
{ aDefaultValue, aUserValue },
uint8_t(PrefType::Bool),
aFlags.mHasDefaultValue,
aFlags.mHasUserValue,
aFlags.mIsSticky,
aFlags.mIsLocked,
});
}
void
SharedPrefMapBuilder::Add(const char* aKey,
const Flags& aFlags,
int32_t aDefaultValue,
int32_t aUserValue)
{
ValueIdx index;
if (aFlags.mHasUserValue) {
index = mIntValueTable.Add(aDefaultValue, aUserValue);
} else {
index = mIntValueTable.Add(aDefaultValue);
}
mEntries.AppendElement(Entry{
aKey,
mKeyTable.Add(aKey),
{ index },
uint8_t(PrefType::Int),
aFlags.mHasDefaultValue,
aFlags.mHasUserValue,
aFlags.mIsSticky,
aFlags.mIsLocked,
});
}
void
SharedPrefMapBuilder::Add(const char* aKey,
const Flags& aFlags,
const nsCString& aDefaultValue,
const nsCString& aUserValue)
{
ValueIdx index;
StringTableEntry defaultVal = mValueStringTable.Add(aDefaultValue);
if (aFlags.mHasUserValue) {
StringTableEntry userVal = mValueStringTable.Add(aUserValue);
index = mStringValueTable.Add(defaultVal, userVal);
} else {
index = mStringValueTable.Add(defaultVal);
}
mEntries.AppendElement(Entry{
aKey,
mKeyTable.Add(aKey),
{ index },
uint8_t(PrefType::String),
aFlags.mHasDefaultValue,
aFlags.mHasUserValue,
aFlags.mIsSticky,
aFlags.mIsLocked,
});
}
Result<Ok, nsresult>
SharedPrefMapBuilder::Finalize(loader::AutoMemMap& aMap)
{
using Header = SharedPrefMap::Header;
// Create an array of entry pointers for the entry array, and sort it by
// preference name prior to serialization, so that entries can be looked up
// using binary search.
nsTArray<Entry*> entries(mEntries.Length());
for (auto& entry : mEntries) {
entries.AppendElement(&entry);
}
entries.Sort([](const Entry* aA, const Entry* aB) {
return strcmp(aA->mKeyString, aB->mKeyString);
});
Header header = { uint32_t(entries.Length()) };
size_t offset = sizeof(header);
offset += GetAlignmentOffset(offset, alignof(Header));
offset += entries.Length() * sizeof(SharedPrefMap::Entry);
header.mKeyStrings.mOffset = offset;
header.mKeyStrings.mSize = mKeyTable.Size();
offset += header.mKeyStrings.mSize;
offset += GetAlignmentOffset(offset, mIntValueTable.Alignment());
header.mUserIntValues.mOffset = offset;
header.mUserIntValues.mSize = mIntValueTable.UserSize();
offset += header.mUserIntValues.mSize;
offset += GetAlignmentOffset(offset, mIntValueTable.Alignment());
header.mDefaultIntValues.mOffset = offset;
header.mDefaultIntValues.mSize = mIntValueTable.DefaultSize();
offset += header.mDefaultIntValues.mSize;
offset += GetAlignmentOffset(offset, mStringValueTable.Alignment());
header.mUserStringValues.mOffset = offset;
header.mUserStringValues.mSize = mStringValueTable.UserSize();
offset += header.mUserStringValues.mSize;
offset += GetAlignmentOffset(offset, mStringValueTable.Alignment());
header.mDefaultStringValues.mOffset = offset;
header.mDefaultStringValues.mSize = mStringValueTable.DefaultSize();
offset += header.mDefaultStringValues.mSize;
header.mValueStrings.mOffset = offset;
header.mValueStrings.mSize = mValueStringTable.Size();
offset += header.mValueStrings.mSize;
MemMapSnapshot mem;
MOZ_TRY(mem.Init(offset));
auto headerPtr = mem.Get<Header>();
headerPtr[0] = header;
auto* entryPtr = reinterpret_cast<SharedPrefMap::Entry*>(&headerPtr[1]);
for (auto* entry : entries) {
*entryPtr = {
entry->mKey, GetValue(*entry),
entry->mType, entry->mHasDefaultValue,
entry->mHasUserValue, entry->mIsSticky,
entry->mIsLocked,
};
entryPtr++;
}
auto ptr = mem.Get<uint8_t>();
mKeyTable.Write(
{ &ptr[header.mKeyStrings.mOffset], header.mKeyStrings.mSize });
mValueStringTable.Write(
{ &ptr[header.mValueStrings.mOffset], header.mValueStrings.mSize });
mIntValueTable.WriteDefaultValues(
{ &ptr[header.mDefaultIntValues.mOffset], header.mDefaultIntValues.mSize });
mIntValueTable.WriteUserValues(
{ &ptr[header.mUserIntValues.mOffset], header.mUserIntValues.mSize });
mStringValueTable.WriteDefaultValues(
{ &ptr[header.mDefaultStringValues.mOffset],
header.mDefaultStringValues.mSize });
mStringValueTable.WriteUserValues(
{ &ptr[header.mUserStringValues.mOffset], header.mUserStringValues.mSize });
mKeyTable.Clear();
mValueStringTable.Clear();
mIntValueTable.Clear();
mStringValueTable.Clear();
mEntries.Clear();
return mem.Finalize(aMap);
}
} // mozilla

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

@ -0,0 +1,895 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=8 sts=4 et sw=4 tw=99: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef dom_ipc_SharedPrefMap_h
#define dom_ipc_SharedPrefMap_h
#include "mozilla/AutoMemMap.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/Preferences.h"
#include "mozilla/Result.h"
#include "mozilla/dom/ipc/StringTable.h"
#include "nsDataHashtable.h"
namespace mozilla {
// The approximate number of preferences expected to be in an ordinary
// preferences database.
//
// This number is used to determine initial allocation sizes for data structures
// when building the shared preference map, and should be slightly higher than
// the expected number of preferences in an ordinary database to avoid
// unnecessary reallocations/rehashes.
constexpr size_t kExpectedPrefCount = 4000;
class SharedPrefMapBuilder;
// This class provides access to a compact, read-only copy of a preference
// database, backed by a shared memory buffer which can be shared between
// processes. All state data for the database is stored in the shared memory
// region, so individual instances require no dynamic memory allocation.
//
// Further, all strings returned from this API are nsLiteralCStrings with
// pointers into the shared memory region, which means that they can be copied
// into new nsCString instances without additional allocations. For instance,
// the following (where `pref` is a Pref object) will not cause any string
// copies, memory allocations, or atomic refcount changes:
//
// nsCString prefName(pref.NameString());
//
// whereas if we returned a nsDependentCString or a dynamically allocated
// nsCString, it would.
//
// The set of entries is stored in sorted order by preference name, so look-ups
// are done by binary search. This means that look-ups have O(log n) complexity,
// rather than the O(1) complexity of a dynamic hashtable. Consumers should keep
// this in mind when planning their accesses.
//
// Important: The mapped memory created by this class is persistent. Once an
// instance has been initialized, the memory that it allocates can never be
// freed before process shutdown. Do not use it for short-lived mappings.
class SharedPrefMap
{
using FileDescriptor = mozilla::ipc::FileDescriptor;
friend class SharedPrefMapBuilder;
// Describes a block of memory within the shared memory region.
struct DataBlock
{
// The byte offset from the start of the shared memory region to the start
// of the block.
size_t mOffset;
// The size of the block, in bytes. This is typically used only for bounds
// checking in debug builds.
size_t mSize;
};
// Describes the contents of the shared memory region, which is laid-out as
// follows:
//
// - The Header struct
//
// - An array of Entry structs with mEntryCount elements, lexicographically
// sorted by preference name.
//
// - A set of data blocks, with offsets and sizes described by the DataBlock
// entries in the header, described below.
//
// Each entry stores its name string and values as indices into these blocks,
// as documented in the Entry struct, but with some important optimizations:
//
// - Boolean values are always stored inline. Both the default and user
// values can be retrieved directly from the entry. Other types have only
// one value index, and their values appear at the same indices in the
// default and user value arrays.
//
// Aside from reducing our memory footprint, this space-efficiency means
// that we can fit more entries in the CPU cache at once, and reduces the
// number of likely cache misses during lookups.
//
// - Key strings are stored in a separate string table from value strings. As
// above, this makes it more likely that the strings we need will be
// available in the CPU cache during lookups by not interleaving them with
// extraneous data.
//
// - Default and user values are stored in separate arrays. Entries with user
// values always appear before entries with default values in the value
// arrays, and entries without user values do not have entries in the user
// array at all. Since the same index is used for both arrays, this means
// that entries with a default value but no user value do not allocate any
// space to store their user value.
//
// - For preferences with no user value, the entries in the default value are
// de-duplicated. All preferences with the same default value (and no user
// value) point to the same index in the default value array.
//
//
// For example, a preference database containing:
//
// +---------+-------------------------------+-------------------------------+
// | Name | Default Value | User Value | |
// +---------+---------------+---------------+-------------------------------+
// | string1 | "meh" | "hem" | |
// | string2 | | "b" | |
// | string3 | "a" | | |
// | string4 | "foo" | | |
// | string5 | "foo" | | |
// | string6 | "meh" | | |
// +---------+---------------+---------------+-------------------------------+
// | bool1 | false | true | |
// | bool2 | | false | |
// | bool3 | true | | |
// +---------+---------------+---------------+-------------------------------+
// | int1 | 18 | 16 | |
// | int2 | | 24 | |
// | int3 | 42 | | |
// | int4 | 12 | | |
// | int5 | 12 | | |
// | int6 | 18 | | |
// +---------+---------------+---------------+-------------------------------+
//
// Results in a database that looks like:
//
// +-------------------------------------------------------------------------+
// | Header: |
// +-------------------------------------------------------------------------+
// | mEntryCount = 15 |
// | ... |
// +-------------------------------------------------------------------------+
//
// +-------------------------------------------------------------------------+
// | Key strings: |
// +--------+----------------------------------------------------------------+
// | Offset | Value |
// +--------+----------------------------------------------------------------+
// | 0 | string1\0 |
// | 8 | string2\0 |
// | 16 | string3\0 |
// | 24 | string4\0 |
// | 32 | string5\0 |
// | 40 | string6\0 |
// | 48 | bool1\0 |
// | 54 | bool2\0 |
// | 60 | bool3\0 |
// | 66 | int1\0 |
// | 71 | int2\0 |
// | 76 | int3\0 |
// | 81 | int4\0 |
// | 86 | int6\0 |
// | 91 | int6\0 |
// +--------+----------------------------------------------------------------+
//
// +-------------------------------------------------------------------------+
// | Entries: |
// +---------------------+------+------------+------------+------------------+
// | Key[1] | Type | HasDefault | HasUser | Value |
// +---------------------+------+------------+------------+------------------+
// | K["bool1", 48, 5] | 3 | true | true | { false, true } |
// | K["bool2", 54, 5] | 3 | false | true | { 0, false } |
// | K["bool3", 60, 5] | 3 | true | false | { true, 0 } |
// | K["int1", 66, 4] | 2 | true | true | 0 |
// | K["int2", 71, 4] | 2 | false | true | 1 |
// | K["int3", 76, 4] | 2 | true | false | 2 |
// | K["int4", 81, 4] | 2 | true | false | 3 |
// | K["int5", 86, 4] | 2 | true | false | 3 |
// | K["int6", 91, 4] | 2 | true | false | 4 |
// | K["string1", 0, 6] | 1 | true | true | 0 |
// | K["string2", 8, 6] | 1 | false | true | 1 |
// | K["string3", 16, 6] | 1 | true | false | 2 |
// | K["string4", 24, 6] | 1 | true | false | 3 |
// | K["string5", 32, 6] | 1 | true | false | 3 |
// | K["string6", 40, 6] | 1 | true | false | 4 |
// +---------------------+------+------------+------------+------------------+
// | [1]: Encoded as an offset into the key table and a length. Specified |
// | as K[string, offset, length] for clarity. |
// +-------------------------------------------------------------------------+
//
// +------------------------------------+------------------------------------+
// | User integer values | Default integer values |
// +-------+----------------------------+-------+----------------------------+
// | Index | Contents | Index | Contents |
// +-------+----------------------------+-------+----------------------------+
// | 0 | 16 | 0 | 18 |
// | 1 | 24 | 1 | |
// | | | 2 | 42 |
// | | | 3 | 12 |
// | | | 4 | 18 |
// +-------+----------------------------+-------+----------------------------+
// | * Note: Tables are laid out sequentially in memory, but displayed |
// | here side-by-side for clarity. |
// +-------------------------------------------------------------------------+
//
// +------------------------------------+------------------------------------+
// | User string values | Default string values |
// +-------+----------------------------+-------+----------------------------+
// | Index | Contents[1] | Index | Contents[1] |
// +-------+----------------------------+-------+----------------------------+
// | 0 | V["hem", 0, 3] | 0 | V["meh", 4, 3] |
// | 1 | V["b", 8, 1] | 1 | |
// | | | 2 | V["a", 10, 1] |
// | | | 3 | V["foo", 12, 3] |
// | | | 4 | V["meh", 4, 3] |
// |-------+----------------------------+-------+----------------------------+
// | [1]: Encoded as an offset into the value table and a length. Specified |
// | as V[string, offset, length] for clarity. |
// +-------------------------------------------------------------------------+
// | * Note: Tables are laid out sequentially in memory, but displayed |
// | here side-by-side for clarity. |
// +-------------------------------------------------------------------------+
//
// +-------------------------------------------------------------------------+
// | Value strings: |
// +--------+----------------------------------------------------------------+
// | Offset | Value |
// +--------+----------------------------------------------------------------+
// | 0 | hem\0 |
// | 4 | meh\0 |
// | 8 | b\0 |
// | 10 | a\0 |
// | 12 | foo\0 |
// +--------+----------------------------------------------------------------+
struct Header
{
// The number of entries in this map.
uint32_t mEntryCount;
// The StringTable data block for preference name strings, which act as keys
// in the map.
DataBlock mKeyStrings;
// The int32_t arrays of user and default int preference values. Entries in
// the map store their values as indices into these arrays.
DataBlock mUserIntValues;
DataBlock mDefaultIntValues;
// The StringTableEntry arrays of user and default string preference values.
//
// Strings are stored as StringTableEntry structs with character offsets
// into the mValueStrings string table and their corresponding lenghts.
//
// Entries in the map, likewise, store their string values as indices into
// these arrays.
DataBlock mUserStringValues;
DataBlock mDefaultStringValues;
// The StringTable data block for string preference values, referenced by
// the above two data blocks.
DataBlock mValueStrings;
};
using StringTableEntry = mozilla::dom::ipc::StringTableEntry;
// Represents a preference value, as either a pair of boolean values, or an
// index into one of the above value arrays.
union Value {
Value(bool aDefaultValue, bool aUserValue)
: mDefaultBool(aDefaultValue)
, mUserBool(aUserValue)
{
}
MOZ_IMPLICIT Value(uint16_t aIndex)
: mIndex(aIndex)
{
}
// The index of this entry in the value arrays.
//
// User and default preference values have the same indices in their
// respective arrays. However, entries without a user value are not
// guaranteed to have space allocated for them in the user value array, and
// likewise for preferences without default values in the default value
// array. This means that callers must only access value entries for entries
// which claim to have a value of that type.
uint16_t mIndex;
struct
{
bool mDefaultBool;
bool mUserBool;
};
};
// Represents a preference entry in the map, containing its name, type info,
// flags, and a reference to its value.
struct Entry
{
// A pointer to the preference name in the KeyTable string table.
StringTableEntry mKey;
// The preference's value, either as a pair of booleans, or an index into
// the value arrays. Please see the documentation for the Value struct
// above.
Value mValue;
// The preference's type, as a PrefType enum value. This must *never* be
// PrefType::None for values in a shared array.
uint8_t mType : 2;
// True if the preference has a default value. Callers must not attempt to
// access the entry's default value if this is false.
uint8_t mHasDefaultValue : 1;
// True if the preference has a user value. Callers must not attempt to
// access the entry's user value if this is false.
uint8_t mHasUserValue : 1;
// True if the preference is sticky, as defined by the preference service.
uint8_t mIsSticky : 1;
// True if the preference is locked, as defined by the preference service.
uint8_t mIsLocked : 1;
};
public:
NS_INLINE_DECL_REFCOUNTING(SharedPrefMap)
// A temporary wrapper class for accessing entries in the array. Instances of
// this class are valid as long as SharedPrefMap instance is alive, but
// generally should not be stored long term, or allocated on the heap.
//
// The class is implemented as two pointers, one to the SharedPrefMap
// instance, and one to the Entry that corresponds to the preference, and is
// meant to be cheaply returned by value from preference lookups and
// iterators. All property accessors lazily fetch the appropriate values from
// the shared memory region.
class MOZ_STACK_CLASS Pref final
{
public:
const char* Name() const { return mMap->KeyTable().GetBare(mEntry->mKey); }
nsCString NameString() const { return mMap->KeyTable().Get(mEntry->mKey); }
PrefType Type() const
{
MOZ_ASSERT(PrefType(mEntry->mType) != PrefType::None);
return PrefType(mEntry->mType);
}
bool HasDefaultValue() const { return mEntry->mHasDefaultValue; }
bool HasUserValue() const { return mEntry->mHasUserValue; }
bool IsLocked() const { return mEntry->mIsLocked; }
bool IsSticky() const { return mEntry->mIsSticky; }
bool GetBoolValue(PrefValueKind aKind = PrefValueKind::User) const
{
MOZ_ASSERT(Type() == PrefType::Bool);
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue());
return aKind == PrefValueKind::Default ? mEntry->mValue.mDefaultBool
: mEntry->mValue.mUserBool;
}
int32_t GetIntValue(PrefValueKind aKind = PrefValueKind::User) const
{
MOZ_ASSERT(Type() == PrefType::Int);
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue());
return aKind == PrefValueKind::Default
? mMap->DefaultIntValues()[mEntry->mValue.mIndex]
: mMap->UserIntValues()[mEntry->mValue.mIndex];
}
private:
const StringTableEntry& GetStringEntry(PrefValueKind aKind) const
{
MOZ_ASSERT(Type() == PrefType::String);
MOZ_ASSERT(aKind == PrefValueKind::Default ? HasDefaultValue()
: HasUserValue());
return aKind == PrefValueKind::Default
? mMap->DefaultStringValues()[mEntry->mValue.mIndex]
: mMap->UserStringValues()[mEntry->mValue.mIndex];
}
public:
nsCString GetStringValue(PrefValueKind aKind = PrefValueKind::User) const
{
return mMap->ValueTable().Get(GetStringEntry(aKind));
}
const char* GetBareStringValue(
PrefValueKind aKind = PrefValueKind::User) const
{
return mMap->ValueTable().GetBare(GetStringEntry(aKind));
}
// Returns the entry's index in the map, as understood by GetKeyAt() and
// GetValueAt().
size_t Index() const { return mEntry - mMap->Entries().get(); }
bool operator==(const Pref& aPref) const { return mEntry == aPref.mEntry; }
bool operator!=(const Pref& aPref) const { return !(*this == aPref); }
// This is odd, but necessary in order for the C++ range iterator protocol
// to work here.
Pref& operator*() { return *this; }
// Updates this wrapper to point to the next entry in the map. This should
// not be attempted unless Index() is less than the map's Count().
Pref& operator++()
{
mEntry++;
return *this;
}
Pref(const Pref& aPref) = default;
protected:
friend class SharedPrefMap;
Pref(const SharedPrefMap* aPrefMap, const Entry* aEntry)
: mMap(aPrefMap)
, mEntry(aEntry)
{
}
private:
const SharedPrefMap* const mMap;
const Entry* mEntry;
};
// Note: These constructors are infallible, because the preference database is
// critical to platform functionality, and we cannot operate without it.
SharedPrefMap(const FileDescriptor&, size_t);
explicit SharedPrefMap(SharedPrefMapBuilder&&);
// Searches for the given preference in the map, and returns true if it
// exists.
bool Has(const char* aKey) const;
bool Has(const nsCString& aKey) const { return Has(aKey.get()); }
// Searches for the given preference in the map, and if it exists, returns
// a Some<Pref> containing its details.
Maybe<const Pref> Get(const char* aKey) const;
Maybe<const Pref> Get(const nsCString& aKey) const { return Get(aKey.get()); }
private:
// Searches for an entry for the given key. If found, returns true, and
// places its index in the entry array in aIndex.
bool Find(const char* aKey, size_t* aIndex) const;
public:
// Returns the number of entries in the map.
uint32_t Count() const { return EntryCount(); }
// Returns the string entry at the given index. Keys are guaranteed to be
// sorted lexicographically.
//
// The given index *must* be less than the value returned by Count().
//
// The returned value is a literal string which references the mapped memory
// region.
nsCString GetKeyAt(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < Count());
return KeyTable().Get(Entries()[aIndex].mKey);
}
// Returns the value for the entry at the given index.
//
// The given index *must* be less than the value returned by Count().
//
// The returned value is valid for the lifetime of this map instance.
const Pref GetValueAt(uint32_t aIndex) const
{
MOZ_ASSERT(aIndex < Count());
return UncheckedGetValueAt(aIndex);
}
private:
// Returns a wrapper with a pointer to an entry without checking its bounds.
// This should only be used by range iterators, to check their end positions.
//
// Note: In debug builds, the RangePtr returned by entries will still assert
// that aIndex is no more than 1 past the last element in the array, since it
// also takes into account the ranged iteration use case.
Pref UncheckedGetValueAt(uint32_t aIndex) const
{
return { this, (Entries() + aIndex).get() };
}
public:
// C++ range iterator protocol. begin() and end() return references to the
// first and last entries in the array. The begin wrapper can be incremented
// until it matches the last element in the array, at which point it becomes
// invalid and the iteration is over.
Pref begin() const { return UncheckedGetValueAt(0); }
Pref end() const { return UncheckedGetValueAt(Count()); }
// A cosmetic helper for range iteration. Returns a reference value from a
// pointer to this instance so that its .begin() and .end() methods can be
// accessed in a ranged for loop. `map->Iter()` is equivalent to `*map`, but
// makes its purpose slightly clearer.
const SharedPrefMap& Iter() const { return *this; }
// Returns a copy of the read-only file descriptor which backs the shared
// memory region for this map. The file descriptor may be passed between
// processes, and used to construct new instances of SharedPrefMap with
// the same data as this instance.
FileDescriptor CloneFileDescriptor() const;
// Returns the size of the mapped memory region. This size must be passed to
// the constructor when mapping the shared region in another process.
size_t MapSize() const { return mMap.size(); }
protected:
~SharedPrefMap() = default;
private:
template<typename T>
using StringTable = mozilla::dom::ipc::StringTable<T>;
// Type-safe getters for values in the shared memory region:
const Header& GetHeader() const { return mMap.get<Header>()[0]; }
RangedPtr<const Entry> Entries() const
{
return { reinterpret_cast<const Entry*>(&GetHeader() + 1), EntryCount() };
}
uint32_t EntryCount() const { return GetHeader().mEntryCount; }
template<typename T>
RangedPtr<const T> GetBlock(const DataBlock& aBlock) const
{
return RangedPtr<uint8_t>(&mMap.get<uint8_t>()[aBlock.mOffset],
aBlock.mSize)
.ReinterpretCast<const T>();
}
RangedPtr<const int32_t> DefaultIntValues() const
{
return GetBlock<int32_t>(GetHeader().mDefaultIntValues);
}
RangedPtr<const int32_t> UserIntValues() const
{
return GetBlock<int32_t>(GetHeader().mUserIntValues);
}
RangedPtr<const StringTableEntry> DefaultStringValues() const
{
return GetBlock<StringTableEntry>(GetHeader().mDefaultStringValues);
}
RangedPtr<const StringTableEntry> UserStringValues() const
{
return GetBlock<StringTableEntry>(GetHeader().mUserStringValues);
}
StringTable<nsCString> KeyTable() const
{
auto& block = GetHeader().mKeyStrings;
return { { &mMap.get<uint8_t>()[block.mOffset], block.mSize } };
}
StringTable<nsCString> ValueTable() const
{
auto& block = GetHeader().mValueStrings;
return { { &mMap.get<uint8_t>()[block.mOffset], block.mSize } };
}
loader::AutoMemMap mMap;
};
// A helper class which builds the contiguous look-up table used by
// SharedPrefMap. Each preference in the final map is added to the builder,
// before it is finalized and transformed into a read-only snapshot.
class MOZ_RAII SharedPrefMapBuilder
{
public:
SharedPrefMapBuilder() = default;
// The set of flags for the preference, as documented in SharedPrefMap::Entry.
struct Flags
{
uint8_t mHasDefaultValue : 1;
uint8_t mHasUserValue : 1;
uint8_t mIsSticky : 1;
uint8_t mIsLocked : 1;
};
void Add(const char* aKey,
const Flags& aFlags,
bool aDefaultValue,
bool aUserValue);
void Add(const char* aKey,
const Flags& aFlags,
int32_t aDefaultValue,
int32_t aUserValue);
void Add(const char* aKey,
const Flags& aFlags,
const nsCString& aDefaultValue,
const nsCString& aUserValue);
// Finalizes the binary representation of the map, writes it to a shared
// memory region, and then initializes the given AutoMemMap with a reference
// to the read-only copy of it.
//
// This should generally not be used directly by callers. The
// SharedPrefMapBuilder instance should instead be passed to the SharedPrefMap
// constructor as a move reference.
Result<Ok, nsresult> Finalize(loader::AutoMemMap& aMap);
private:
using StringTableEntry = mozilla::dom::ipc::StringTableEntry;
template<typename T, typename U>
using StringTableBuilder = mozilla::dom::ipc::StringTableBuilder<T, U>;
// An opaque descriptor of the index of a preference entry in a value array,
// which can be converted numeric index after the ValueTableBuilder is
// finalized.
struct ValueIdx
{
// The relative index of the entry, based on its class. Entries for
// preferences with user values appear at the value arrays. Entries with
// only default values begin after the last entry with a user value.
uint16_t mIndex;
bool mHasUserValue;
};
// A helper class for building default and user value arrays for preferences.
//
// As described in the SharedPrefMap class, this helper optimizes the way that
// it builds its value arrays, in that:
//
// - It stores value entries for all preferences with user values before
// entries for preferences with only default values, and allocates no
// entries for preferences with only default values in the user value array.
// Since most preferences have only default values, this dramatically
// reduces the space required for value storage.
//
// - For preferences with only default values, it de-duplicates value entries,
// and returns the same indices for all preferences with the same value.
//
// One important complication of this approach is that it means we cannot know
// the final index of any entry with only a default value until all entries
// have been added to the builder, since it depends on the final number of
// user entries in the output.
//
// To deal with this, when entries are added, we return an opaque ValueIndex
// struct, from which we can calculate the final index after the map has been
// finalized.
template<typename HashKey, typename ValueType_>
class ValueTableBuilder
{
public:
using ValueType = ValueType_;
// Adds an entry for a preference with only a default value to the array,
// and returns an opaque descriptor for its index.
ValueIdx Add(const ValueType& aDefaultValue)
{
auto index = uint16_t(mDefaultEntries.Count());
auto entry = mDefaultEntries.LookupForAdd(aDefaultValue).OrInsert([&]() {
return Entry{ index, false, aDefaultValue };
});
return { entry.mIndex, false };
}
// Adds an entry for a preference with a user value to the array. Regardless
// of whether the preference has a default value, space must be allocated
// for it. For preferences with no default value, the actual value which
// appears in the array at its value index is ignored.
ValueIdx Add(const ValueType& aDefaultValue, const ValueType& aUserValue)
{
auto index = uint16_t(mUserEntries.Length());
mUserEntries.AppendElement(
Entry{ index, true, aDefaultValue, aUserValue });
return { index, true };
}
// Returns the final index for an entry based on its opaque index
// descriptor. This must only be called after the caller has finished adding
// entries to the builder.
uint16_t GetIndex(const ValueIdx& aIndex) const
{
uint16_t base = aIndex.mHasUserValue ? 0 : UserCount();
return base + aIndex.mIndex;
}
// Writes out the array of default values at the block beginning at the
// given pointer. The block must be at least as large as the value returned
// by DefaultSize().
void WriteDefaultValues(const RangedPtr<uint8_t>& aBuffer) const
{
auto buffer = aBuffer.ReinterpretCast<ValueType>();
for (const auto& entry : mUserEntries) {
buffer[entry.mIndex] = entry.mDefaultValue;
}
size_t defaultsOffset = UserCount();
for (auto iter = mDefaultEntries.ConstIter(); !iter.Done(); iter.Next()) {
const auto& entry = iter.Data();
buffer[defaultsOffset + entry.mIndex] = entry.mDefaultValue;
}
}
// Writes out the array of user values at the block beginning at the
// given pointer. The block must be at least as large as the value returned
// by UserSize().
void WriteUserValues(const RangedPtr<uint8_t>& aBuffer) const
{
auto buffer = aBuffer.ReinterpretCast<ValueType>();
for (const auto& entry : mUserEntries) {
buffer[entry.mIndex] = entry.mUserValue;
}
}
// These return the number of entries in the default and user value arrays,
// respectively.
uint32_t DefaultCount() const
{
return UserCount() + mDefaultEntries.Count();
}
uint32_t UserCount() const { return mUserEntries.Length(); }
// These return the byte sizes of the default and user value arrays,
// respectively.
uint32_t DefaultSize() const { return DefaultCount() * sizeof(ValueType); }
uint32_t UserSize() const { return UserCount() * sizeof(ValueType); }
void Clear()
{
mUserEntries.Clear();
mDefaultEntries.Clear();
}
static constexpr size_t Alignment() { return alignof(ValueType); }
private:
struct Entry
{
uint16_t mIndex;
bool mHasUserValue;
ValueType mDefaultValue;
ValueType mUserValue{};
};
AutoTArray<Entry, 256> mUserEntries;
nsDataHashtable<HashKey, Entry> mDefaultEntries;
};
// A special-purpose string table builder for keys which are already
// guaranteed to be unique. Duplicate values will not be detected or
// de-duplicated.
template<typename CharType>
class UniqueStringTableBuilder
{
public:
using ElemType = CharType;
explicit UniqueStringTableBuilder(size_t aCapacity)
: mEntries(aCapacity)
{
}
StringTableEntry Add(const CharType* aKey)
{
auto entry =
mEntries.AppendElement(Entry{ mSize, uint32_t(strlen(aKey)), aKey });
mSize += entry->mLength + 1;
return { entry->mOffset, entry->mLength };
}
void Write(const RangedPtr<uint8_t>& aBuffer)
{
auto buffer = aBuffer.ReinterpretCast<ElemType>();
for (auto& entry : mEntries) {
memcpy(&buffer[entry.mOffset],
entry.mValue,
sizeof(ElemType) * (entry.mLength + 1));
}
}
uint32_t Count() const { return mEntries.Length(); }
uint32_t Size() const { return mSize * sizeof(ElemType); }
void Clear() { mEntries.Clear(); }
static constexpr size_t Alignment() { return alignof(ElemType); }
private:
struct Entry
{
uint32_t mOffset;
uint32_t mLength;
const CharType* mValue;
};
nsTArray<Entry> mEntries;
uint32_t mSize = 0;
};
// A preference value entry, roughly corresponding to the
// SharedPrefMap::Value struct, but with a temporary place-holder value rather
// than a final value index.
union Value {
Value(bool aDefaultValue, bool aUserValue)
: mDefaultBool(aDefaultValue)
, mUserBool(aUserValue)
{
}
MOZ_IMPLICIT Value(const ValueIdx& aIndex)
: mIndex(aIndex)
{
}
// For Bool preferences, their default and user bool values.
struct
{
bool mDefaultBool;
bool mUserBool;
};
// For Int and String preferences, an opaque descriptor for their entries in
// their value arrays. This must be passed to the appropriate
// ValueTableBuilder to obtain the final index when the entry is serialized.
ValueIdx mIndex;
};
// A preference entry, to be converted to a SharedPrefMap::Entry struct during
// serialization.
struct Entry
{
// The entry's preference name, as passed to Add(). The caller is
// responsible for keeping this pointer alive until the builder is
// finalized.
const char* mKeyString;
// The entry in mKeyTable corresponding to mKeyString.
StringTableEntry mKey;
Value mValue;
uint8_t mType : 2;
uint8_t mHasDefaultValue : 1;
uint8_t mHasUserValue : 1;
uint8_t mIsSticky : 1;
uint8_t mIsLocked : 1;
};
// Converts a builder Value struct to a SharedPrefMap::Value struct for
// serialization. This must not be called before callers have finished adding
// entries to the value array builders.
SharedPrefMap::Value GetValue(const Entry& aEntry) const
{
switch (PrefType(aEntry.mType)) {
case PrefType::Bool:
return { aEntry.mValue.mDefaultBool, aEntry.mValue.mUserBool };
case PrefType::Int:
return { mIntValueTable.GetIndex(aEntry.mValue.mIndex) };
case PrefType::String:
return { mStringValueTable.GetIndex(aEntry.mValue.mIndex) };
default:
MOZ_ASSERT_UNREACHABLE("Invalid pref type");
return { false, false };
}
}
UniqueStringTableBuilder<char> mKeyTable{ kExpectedPrefCount };
StringTableBuilder<nsCStringHashKey, nsCString> mValueStringTable;
ValueTableBuilder<nsUint32HashKey, uint32_t> mIntValueTable;
ValueTableBuilder<nsGenericHashKey<StringTableEntry>, StringTableEntry>
mStringValueTable;
nsTArray<Entry> mEntries{ kExpectedPrefCount };
};
} // mozilla
#endif // dom_ipc_SharedPrefMap_h

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

@ -32,6 +32,7 @@ EXPORTS.mozilla += [
UNIFIED_SOURCES += [
'Preferences.cpp',
'SharedPrefMap.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')

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

@ -15,7 +15,7 @@ interface nsIFile;
[function, scriptable, uuid(c3f0cedc-e244-4316-b33a-80306a1c35a1)]
interface nsIPrefStatsCallback : nsISupports
{
void visit(in string prefName, in unsigned long accessCount);
void visit(in ACString prefName, in unsigned long accessCount);
};
/**

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

@ -235,9 +235,9 @@ function run_test() {
// locking and unlocking a nonexistent pref should throw
do_check_throws(function() {
ps.lockPref("DefaultPref.nonexistent");}, Cr.NS_ERROR_UNEXPECTED);
ps.lockPref("DefaultPref.nonexistent");}, Cr.NS_ERROR_ILLEGAL_VALUE);
do_check_throws(function() {
ps.unlockPref("DefaultPref.nonexistent");}, Cr.NS_ERROR_UNEXPECTED);
ps.unlockPref("DefaultPref.nonexistent");}, Cr.NS_ERROR_ILLEGAL_VALUE);
// getting a locked pref branch should return the "default" value
Assert.ok(!ps.prefIsLocked("DefaultPref.char"));

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

@ -52,6 +52,14 @@ function run_test() {
let isParent = isParentProcess();
if (isParent) {
// Preferences with large values will still appear in the shared memory
// snapshot that we share with all processes. They should not, however, be
// sent with the list of changes on top of the snapshot.
//
// So, make sure we've generated the initial snapshot before we set the
// preference values by launching a child process with an empty test.
sendCommand("");
// Set all combinations of none, small and large, for default and user prefs.
for (let def of testValues) {
for (let user of testValues) {
@ -82,8 +90,8 @@ function run_test() {
// large, so the preference should not be set.
let prefExists;
try {
pb.getCharPref(pref_name);
prefExists = true;
let val = pb.getCharPref(pref_name);
prefExists = val.length > 128;
} catch(e) {
prefExists = false;
}

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

@ -0,0 +1,302 @@
/* 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/. */
"use strict";
// This file tests the functionality of the preference service when using a
// shared memory snapshot. In this configuration, a snapshot of the initial
// state of the preferences database is made when we first spawn a child
// process, and changes after that point are stored as entries in a dynamic hash
// table, on top of the snapshot.
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm");
ExtensionTestUtils.init(this);
let contentPage;
const {prefs} = Services;
const defaultPrefs = prefs.getDefaultBranch("");
const FRAME_SCRIPT_INIT = `
ChromeUtils.import("resource://gre/modules/Services.jsm");
const {prefs} = Services;
const defaultPrefs = prefs.getDefaultBranch("");
`;
function try_(fn) {
try {
return fn();
} catch (e) {
return undefined;
}
}
function getPref(pref) {
let flags = {
locked: try_(() => prefs.prefIsLocked(pref)),
hasUser: try_(() => prefs.prefHasUserValue(pref)),
};
switch (prefs.getPrefType(pref)) {
case prefs.PREF_INT:
return {
...flags,
type: "Int",
user: try_(() => prefs.getIntPref(pref)),
default: try_(() => defaultPrefs.getIntPref(pref)),
};
case prefs.PREF_BOOL:
return {
...flags,
type: "Bool",
user: try_(() => prefs.getBoolPref(pref)),
default: try_(() => defaultPrefs.getBoolPref(pref)),
};
case prefs.PREF_STRING:
return {
...flags,
type: "String",
user: try_(() => prefs.getStringPref(pref)),
default: try_(() => defaultPrefs.getStringPref(pref)),
};
}
return {};
}
function getPrefs(prefNames) {
let result = {};
for (let pref of prefNames) {
result[pref] = getPref(pref);
}
result.childList = prefs.getChildList("");
return result;
}
function checkPref(pref, proc, val, type, userVal, defaultVal, expectedFlags = {}) {
info(`Check "${pref}" ${proc} value`);
equal(val.type, type, `Expected type for "${pref}"`);
equal(val.user, userVal, `Expected user value for "${pref}"`);
// We only send changes to the content process when they'll make a visible
// difference, so ignore content process default values when we have a defined
// user value.
if (proc !== "content" || val.user === undefined) {
equal(val.default, defaultVal, `Expected default value for "${pref}"`);
}
for (let [flag, value] of Object.entries(expectedFlags)) {
equal(val[flag], value, `Expected ${flag} value for "${pref}"`);
}
}
function getPrefList() {
return prefs.getChildList("");
}
const TESTS = {
"exists.thenDoesNot": {
beforeContent(PREF) {
prefs.setBoolPref(PREF, true);
ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`);
},
contentStartup(PREF, val, childList) {
ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`);
ok(childList.includes(PREF), `Child list includes "${PREF}"`);
prefs.clearUserPref(PREF);
ok(!getPrefList().includes(PREF), `Parent list doesn't include "${PREF}"`);
},
contentUpdate1(PREF, val, childList) {
ok(!getPrefList().includes(PREF), `Parent list doesn't include "${PREF}"`);
ok(!childList.includes(PREF), `Child list doesn't include "${PREF}"`);
prefs.setCharPref(PREF, "foo");
ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`);
checkPref(PREF, "parent", getPref(PREF), "String", "foo");
},
contentUpdate2(PREF, val, childList) {
ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`);
ok(childList.includes(PREF), `Child list includes "${PREF}"`);
checkPref(PREF, "parent", getPref(PREF), "String", "foo");
checkPref(PREF, "child", val, "String", "foo");
},
},
"doesNotExists.thenDoes": {
contentStartup(PREF, val, childList) {
ok(!getPrefList().includes(PREF), `Parent list doesn't include "${PREF}"`);
ok(!childList.includes(PREF), `Child list doesn't include "${PREF}"`);
prefs.setIntPref(PREF, 42);
ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`);
},
contentUpdate1(PREF, val, childList) {
ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`);
ok(childList.includes(PREF), `Child list includes "${PREF}"`);
checkPref(PREF, "parent", getPref(PREF), "Int", 42);
checkPref(PREF, "child", val, "Int", 42);
},
},
};
const PREFS = [
{type: "Bool", values: [true, false, true]},
{type: "Int", values: [24, 42, 73]},
{type: "String", values: ["meh", "hem", "hrm"]},
];
for (let {type, values} of PREFS) {
let set = `set${type}Pref`;
let get = `get${type}Pref`;
function prefTest(opts) {
function check(pref, proc, val, {expectedVal, defaultVal = undefined, expectedDefault = defaultVal, expectedFlags = {}}) {
checkPref(pref, proc, val, type, expectedVal, expectedDefault, expectedFlags);
}
function updatePref(PREF,
{userVal = undefined,
defaultVal = undefined,
flags = {}}) {
info(`Update "${PREF}"`);
if (userVal !== undefined) {
prefs[set](PREF, userVal);
}
if (defaultVal !== undefined) {
defaultPrefs[set](PREF, defaultVal);
}
if (flags.locked === true) {
prefs.lockPref(PREF);
} else if (flags.locked === false) {
prefs.unlockPref(PREF);
}
}
return {
beforeContent(PREF) {
updatePref(PREF, opts.initial)
check(PREF, "parent", getPref(PREF), opts.initial);
},
contentStartup(PREF, contentVal) {
check(PREF, "content", contentVal, opts.initial);
check(PREF, "parent", getPref(PREF), opts.initial);
updatePref(PREF, opts.change1)
check(PREF, "parent", getPref(PREF), opts.change1);
},
contentUpdate1(PREF, contentVal) {
check(PREF, "content", contentVal, opts.change1);
check(PREF, "parent", getPref(PREF), opts.change1);
if (opts.change2) {
updatePref(PREF, opts.change2)
check(PREF, "parent", getPref(PREF), opts.change2);
}
},
contentUpdate2(PREF, contentVal) {
if (opts.change2) {
check(PREF, "content", contentVal, opts.change2);
check(PREF, "parent", getPref(PREF), opts.change2);
}
},
};
}
for (let i of [0, 1]) {
let userVal = values[i];
let defaultVal = values[+!i];
TESTS[`type.${type}.${i}.default`] = prefTest({
initial: {defaultVal, expectedVal: defaultVal},
change1: {defaultVal: values[2], expectedVal: values[2]},
});
TESTS[`type.${type}.${i}.user`] = prefTest({
initial: {userVal, expectedVal: userVal},
change1: {defaultVal: values[2], expectedVal: userVal},
change2: {userVal: values[2],
expectedDefault: values[2],
expectedVal: values[2]},
});
TESTS[`type.${type}.${i}.both`] = prefTest({
initial: {userVal, defaultVal, expectedVal: userVal},
change1: {defaultVal: values[2], expectedVal: userVal},
change2: {userVal: values[2],
expectedDefault: values[2],
expectedVal: values[2]},
});
TESTS[`type.${type}.${i}.both.thenLock`] = prefTest({
initial: {userVal, defaultVal, expectedVal: userVal},
change1: {expectedDefault: defaultVal,
expectedVal: defaultVal,
flags: {locked: true},
expectFlags: {locked: true}},
});
TESTS[`type.${type}.${i}.both.thenUnlock`] = prefTest({
initial: {userVal, defaultVal, expectedVal: defaultVal,
flags: {locked: true}, expectedFlags: {locked: true}},
change1: {expectedDefault: defaultVal,
expectedVal: userVal,
flags: {locked: false},
expectFlags: {locked: false}},
});
TESTS[`type.${type}.${i}.both.locked`] = prefTest({
initial: {userVal, defaultVal, expectedVal: defaultVal,
flags: {locked: true}, expectedFlags: {locked: true}},
change1: {userVal: values[2],
expectedDefault: defaultVal,
expectedVal: defaultVal,
expectedFlags: {locked: true}},
change2: {defaultVal: values[2],
expectedDefault: defaultVal,
expectedVal: defaultVal,
expectedFlags: {locked: true}},
});
}
}
add_task(async function test_sharedMap_prefs() {
let prefValues = {};
async function runChecks(op) {
for (let [pref, ops] of Object.entries(TESTS)) {
if (ops[op]) {
info(`Running ${op} for "${pref}"`);
await ops[op](pref,
prefValues[pref] || undefined,
prefValues.childList || undefined);
}
}
}
await runChecks("beforeContent");
contentPage = await ExtensionTestUtils.loadContentPage("about:blank", {remote: true});
registerCleanupFunction(() => contentPage.close());
contentPage.addFrameScriptHelper(FRAME_SCRIPT_INIT);
contentPage.addFrameScriptHelper(try_);
contentPage.addFrameScriptHelper(getPref);
let prefNames = Object.keys(TESTS);
prefValues = await contentPage.spawn(prefNames, getPrefs);
await runChecks("contentStartup");
prefValues = await contentPage.spawn(prefNames, getPrefs);
await runChecks("contentUpdate1");
prefValues = await contentPage.spawn(prefNames, getPrefs);
await runChecks("contentUpdate2");
});

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

@ -8,4 +8,5 @@ skip-if = toolkit == 'android'
[test_locked_prefs.js]
[test_observed_prefs.js]
[test_update_prefs.js]
[test_sharedMap.js]
[test_user_default_prefs.js]

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

@ -393,7 +393,7 @@ FreeArgv(char** argv, int argc)
}
extern "C" APKOPEN_EXPORT void MOZ_JNICALL
Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jobjectArray jargs, int prefsFd, int ipcFd, int crashFd, int crashAnnotationFd)
Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jobjectArray jargs, int prefsFd, int prefMapFd, int ipcFd, int crashFd, int crashAnnotationFd)
{
int argc = 0;
char** argv = CreateArgvFromObjectArray(jenv, jargs, &argc);
@ -408,7 +408,7 @@ Java_org_mozilla_gecko_mozglue_GeckoLoader_nativeRun(JNIEnv *jenv, jclass jc, jo
gBootstrap->GeckoStart(jenv, argv, argc, sAppData);
ElfLoader::Singleton.ExpectShutdown(true);
} else {
gBootstrap->XRE_SetAndroidChildFds(jenv, prefsFd, ipcFd, crashFd, crashAnnotationFd);
gBootstrap->XRE_SetAndroidChildFds(jenv, { prefsFd, prefMapFd, ipcFd, crashFd, crashAnnotationFd });
gBootstrap->XRE_SetProcessType(argv[argc - 1]);
XREChildData childData;

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

@ -29,10 +29,6 @@ public:
MFBT_API MutexImpl();
MFBT_API ~MutexImpl();
bool operator==(const MutexImpl& rhs) {
return platformData_ == rhs.platformData_;
}
protected:
MFBT_API void lock();
MFBT_API void unlock();
@ -42,6 +38,7 @@ private:
void operator=(const MutexImpl&) = delete;
MutexImpl(MutexImpl&&) = delete;
void operator=(MutexImpl&&) = delete;
bool operator==(const MutexImpl& rhs) = delete;
void mutexLock();
#ifdef XP_DARWIN

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

@ -798,12 +798,12 @@ win32-msvc/debug:
- win64-sccache
win32-msvc/opt:
description: "Win32 MSVC Opt"
description: "Win32 MSVC PGO"
index:
product: firefox
job-name: win32-msvc-opt
job-name: win32-msvc-pgo
treeherder:
platform: windows2012-32/opt
platform: windows2012-32/pgo
symbol: Bmsvc
tier: 2
stub-installer:
@ -820,7 +820,7 @@ win32-msvc/opt:
PERFHERDER_EXTRA_OPTIONS: msvc
run:
using: mozharness
options: [append-env-variables-from-configs]
options: [enable-pgo, append-env-variables-from-configs]
script: mozharness/scripts/fx_desktop_build.py
config:
- builds/releng_base_firefox.py
@ -868,12 +868,12 @@ win64-msvc/debug:
- win64-sccache
win64-msvc/opt:
description: "Win64 MSVC Opt"
description: "Win64 MSVC PGO"
index:
product: firefox
job-name: win64-msvc-opt
job-name: win64-msvc-pgo
treeherder:
platform: windows2012-64/opt
platform: windows2012-64/pgo
symbol: Bmsvc
tier: 2
worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
@ -884,7 +884,7 @@ win64-msvc/opt:
PERFHERDER_EXTRA_OPTIONS: msvc
run:
using: mozharness
options: [append-env-variables-from-configs]
options: [enable-pgo, append-env-variables-from-configs]
script: mozharness/scripts/fx_desktop_build.py
config:
- builds/releng_base_firefox.py

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

@ -40,6 +40,7 @@ gtest:
run-on-projects:
by-test-platform:
windows.*-pgo/.*: [] # permafails on pgo
windows.*-msvc/opt: [] # msvc opt builds are pgo
windows.*-nightly/.*: [] # permafails on nightly too
windows10-64-asan/opt: [] # permafails on asan too
.*-devedition/.*: [] # don't run on devedition

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

@ -167,6 +167,11 @@ class ContentPage {
this.browser.messageManager.loadFrameScript(frameScript, true);
}
addFrameScriptHelper(func) {
let frameScript = `data:text/javascript,${encodeURI(func)}`;
this.browser.messageManager.loadFrameScript(frameScript, false, true);
}
async loadURL(url, redirectUrl = undefined) {
await this.browserReady;

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

@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="host">host</div>
<script>
"use strict";
document.getElementById("host").attachShadow({mode: "closed"});
</script>
</body>
</html>

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

@ -0,0 +1,65 @@
"use strict";
ChromeUtils.defineModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
// ExtensionContent.jsm needs to know when it's running from xpcshell,
// to use the right timeout for content scripts executed at document_idle.
ExtensionTestUtils.mockAppInfo();
const server = createHttpServer();
server.registerDirectory("/data/", do_get_file("data"));
const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
add_task(async function test_contentscript_shadowDOM() {
const PREFS = {
"dom.webcomponents.shadowdom.enabled": true,
};
// Set prefs to our initial values.
for (let pref in PREFS) {
Preferences.set(pref, PREFS[pref]);
}
registerCleanupFunction(() => {
// Reset the prefs.
for (let pref in PREFS) {
Preferences.reset(pref);
}
});
function backgroundScript() {
browser.test.assertTrue("openOrClosedShadowRoot" in document.documentElement,
"Should have openOrClosedShadowRoot in Element in background script.");
}
function contentScript() {
let host = document.getElementById("host");
browser.test.assertTrue("openOrClosedShadowRoot" in host, "Should have openOrClosedShadowRoot in Element.");
let shadowRoot = host.openOrClosedShadowRoot;
browser.test.assertEq(shadowRoot.mode, "closed", "Should have closed ShadowRoot.");
browser.test.sendMessage("contentScript");
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
content_scripts: [{
"matches": ["http://*/*/file_shadowdom.html"],
"js": ["content_script.js"],
}],
},
background: backgroundScript,
files: {
"content_script.js": contentScript,
},
});
await extension.startup();
let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_shadowdom.html`);
await extension.awaitMessage("contentScript");
await contentPage.close();
await extension.unload();
});

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

@ -10,3 +10,4 @@ skip-if = (os == "android" && debug) || (os == "win" && debug) # Windows: Bug 14
[test_ext_contentScripts_register.js]
skip-if = os == "android"
[test_ext_adoption_with_xrays.js]
[test_ext_shadowdom.js]

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

@ -9,11 +9,10 @@ function TooltipTextProvider() {}
TooltipTextProvider.prototype = {
getNodeText(tipElement, textOut, directionOut) {
// Don't show the tooltip if the tooltip node is a document, browser, or disconnected.
// Don't show the tooltip if the tooltip node is a document or browser.
// Caller should ensure the node is in (composed) document.
if (!tipElement || !tipElement.ownerDocument ||
tipElement.localName == "browser" ||
(tipElement.ownerDocument.compareDocumentPosition(tipElement) &
tipElement.ownerDocument.DOCUMENT_POSITION_DISCONNECTED)) {
tipElement.localName == "browser") {
return false;
}
@ -123,7 +122,18 @@ TooltipTextProvider.prototype = {
usedTipElement = tipElement;
}
tipElement = tipElement.parentNode;
let parent = tipElement.parentNode;
if (defView.ShadowRoot &&
parent instanceof defView.ShadowRoot) {
tipElement = parent.host;
} else {
let slot = tipElement.openOrClosedAssignedSlot;
if (slot) {
tipElement = slot;
} else {
tipElement = parent;
}
}
}
return [titleText, XLinkTitleText, SVGTitleText, XULtooltiptextText].some(function(t) {

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

@ -6,3 +6,4 @@ support-files = xul_tooltiptext.xhtml
[browser_bug581947.js]
[browser_input_file_tooltips.js]
skip-if = os == 'win' && os_version == '10.0' # Permafail on Win 10 (bug 1400368)
[browser_shadow_dom_tooltip.js]

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

@ -0,0 +1,131 @@
/* eslint-disable mozilla/no-arbitrary-setTimeout */
add_task(async function setup() {
await SpecialPowers.pushPrefEnv(
{"set": [["ui.tooltipDelay", 0],
["dom.webcomponents.shadowdom.enabled", true]]});
});
add_task(async function test_title_in_shadow_dom() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
info("Moving mouse out of the way.");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 300, 300);
info("creating host");
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
let doc = content.document;
let host = doc.createElement("div");
doc.body.appendChild(host);
host.setAttribute("style", "position: absolute; top: 0; left: 0;");
var sr = host.attachShadow({ mode: "closed" });
sr.innerHTML = "<div title='shadow' style='width: 200px; height: 200px;'>shadow</div>";
});
let awaitTooltipOpen = new Promise(resolve => {
let tooltipId = Services.appinfo.browserTabsRemoteAutostart ?
"remoteBrowserTooltip" :
"aHTMLTooltip";
let tooltip = document.getElementById(tooltipId);
tooltip.addEventListener("popupshown", function(event) {
resolve(event.target);
}, {once: true});
});
info("Initial mouse move");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5);
info("Waiting");
await new Promise(resolve => setTimeout(resolve, 400));
info("Second mouse move");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
info("Waiting for tooltip to open");
let tooltip = await awaitTooltipOpen;
is(tooltip.getAttribute("label"), "shadow", "tooltip label should match expectation");
info("Closing tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_title_in_light_dom() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
info("Moving mouse out of the way.");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 300, 300);
info("creating host");
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
let doc = content.document;
let host = doc.createElement("div");
host.title = "light";
doc.body.appendChild(host);
host.setAttribute("style", "position: absolute; top: 0; left: 0;");
var sr = host.attachShadow({ mode: "closed" });
sr.innerHTML = "<div style='width: 200px; height: 200px;'>shadow</div>";
});
let awaitTooltipOpen = new Promise(resolve => {
let tooltipId = Services.appinfo.browserTabsRemoteAutostart ?
"remoteBrowserTooltip" :
"aHTMLTooltip";
let tooltip = document.getElementById(tooltipId);
tooltip.addEventListener("popupshown", function(event) {
resolve(event.target);
}, {once: true});
});
info("Initial mouse move");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5);
info("Waiting");
await new Promise(resolve => setTimeout(resolve, 400));
info("Second mouse move");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
info("Waiting for tooltip to open");
let tooltip = await awaitTooltipOpen;
is(tooltip.getAttribute("label"), "light", "tooltip label should match expectation");
info("Closing tab");
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_title_through_slot() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
info("Moving mouse out of the way.");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 300, 300);
info("creating host");
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
let doc = content.document;
let host = doc.createElement("div");
host.title = "light";
host.innerHTML = "<div style='width: 200px; height: 200px;'>light</div>";
doc.body.appendChild(host);
host.setAttribute("style", "position: absolute; top: 0; left: 0;");
var sr = host.attachShadow({ mode: "closed" });
sr.innerHTML = "<div title='shadow' style='width: 200px; height: 200px;'><slot></slot></div>";
});
let awaitTooltipOpen = new Promise(resolve => {
let tooltipId = Services.appinfo.browserTabsRemoteAutostart ?
"remoteBrowserTooltip" :
"aHTMLTooltip";
let tooltip = document.getElementById(tooltipId);
tooltip.addEventListener("popupshown", function(event) {
resolve(event.target);
}, {once: true});
});
info("Initial mouse move");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 50, 5);
info("Waiting");
await new Promise(resolve => setTimeout(resolve, 400));
info("Second mouse move");
await EventUtils.synthesizeAndWaitNativeMouseMove(tab.linkedBrowser, 70, 5);
info("Waiting for tooltip to open");
let tooltip = await awaitTooltipOpen;
is(tooltip.getAttribute("label"), "shadow", "tooltip label should match expectation");
info("Closing tab");
BrowserTestUtils.removeTab(tab);
});

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

@ -78,8 +78,8 @@ public:
::GeckoStart(aEnv, argv, argc, aAppData);
}
virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, int aPrefsFd, int aIPCFd, int aCrashFd, int aCrashAnnotationFd) override {
::XRE_SetAndroidChildFds(aEnv, aPrefsFd, aIPCFd, aCrashFd, aCrashAnnotationFd);
virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, const XRE_AndroidChildFds& aFds) override {
::XRE_SetAndroidChildFds(aEnv, aFds);
}
#endif

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

@ -113,7 +113,7 @@ public:
#ifdef MOZ_WIDGET_ANDROID
virtual void GeckoStart(JNIEnv* aEnv, char** argv, int argc, const StaticXREAppData& aAppData) = 0;
virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, int aPrefsFd, int aIPCFd, int aCrashFd, int aCrashAnnotationFd) = 0;
virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, const XRE_AndroidChildFds& fds) = 0;
#endif
#ifdef LIBFUZZER

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

@ -244,13 +244,14 @@ GeckoProcessType sChildProcessType = GeckoProcessType_Default;
#if defined(MOZ_WIDGET_ANDROID)
void
XRE_SetAndroidChildFds (JNIEnv* env, int prefsFd, int ipcFd, int crashFd, int crashAnnotationFd)
XRE_SetAndroidChildFds (JNIEnv* env, const XRE_AndroidChildFds& fds)
{
mozilla::jni::SetGeckoThreadEnv(env);
mozilla::dom::SetPrefsFd(prefsFd);
IPC::Channel::SetClientChannelFd(ipcFd);
CrashReporter::SetNotificationPipeForChild(crashFd);
CrashReporter::SetCrashAnnotationPipeForChild(crashAnnotationFd);
mozilla::dom::SetPrefsFd(fds.mPrefsFd);
mozilla::dom::SetPrefMapFd(fds.mPrefMapFd);
IPC::Channel::SetClientChannelFd(fds.mIpcFd);
CrashReporter::SetNotificationPipeForChild(fds.mCrashFd);
CrashReporter::SetCrashAnnotationPipeForChild(fds.mCrashAnnotationFd);
}
#endif // defined(MOZ_WIDGET_ANDROID)

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

@ -7,6 +7,7 @@
#include "OfflineCacheUpdateChild.h"
#include "nsOfflineCacheUpdate.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/OfflineResourceListBinding.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/net/NeckoCommon.h"
@ -18,7 +19,6 @@
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "nsPIDOMWindow.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsIDocument.h"
#include "nsIObserverService.h"
#include "nsIURL.h"
@ -256,13 +256,13 @@ OfflineCacheUpdateChild::GetStatus(uint16_t *aStatus)
{
switch (mState) {
case STATE_CHECKING :
*aStatus = nsIDOMOfflineResourceList::CHECKING;
*aStatus = mozilla::dom::OfflineResourceList_Binding::CHECKING;
return NS_OK;
case STATE_DOWNLOADING :
*aStatus = nsIDOMOfflineResourceList::DOWNLOADING;
*aStatus = mozilla::dom::OfflineResourceList_Binding::DOWNLOADING;
return NS_OK;
default :
*aStatus = nsIDOMOfflineResourceList::IDLE;
*aStatus = mozilla::dom::OfflineResourceList_Binding::IDLE;
return NS_OK;
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше