зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
Коммит
35a115b001
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
if (IS_CHILD_PROCESS()) {
|
||||
aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMOfflineResourceList::MozHasItem(const nsAString& aURI, bool* aExists)
|
||||
{
|
||||
if (IS_CHILD_PROCESS())
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
|
||||
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;
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aExists = ((types & nsIApplicationCache::ITEM_DYNAMIC) != 0);
|
||||
return NS_OK;
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMOfflineResourceList::GetMozLength(uint32_t *aLength)
|
||||
return types & nsIApplicationCache::ITEM_DYNAMIC;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
rv = CacheKeys();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aLength = mCachedKeysCount;
|
||||
return NS_OK;
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDOMOfflineResourceList::MozItem(uint32_t aIndex, nsAString& aURI)
|
||||
rv = CacheKeys();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mCachedKeysCount;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
rv = update->AddDynamicURI(requestedURI);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
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)
|
||||
rv = update->AddDynamicURI(requestedURI);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
rv = update->Schedule();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
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,7 +103,7 @@ private:
|
|||
// Whether this storage is running in private-browsing window.
|
||||
bool mIsPrivate : 1;
|
||||
|
||||
void BroadcastChangeNotification(const nsAString& aKey,
|
||||
void OnChange(const nsAString& aKey,
|
||||
const nsAString& aOldValue,
|
||||
const nsAString& aNewValue);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String urlStr,
|
||||
final int target,
|
||||
final int flags,
|
||||
final GeckoResponse<Boolean> response) {
|
||||
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,
|
||||
public GeckoResult<Boolean> onLoadRequest(final GeckoSession session, final String urlStr,
|
||||
final int target,
|
||||
final int flags,
|
||||
final GeckoResponse<Boolean> response) {
|
||||
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 {
|
||||
try {
|
||||
let state = this.collectSessionState();
|
||||
sendAsyncMessage("GeckoView:SaveStateFinish", {state: JSON.stringify(state), id: aMsg.data.id});
|
||||
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.
|
||||
*/
|
||||
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,24 +202,45 @@ 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>() {
|
||||
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 void respond(Boolean handled) {
|
||||
callback.sendSuccess(handled);
|
||||
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) {
|
||||
final GeckoResult<GeckoSession> result = delegate.onNewSession(GeckoSession.this, uri);
|
||||
if (result == null) {
|
||||
callback.sendSuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
@ -230,6 +251,14 @@ public class GeckoSession extends LayerSession
|
|||
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,
|
||||
@Nullable GeckoResult<Boolean> onLoadRequest(@NonNull GeckoSession session,
|
||||
@NonNull String uri,
|
||||
@TargetWindow int target,
|
||||
@LoadRequestFlags int flags,
|
||||
GeckoResponse<Boolean> response);
|
||||
@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;
|
||||
}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче