Bug 1371888 - cache plugin information in pluginreg.dat to avoid sync startup load, r=florian,mossop

This changes the pluginreg.dat format to include the blocklist state.

There is now only the saved blocklist state in a plugin tag instance, rather than
looking it up from in there using the blocklist service, so it was renamed from
mCachedBlocklistState to mBlocklistState. We pass the 'right' state to the plugin
instance when the plugintag is constructed. If we don't have state, we mark it as
unblocked.

mCachedBlocklistStateChanged was never read so it's being removed.

Bug 1439519 adds a 'blocklist-loaded' notification that is fired once the blocklist is loaded.
The plugin host implementation will listen to this in the parent process and update the
blocklist state of all the plugins, and broadcast changes to the child process, just like when
we update the blocklist from the server. We now also avoid re-sending plugin content to the
content processes if the plugin state hasn't changed as a result of the blocklist having been
loaded.

Finally, because new plugins should still get an up-to-date blocklist state, and
telemetry should get up-to-date data about which plugins are and aren't enabled
once we have that data, we ensure that once we've loaded the blocklist async,
we schedule an idle task to parse it and consider it loaded.

All this means that plugin blocklist information could be mistaken between the points where
a new plugin is installed and we first run Firefox with the new plugin, and the point where
we load the blocklist. Given the trade-offs, that size of window (tiny) seems OK, also given
that there's already a much larger window in blocklist updates (which only happen once every 24h).

MozReview-Commit-ID: 1gsojRkUzTw

--HG--
extra : rebase_source : 4709916b4674ada54f8a495fd2d16fcef8c58d20
This commit is contained in:
Gijs Kruitbosch 2018-02-20 16:53:48 +00:00
parent ff2e63c19e
commit ca508d989b
8 changed files with 120 additions and 118 deletions

View File

@ -807,22 +807,18 @@ nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
pluginHost->GetPluginTagForInstance(pluginInstance,
getter_AddRefs(pluginTag));
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
if (blocklist) {
uint32_t blockState = nsIBlocklistService::STATE_NOT_BLOCKED;
blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
EmptyString(), &blockState);
if (blockState == nsIBlocklistService::STATE_OUTDATED) {
// Fire plugin outdated event if necessary
LOG(("OBJLC [%p]: Dispatching plugin outdated event for content\n",
this));
nsCOMPtr<nsIRunnable> ev = new nsSimplePluginEvent(thisContent,
NS_LITERAL_STRING("PluginOutdated"));
nsresult rv = NS_DispatchToCurrentThread(ev);
if (NS_FAILED(rv)) {
NS_WARNING("failed to dispatch nsSimplePluginEvent");
}
uint32_t blockState = nsIBlocklistService::STATE_NOT_BLOCKED;
pluginTag->GetBlocklistState(&blockState);
if (blockState == nsIBlocklistService::STATE_OUTDATED) {
// Fire plugin outdated event if necessary
LOG(("OBJLC [%p]: Dispatching plugin outdated event for content\n",
this));
nsCOMPtr<nsIRunnable> ev = new nsSimplePluginEvent(thisContent,
NS_LITERAL_STRING("PluginOutdated"));
nsresult rv = NS_DispatchToCurrentThread(ev);
if (NS_FAILED(rv)) {
NS_WARNING("failed to dispatch nsSimplePluginEvent");
}
}

View File

@ -397,7 +397,8 @@ nsPluginArray::EnsurePlugins()
if (mPlugins.Length() == 0 && mCTPPlugins.Length() != 0) {
nsCOMPtr<nsPluginTag> hiddenTag = new nsPluginTag("Hidden Plugin", nullptr, "dummy.plugin", nullptr, nullptr,
nullptr, nullptr, nullptr, 0, 0, false);
nullptr, nullptr, nullptr, 0, 0, false,
nsIBlocklistService::STATE_NOT_BLOCKED);
mPlugins.AppendElement(new nsPluginElement(mWindow, hiddenTag));
}

View File

@ -134,7 +134,7 @@ static const char *kPrefDisableFullPage = "plugin.disable_full_page_plugin_for_t
static const char *kPrefUnloadPluginTimeoutSecs = "dom.ipc.plugins.unloadTimeoutSecs";
static const uint32_t kDefaultPluginUnloadingTimeout = 30;
static const char *kPluginRegistryVersion = "0.18";
static const char *kPluginRegistryVersion = "0.19";
static const char kDirectoryServiceContractID[] = "@mozilla.org/file/directory_service;1";
@ -266,7 +266,10 @@ nsPluginHost::nsPluginHost()
mozilla::services::GetObserverService();
if (obsService) {
obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
obsService->AddObserver(this, "blocklist-updated", false);
if (XRE_IsParentProcess()) {
obsService->AddObserver(this, "blocklist-updated", false);
obsService->AddObserver(this, "blocklist-loaded", false);
}
}
#ifdef PLUGIN_LOGGING
@ -1999,6 +2002,13 @@ nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir,
nsCOMArray<nsIFile> extensionDirs;
GetExtensionDirectories(extensionDirs);
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
bool isBlocklistLoaded = false;
if (blocklist && NS_FAILED(blocklist->GetIsLoaded(&isBlocklistLoaded))) {
isBlocklistLoaded = false;
}
for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) {
nsCOMPtr<nsIFile>& localfile = pluginFiles[i];
@ -2090,12 +2100,17 @@ nsresult nsPluginHost::ScanPluginsDirectory(nsIFile *pluginsDir,
continue;
}
pluginTag = new nsPluginTag(&info, fileModTime, fromExtension);
pluginFile.FreePluginInfo(info);
uint32_t state = nsIBlocklistService::STATE_NOT_BLOCKED;
pluginTag = new nsPluginTag(&info, fileModTime, fromExtension, state);
pluginTag->mLibrary = library;
uint32_t state;
rv = pluginTag->GetBlocklistState(&state);
NS_ENSURE_SUCCESS(rv, rv);
// If the blocklist is loaded, get the blocklist state now.
// If it isn't loaded yet, we'll update it once it loads.
if (isBlocklistLoaded &&
NS_SUCCEEDED(blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
EmptyString(), &state))) {
pluginTag->SetBlocklistState(state);
}
pluginFile.FreePluginInfo(info);
// If the blocklist says it is risky and we have never seen this
// plugin before, then disable it.
@ -2744,8 +2759,8 @@ nsPluginHost::WritePluginInfo()
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
// lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension
PR_fprintf(fd, "%lld%c%d%c%lu%c%d%c%c\n",
// lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension|blocklistState
PR_fprintf(fd, "%lld%c%d%c%lu%c%d%c%d%c%c\n",
tag->mLastModifiedTime,
PLUGIN_REGISTRY_FIELD_DELIMITER,
false, // did store whether or not to unload in-process plugins
@ -2754,6 +2769,8 @@ nsPluginHost::WritePluginInfo()
PLUGIN_REGISTRY_FIELD_DELIMITER,
tag->IsFromExtension(),
PLUGIN_REGISTRY_FIELD_DELIMITER,
tag->BlocklistState(),
PLUGIN_REGISTRY_FIELD_DELIMITER,
PLUGIN_REGISTRY_END_OF_LINE_MARKER);
//description, name & mtypecount are on separate line
@ -2974,12 +2991,13 @@ nsPluginHost::ReadPluginInfo()
if (!reader.NextLine())
return rv;
// lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension
if (4 != reader.ParseLine(values, 4))
// lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension|blocklistState
if (5 != reader.ParseLine(values, 5))
return rv;
int64_t lastmod = nsCRT::atoll(values[0]);
bool fromExtension = atoi(values[3]);
uint16_t blocklistState = atoi(values[4]);
if (!reader.NextLine())
return rv;
@ -3040,7 +3058,7 @@ nsPluginHost::ReadPluginInfo()
(const char* const*)mimetypes,
(const char* const*)mimedescriptions,
(const char* const*)extensions,
mimetypecount, lastmod, fromExtension, true);
mimetypecount, lastmod, fromExtension, blocklistState, true);
delete [] heapalloced;
@ -3389,15 +3407,32 @@ NS_IMETHODIMP nsPluginHost::Observe(nsISupports *aSubject,
LoadPlugins();
}
}
if (!strcmp("blocklist-updated", aTopic)) {
if (XRE_IsParentProcess() &&
(!strcmp("blocklist-updated", aTopic) || !strcmp("blocklist-loaded", aTopic))) {
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
if (!blocklist) {
return NS_OK;
}
nsPluginTag* plugin = mPlugins;
bool blocklistAlteredPlugins = false;
while (plugin) {
plugin->InvalidateBlocklistState();
uint32_t blocklistState = nsIBlocklistService::STATE_NOT_BLOCKED;
nsresult rv = blocklist->GetPluginBlocklistState(plugin, EmptyString(),
EmptyString(), &blocklistState);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t oldBlocklistState;
plugin->GetBlocklistState(&oldBlocklistState);
plugin->SetBlocklistState(blocklistState);
blocklistAlteredPlugins |= (oldBlocklistState != blocklistState);
plugin = plugin->mNext;
}
// We update blocklists asynchronously by just sending a new plugin list to
// content.
if (XRE_IsParentProcess()) {
if (blocklistAlteredPlugins) {
// Write the changed list to disk:
WritePluginInfo();
// We update blocklists asynchronously by just sending a new plugin list to
// content.
// We'll need to repack our tags and send them to content again.
IncrementChromeEpoch();
SendPluginsToContent();

View File

@ -223,7 +223,8 @@ uint32_t nsPluginTag::sNextId;
nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
int64_t aLastModifiedTime,
bool fromExtension)
bool fromExtension,
uint32_t aBlocklistState)
: nsIInternalPluginTag(aPluginInfo->fName, aPluginInfo->fDescription,
aPluginInfo->fFileName, aPluginInfo->fVersion),
mId(sNextId++),
@ -236,9 +237,8 @@ nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
mLastModifiedTime(aLastModifiedTime),
mSandboxLevel(0),
mIsSandboxLoggingEnabled(false),
mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED),
mCachedBlocklistStateValid(false),
mIsFromExtension(fromExtension)
mIsFromExtension(fromExtension),
mBlocklistState(aBlocklistState)
{
InitMime(aPluginInfo->fMimeTypeArray,
aPluginInfo->fMimeDescriptionArray,
@ -260,6 +260,7 @@ nsPluginTag::nsPluginTag(const char* aName,
int32_t aVariants,
int64_t aLastModifiedTime,
bool fromExtension,
uint32_t aBlocklistState,
bool aArgsAreUTF8)
: nsIInternalPluginTag(aName, aDescription, aFileName, aVersion),
mId(sNextId++),
@ -272,9 +273,8 @@ nsPluginTag::nsPluginTag(const char* aName,
mLastModifiedTime(aLastModifiedTime),
mSandboxLevel(0),
mIsSandboxLoggingEnabled(false),
mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED),
mCachedBlocklistStateValid(false),
mIsFromExtension(fromExtension)
mIsFromExtension(fromExtension),
mBlocklistState(aBlocklistState)
{
InitMime(aMimeTypes, aMimeDescriptions, aExtensions,
static_cast<uint32_t>(aVariants));
@ -298,7 +298,7 @@ nsPluginTag::nsPluginTag(uint32_t aId,
int64_t aLastModifiedTime,
bool aFromExtension,
int32_t aSandboxLevel,
uint16_t aBlocklistState)
uint32_t aBlocklistState)
: nsIInternalPluginTag(aName, aDescription, aFileName, aVersion, aMimeTypes,
aMimeDescriptions, aExtensions),
mId(aId),
@ -310,9 +310,8 @@ nsPluginTag::nsPluginTag(uint32_t aId,
mSandboxLevel(aSandboxLevel),
mIsSandboxLoggingEnabled(false),
mNiceFileName(),
mCachedBlocklistState(aBlocklistState),
mCachedBlocklistStateValid(true),
mIsFromExtension(aFromExtension)
mIsFromExtension(aFromExtension),
mBlocklistState(aBlocklistState)
{
}
@ -548,9 +547,7 @@ nsPluginTag::GetDisabled(bool* aDisabled)
bool
nsPluginTag::IsBlocklisted()
{
uint32_t blocklistState;
nsresult rv = GetBlocklistState(&blocklistState);
return NS_FAILED(rv) || blocklistState == nsIBlocklistService::STATE_BLOCKED;
return mBlocklistState == nsIBlocklistService::STATE_BLOCKED;
}
NS_IMETHODIMP
@ -722,48 +719,20 @@ nsPluginTag::GetNiceName(nsACString & aResult)
NS_IMETHODIMP
nsPluginTag::GetBlocklistState(uint32_t *aResult)
{
// If we're in the content process, assume our cache state to always be valid,
// as the only way it can be updated is via a plugin list push from the
// parent process.
if (!XRE_IsParentProcess()) {
*aResult = mCachedBlocklistState;
return NS_OK;
}
nsCOMPtr<nsIBlocklistService> blocklist =
do_GetService("@mozilla.org/extensions/blocklist;1");
if (!blocklist) {
*aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
}
// The EmptyString()s are so we use the currently running application
// and toolkit versions
else if (NS_FAILED(blocklist->GetPluginBlocklistState(this, EmptyString(),
EmptyString(), aResult))) {
*aResult = nsIBlocklistService::STATE_NOT_BLOCKED;
}
MOZ_ASSERT(*aResult <= UINT16_MAX);
mCachedBlocklistState = (uint16_t) *aResult;
mCachedBlocklistStateValid = true;
*aResult = mBlocklistState;
return NS_OK;
}
void
nsPluginTag::SetBlocklistState(uint16_t aBlocklistState)
nsPluginTag::SetBlocklistState(uint32_t aBlocklistState)
{
// We should only ever call this on content processes. Any calls in the parent
// process should route through GetBlocklistState since we'll have the
// blocklist service there.
MOZ_ASSERT(!XRE_IsParentProcess());
mCachedBlocklistState = aBlocklistState;
mCachedBlocklistStateValid = true;
mBlocklistState = aBlocklistState;
}
void
nsPluginTag::InvalidateBlocklistState()
uint32_t
nsPluginTag::BlocklistState()
{
mCachedBlocklistStateValid = false;
return mBlocklistState;
}
NS_IMETHODIMP

View File

@ -10,6 +10,7 @@
#include "nscore.h"
#include "nsCOMPtr.h"
#include "nsCOMArray.h"
#include "nsIBlocklistService.h"
#include "nsIPluginTag.h"
#include "nsITimer.h"
#include "nsString.h"
@ -108,7 +109,8 @@ public:
nsPluginTag(nsPluginInfo* aPluginInfo,
int64_t aLastModifiedTime,
bool fromExtension);
bool fromExtension,
uint32_t aBlocklistState);
nsPluginTag(const char* aName,
const char* aDescription,
const char* aFileName,
@ -120,6 +122,7 @@ public:
int32_t aVariants,
int64_t aLastModifiedTime,
bool fromExtension,
uint32_t aBlocklistState,
bool aArgsAreUTF8 = false);
nsPluginTag(uint32_t aId,
const char* aName,
@ -135,7 +138,7 @@ public:
int64_t aLastModifiedTime,
bool aFromExtension,
int32_t aSandboxLevel,
uint16_t aBlocklistState);
uint32_t aBlocklistState);
void TryUnloadPlugin(bool inShutdown);
@ -146,10 +149,11 @@ public:
void SetEnabled(bool enabled);
bool IsClicktoplay();
bool IsBlocklisted();
uint32_t BlocklistState();
PluginState GetPluginState();
void SetPluginState(PluginState state);
void SetBlocklistState(uint16_t aBlocklistState);
void SetBlocklistState(uint32_t aBlocklistState);
bool HasSameNameAndMimes(const nsPluginTag *aPluginTag) const;
const nsCString& GetNiceFileName() override;
@ -175,15 +179,12 @@ public:
int32_t mSandboxLevel;
bool mIsSandboxLoggingEnabled;
void InvalidateBlocklistState();
private:
virtual ~nsPluginTag();
nsCString mNiceFileName; // UTF-8
uint16_t mCachedBlocklistState;
bool mCachedBlocklistStateValid;
bool mIsFromExtension;
uint32_t mBlocklistState;
void InitMime(const char* const* aMimeTypes,
const char* const* aMimeDescriptions,

View File

@ -377,7 +377,7 @@ PluginWrapper.prototype = {
get blocklistState() {
let { tags: [tag] } = pluginFor(this);
return Services.blocklist.getPluginBlocklistState(tag);
return tag.blocklistState;
},
get blocklistURL() {

View File

@ -628,9 +628,6 @@ Blocklist.prototype = {
var oldAddonEntries = this._addonEntries;
var oldPluginEntries = this._pluginEntries;
this._addonEntries = [];
this._gfxEntries = [];
this._pluginEntries = [];
this._loadBlocklistFromXML(responseXML);
// We don't inform the users when the graphics blocklist changed at runtime.
@ -675,13 +672,6 @@ Blocklist.prototype = {
this._gfxEntries = [];
this._pluginEntries = [];
if (this._isBlocklistPreloaded()) {
Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
this._loadBlocklistFromString(this._preloadedBlocklistContent);
delete this._preloadedBlocklistContent;
return;
}
Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true);
var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
@ -806,16 +796,11 @@ Blocklist.prototype = {
return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
},
_isBlocklistPreloaded() {
return this._preloadedBlocklistContent != null;
},
/* Used for testing */
_clear() {
this._addonEntries = null;
this._gfxEntries = null;
this._pluginEntries = null;
this._preloadedBlocklistContent = null;
},
async _preloadBlocklist() {
@ -851,10 +836,15 @@ Blocklist.prototype = {
let text = await OS.File.read(path, { encoding: "utf-8" });
if (!this._addonEntries) {
// Store the content only if a sync load has not been performed in the meantime.
this._preloadedBlocklistContent = text;
}
await new Promise(resolve => {
Services.tm.idleDispatchToMainThread(() => {
if (!this.isLoaded) {
Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
this._loadBlocklistFromString(text);
}
resolve();
});
});
},
_loadBlocklistFromString(text) {
@ -876,6 +866,9 @@ Blocklist.prototype = {
},
_loadBlocklistFromXML(doc) {
this._addonEntries = [];
this._gfxEntries = [];
this._pluginEntries = [];
try {
var childNodes = doc.documentElement.childNodes;
for (let element of childNodes) {

View File

@ -7,34 +7,41 @@ add_task(async function() {
getService().wrappedJSObject;
let scope = ChromeUtils.import("resource://gre/modules/osfile.jsm", {});
// sync -> async
// sync -> async. Check that async code doesn't try to read the file
// once it's already been read synchronously.
let read = scope.OS.File.read;
let triedToRead = false;
scope.OS.File.read = () => triedToRead = true;
blocklist._loadBlocklist();
Assert.ok(blocklist.isLoaded);
await blocklist._preloadBlocklist();
Assert.ok(!blocklist._isBlocklistPreloaded());
Assert.ok(!triedToRead);
scope.OS.File.read = read;
blocklist._clear();
// async -> sync
info("sync -> async complete");
// async first. Check that once we preload the content, that is sufficient.
await blocklist._preloadBlocklist();
Assert.ok(!blocklist.isLoaded);
Assert.ok(blocklist._isBlocklistPreloaded());
blocklist._loadBlocklist();
Assert.ok(blocklist.isLoaded);
Assert.ok(!blocklist._isBlocklistPreloaded());
// Calling _loadBlocklist now would just re-load the list sync.
info("async test complete");
blocklist._clear();
// async -> sync -> async
let read = scope.OS.File.read;
scope.OS.File.read = function(...args) {
return new Promise((resolve, reject) => {
executeSoon(() => {
blocklist._loadBlocklist();
// Now do the async bit after all:
resolve(read(...args));
});
});
};
await blocklist._preloadBlocklist();
// We're mostly just checking this doesn't error out.
Assert.ok(blocklist.isLoaded);
Assert.ok(!blocklist._isBlocklistPreloaded());
info("mixed async/sync test complete");
});