Bug 1502954 - Remove livemarks code from toolkit. r=mak

Differential Revision: https://phabricator.services.mozilla.com/D12118

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mark Banner 2018-11-20 13:29:54 +00:00
Родитель 289a5bff70
Коммит 4eabeccb20
16 изменённых файлов: 17 добавлений и 1109 удалений

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

@ -96,8 +96,6 @@ var BookmarkPropertiesPanel = {
_keyword: "",
_postData: null,
_charSet: "",
_feedURI: null,
_siteURI: null,
_defaultInsertionPoint: null,
_hiddenRows: [],

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

@ -927,15 +927,15 @@ PlacesController.prototype = {
if (!didSuppressNotifications)
result.suppressNotifications = true;
function addData(type, index, feedURI) {
let wrapNode = PlacesUtils.wrapNode(node, type, feedURI);
function addData(type, index) {
let wrapNode = PlacesUtils.wrapNode(node, type);
dt.mozSetDataAt(type, wrapNode, index);
}
function addURIData(index, feedURI) {
addData(PlacesUtils.TYPE_X_MOZ_URL, index, feedURI);
addData(PlacesUtils.TYPE_UNICODE, index, feedURI);
addData(PlacesUtils.TYPE_HTML, index, feedURI);
function addURIData(index) {
addData(PlacesUtils.TYPE_X_MOZ_URL, index);
addData(PlacesUtils.TYPE_UNICODE, index);
addData(PlacesUtils.TYPE_HTML, index);
}
try {

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

@ -241,7 +241,6 @@
@RESPATH@/components/nsURLFormatter.manifest
@RESPATH@/components/nsURLFormatter.js
@RESPATH@/components/toolkitplaces.manifest
@RESPATH@/components/nsLivemarkService.js
@RESPATH@/components/nsTaggingService.js
@RESPATH@/components/UnifiedComplete.js
@RESPATH@/components/nsPlacesExpiration.js

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

@ -118,10 +118,6 @@ https://mismatch.untrusted.example.com:443 privileged,cert=untrusted
https://untrusted-expired.example.com:443 privileged,cert=untrustedandexpired
https://mismatch.untrusted-expired.example.com:443 privileged,cert=untrustedandexpired
# This is here so that we don't load the default live bookmark over
# the network in every test suite.
http://fxfeeds.mozilla.com:80
# Prevent safebrowsing tests from hitting the network for its-a-trap.html and
# its-an-attack.html.
http://www.itisatrap.org:80

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

@ -63,9 +63,6 @@
#define NS_NAVBOOKMARKSSERVICE_CONTRACTID \
"@mozilla.org/browser/nav-bookmarks-service;1"
#define NS_LIVEMARKSERVICE_CONTRACTID \
"@mozilla.org/browser/livemark-service;2"
#define NS_TAGGINGSERVICE_CONTRACTID \
"@mozilla.org/browser/tagging-service;1"

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

@ -92,13 +92,6 @@ const MATCH_ANYWHERE_UNMODIFIED = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE_UNMOD
const BEHAVIOR_BOOKMARK = Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
const SQLITE_MAX_VARIABLE_NUMBER = 999;
// Annotations which insertTree currently accepts. These should be going away
// soon, see bug 1460577.
const ACCEPTED_ANNOTATIONS = [
PlacesUtils.LMANNO_FEEDURI,
PlacesUtils.LMANNO_SITEURI,
];
var Bookmarks = Object.freeze({
/**
* Item's type constants.
@ -443,8 +436,6 @@ var Bookmarks = Object.freeze({
(b.dateAdded && b.lastModified >= b.dateAdded) },
index: { replaceWith: indexToUse++ },
source: { replaceWith: source },
annos: { validIf: b => false,
fixup: b => b.annos = b.annos.filter(anno => ACCEPTED_ANNOTATIONS.includes(anno.name))},
keyword: { validIf: b => b.type == TYPE_BOOKMARK },
charset: { validIf: b => b.type == TYPE_BOOKMARK },
postData: { validIf: b => b.type == TYPE_BOOKMARK },
@ -568,9 +559,6 @@ var Bookmarks = Object.freeze({
isTagging: false,
}));
// Note, annotations for livemark data are deleted from insertInfo
// within appendInsertionInfoForInfoArray, so we won't be duplicating
// the insertions here.
try {
await handleBookmarkItemSpecialData(itemId, item);
} catch (ex) {

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

@ -59,9 +59,6 @@ var EXPORTED_SYMBOLS = ["PlacesTransactions"];
* values:
* - url: a URL object, an nsIURI object, or a href.
* - urls: an array of urls, as above.
* - feedUrl: an url (as above), holding the url for a live bookmark.
* - siteUrl an url (as above), holding the url for the site with which
* a live bookmark is associated.
* - tag - a string.
* - tags: an array of strings.
* - guid, parentGuid, newParentGuid: a valid Places GUID string.
@ -899,7 +896,7 @@ DefineTransaction.verifyInput = function(input,
// Update the documentation at the top of this module if you add or
// remove properties.
DefineTransaction.defineInputProps(["url", "feedUrl", "siteUrl"],
DefineTransaction.defineInputProps(["url"],
DefineTransaction.urlValidate, null);
DefineTransaction.defineInputProps(["guid", "parentGuid", "newParentGuid"],
DefineTransaction.guidValidate);
@ -1389,18 +1386,13 @@ PT.Remove.prototype = {
}
let removeThem = async function() {
let bmsToRemove = [];
for (let info of removedItems) {
if (info.annos &&
info.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
await PlacesUtils.livemarks.removeLivemark({ guid: info.guid });
} else {
bmsToRemove.push({guid: info.guid});
}
}
if (bmsToRemove.length) {
await PlacesUtils.bookmarks.remove(bmsToRemove);
if (removedItems.length) {
// We have to pass just the guids as although remove() accepts full
// info items, promiseBookmarksTree returns dateAdded and lastModified
// as PRTime rather than date types.
await PlacesUtils.bookmarks.remove(removedItems.map(info => {
return { guid: info.guid};
}));
}
};
await removeThem();

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

@ -259,8 +259,7 @@ const SYNC_BOOKMARK_VALIDATORS = Object.freeze({
recordId: simpleValidateFunc(v => typeof v == "string" && (
(PlacesSyncUtils.bookmarks.ROOTS.includes(v) || PlacesUtils.isValidGuid(v)))),
parentRecordId: v => SYNC_BOOKMARK_VALIDATORS.recordId(v),
// Sync uses kinds instead of types, which distinguish between livemarks and
// queries.
// Sync uses kinds instead of types.
kind: simpleValidateFunc(v => typeof v == "string" &&
Object.values(PlacesSyncUtils.bookmarks.KINDS).includes(v)),
query: simpleValidateFunc(v => v === null || (typeof v == "string" && v)),
@ -400,6 +399,7 @@ var PlacesUtils = {
// Used to track the action that populated the clipboard.
TYPE_X_MOZ_PLACE_ACTION: "text/x-moz-place-action",
// Deprecated: Remaining only for supporting migration of old livemarks.
LMANNO_FEEDURI: "livemark/feedURI",
LMANNO_SITEURI: "livemark/siteURI",
CHARSET_ANNO: "URIProperties/characterSet",
@ -1827,10 +1827,6 @@ XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "tagging",
"@mozilla.org/browser/tagging-service;1",
"nsITaggingService");
XPCOMUtils.defineLazyServiceGetter(PlacesUtils, "livemarks",
"@mozilla.org/browser/livemark-service;2",
"mozIAsyncLivemarks");
XPCOMUtils.defineLazyGetter(this, "bundle", function() {
const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
return Services.strings.createBundle(PLACES_STRING_BUNDLE_URI);

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

@ -16,7 +16,6 @@ XPIDL_MODULE = 'places'
if CONFIG['MOZ_PLACES']:
XPIDL_SOURCES += [
'mozIAsyncHistory.idl',
'mozIAsyncLivemarks.idl',
'mozIColorAnalyzer.idl',
'mozIPlacesAutoComplete.idl',
'mozIPlacesPendingOperation.idl',
@ -77,7 +76,6 @@ if CONFIG['MOZ_PLACES']:
EXTRA_COMPONENTS += [
'ColorAnalyzer.js',
'nsLivemarkService.js',
'nsPlacesExpiration.js',
'nsTaggingService.js',
'PageIconProtocolHandler.js',

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

@ -1,181 +0,0 @@
/* 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 "nsISupports.idl"
interface nsIURI;
interface mozILivemarkInfo;
interface mozILivemark;
interface nsINavHistoryResultObserver;
[scriptable, uuid(672387b7-a75d-4e8f-9b49-5c1dcbfff46b)]
interface mozIAsyncLivemarks : nsISupports
{
/**
* Removes an existing livemark.
*
* @param aLivemarkInfo
* mozILivemarkInfo object containing either an id or a guid of the
* livemark to remove.
*
* @return {Promise}
* @throws NS_ERROR_INVALID_ARG if the id/guid is invalid.
*/
jsval removeLivemark(in jsval aLivemarkInfo);
/**
* Gets an existing livemark.
*
* @param aLivemarkInfo
* mozILivemarkInfo object containing either an id or a guid of the
* livemark to retrieve.
*
* @return {Promise}
* @throws NS_ERROR_INVALID_ARG if the id/guid is invalid or an invalid
* callback is provided.
*/
jsval getLivemark(in jsval aLivemarkInfo);
/**
* Reloads all livemarks if they are expired or if forced to do so.
*
* @param [optional]aForceUpdate
* If set to true forces a reload even if contents are still valid.
*
* @note The update process is asynchronous, observers registered through
* registerForUpdates will be notified of updated contents.
*/
void reloadLivemarks([optional]in boolean aForceUpdate);
void handlePlacesEvents(in jsval aEvents);
jsval invalidateCachedLivemarks();
};
[scriptable, uuid(3a3c5e8f-ec4a-4086-ae0a-d16420d30c9f)]
interface mozILivemarkInfo : nsISupports
{
/**
* Id of the bookmarks folder representing this livemark.
*
* @deprecated Use guid instead.
*/
readonly attribute long long id;
/**
* The globally unique identifier of this livemark.
*/
readonly attribute ACString guid;
/**
* Title of this livemark.
*/
readonly attribute AString title;
/**
* Id of the bookmarks parent folder containing this livemark.
*
* @deprecated Use parentGuid instead.
*/
readonly attribute long long parentId;
/**
* Guid of the bookmarks parent folder containing this livemark.
*/
readonly attribute long long parentGuid;
/**
* The position of this livemark in the bookmarks parent folder.
*/
readonly attribute long index;
/**
* Time this livemark was created.
*/
readonly attribute PRTime dateAdded;
/**
* Time this livemark's details were last modified. Doesn't track changes to
* the livemark contents.
*/
readonly attribute PRTime lastModified;
/**
* The URI of the syndication feed associated with this livemark.
*/
readonly attribute nsIURI feedURI;
/**
* The URI of the website associated with this livemark.
*/
readonly attribute nsIURI siteURI;
};
[scriptable, uuid(9f6fdfae-db9a-4bd8-bde1-148758cf1b18)]
interface mozILivemark : mozILivemarkInfo
{
// Indicates the livemark is inactive.
const unsigned short STATUS_READY = 0;
// Indicates the livemark is fetching new contents.
const unsigned short STATUS_LOADING = 1;
// Indicates the livemark failed to fetch new contents.
const unsigned short STATUS_FAILED = 2;
/**
* Status of this livemark. One of the STATUS_* constants above.
*/
readonly attribute unsigned short status;
/**
* Reload livemark contents if they are expired or if forced to do so.
*
* @param [optional]aForceUpdate
* If set to true forces a reload even if contents are still valid.
*
* @note The update process is asynchronous, it's possible to register a
* result observer to be notified of updated contents through
* registerForUpdates.
*/
void reload([optional]in boolean aForceUpdate);
/**
* Returns an array of nsINavHistoryResultNode objects, representing children
* of this livemark. The nodes will have aContainerNode as parent.
*
* @param aContainerNode
* Object implementing nsINavHistoryContainerResultNode, to be used as
* parent of the livemark nodes.
*/
jsval getNodesForContainer(in jsval aContainerNode);
/**
* Registers a container node for updates on this livemark.
* When the livemark contents change, an invalidateContainer(aContainerNode)
* request is sent to aResultObserver.
*
* @param aContainerNode
* Object implementing nsINavHistoryContainerResultNode, representing
* this livemark.
* @param aResultObserver
* The nsINavHistoryResultObserver that should be notified of changes
* to the livemark contents.
*/
void registerForUpdates(in jsval aContainerNode,
in nsINavHistoryResultObserver aResultObserver);
/**
* Unregisters a previously registered container node.
*
* @param aContainerNode
* Object implementing nsINavHistoryContainerResultNode, representing
* this livemark.
*
* @note it's suggested to always unregister containers that are no more used,
* to free up the associated resources. A good time to do so is when
* the container gets closed.
*/
void unregisterForUpdates(in jsval aContainerNode);
};

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

@ -1,841 +0,0 @@
/* 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/. */
// Modules and services.
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
ChromeUtils.defineModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyGetter(this, "history", function() {
let livemarks = PlacesUtils.livemarks;
// Lazily add an history observer when it's actually needed.
PlacesUtils.history.addObserver(livemarks, true);
return PlacesUtils.history;
});
// Constants
// Delay between reloads of consecute livemarks.
const RELOAD_DELAY_MS = 500;
// Expire livemarks after this time.
const EXPIRE_TIME_MS = 3600000; // 1 hour.
// Expire livemarks after this time on error.
const ONERROR_EXPIRE_TIME_MS = 300000; // 5 minutes.
// Livemarks cache.
XPCOMUtils.defineLazyGetter(this, "CACHE_SQL", () => {
function getAnnoSQLFragment(aAnnoParam) {
return `SELECT a.content
FROM moz_items_annos a
JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id
WHERE a.item_id = b.id
AND n.name = ${aAnnoParam}`;
}
return `SELECT b.id, b.title, b.parent As parentId, b.position AS 'index',
b.guid, b.dateAdded, b.lastModified, p.guid AS parentGuid,
( ${getAnnoSQLFragment(":feedURI_anno")} ) AS feedURI,
( ${getAnnoSQLFragment(":siteURI_anno")} ) AS siteURI
FROM moz_bookmarks b
JOIN moz_bookmarks p ON b.parent = p.id
JOIN moz_items_annos a ON a.item_id = b.id
JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
WHERE b.type = :folder_type
AND n.name = :feedURI_anno`;
});
/**
* Convert a Date object to a PRTime (microseconds).
*
* @param date
* the Date object to convert.
* @return microseconds from the epoch.
*/
function toPRTime(date) {
return date * 1000;
}
/**
* Convert a PRTime to a Date object.
*
* @param time
* microseconds from the epoch.
* @return a Date object or undefined if time was not defined.
*/
function toDate(time) {
return time ? new Date(parseInt(time / 1000)) : undefined;
}
// LivemarkService
function LivemarkService() {
// Cleanup on shutdown.
Services.obs.addObserver(this, PlacesUtils.TOPIC_SHUTDOWN, true);
// Observe bookmarks but don't init the service just for that.
PlacesUtils.bookmarks.addObserver(this, true);
this._placesListener = new PlacesWeakCallbackWrapper(
this.handlePlacesEvents.bind(this));
PlacesObservers.addListener(["page-visited"], this._placesListener);
this._livemarksMap = null;
this._promiseLivemarksMapReady = Promise.resolve();
}
LivemarkService.prototype = {
_withLivemarksMap(func) {
let promise = this._promiseLivemarksMapReady.then(async () => {
if (!this._livemarksMap) {
this._livemarksMap = new Map();
let conn = await PlacesUtils.promiseDBConnection();
let rows = await conn.executeCached(CACHE_SQL,
{ folder_type: Ci.nsINavBookmarksService.TYPE_FOLDER,
feedURI_anno: PlacesUtils.LMANNO_FEEDURI,
siteURI_anno: PlacesUtils.LMANNO_SITEURI });
for (let row of rows) {
let siteURI = row.getResultByName("siteURI");
let livemark = new Livemark({
id: row.getResultByName("id"),
guid: row.getResultByName("guid"),
title: row.getResultByName("title"),
parentId: row.getResultByName("parentId"),
parentGuid: row.getResultByName("parentGuid"),
index: row.getResultByName("index"),
dateAdded: row.getResultByName("dateAdded"),
lastModified: row.getResultByName("lastModified"),
feedURI: NetUtil.newURI(row.getResultByName("feedURI")),
siteURI: siteURI ? NetUtil.newURI(siteURI) : null,
});
this._livemarksMap.set(livemark.guid, livemark);
}
}
return func(this._livemarksMap);
});
this._promiseLivemarksMapReady = promise.catch(_ => {});
return promise;
},
_reloading: false,
_startReloadTimer(livemarksMap, forceUpdate, reloaded) {
if (this._reloadTimer) {
this._reloadTimer.cancel();
} else {
this._reloadTimer = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
}
this._reloading = true;
this._reloadTimer.initWithCallback(() => {
// Find first livemark to be reloaded.
for (let [ guid, livemark ] of livemarksMap) {
if (!reloaded.has(guid)) {
reloaded.add(guid);
livemark.reload(forceUpdate);
this._startReloadTimer(livemarksMap, forceUpdate, reloaded);
return;
}
}
// All livemarks have been reloaded.
this._reloading = false;
this._forceUpdate = false;
}, RELOAD_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
},
// nsIObserver
observe(aSubject, aTopic, aData) {
if (aTopic == PlacesUtils.TOPIC_SHUTDOWN) {
this._invalidateCachedLivemarks({
// No need to restart the reload timer on shutdown.
keepReloading: false,
}).catch(Cu.reportError);
}
},
// mozIAsyncLivemarks
removeLivemark(aLivemarkInfo) {
if (!aLivemarkInfo) {
throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
}
// Accept either a guid or an id.
let hasGuid = "guid" in aLivemarkInfo;
let hasId = "id" in aLivemarkInfo;
if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
(hasId && aLivemarkInfo.id < 1) ||
(!hasId && !hasGuid)) {
throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
}
return this._withLivemarksMap(async livemarksMap => {
if (!aLivemarkInfo.guid)
aLivemarkInfo.guid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.id);
if (!livemarksMap.has(aLivemarkInfo.guid))
throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);
await PlacesUtils.bookmarks.remove(aLivemarkInfo.guid,
{ source: aLivemarkInfo.source });
});
},
reloadLivemarks(aForceUpdate) {
// Check if there's a currently running reload, to save some useless work.
let notWorthRestarting =
this._forceUpdate || // We're already forceUpdating.
!aForceUpdate; // The caller didn't request a forced update.
if (this._reloading && notWorthRestarting) {
// Ignore this call.
return;
}
this._withLivemarksMap(livemarksMap => {
this._forceUpdate = !!aForceUpdate;
// Livemarks reloads happen on a timer for performance reasons.
this._startReloadTimer(livemarksMap, this._forceUpdate, new Set());
});
},
getLivemark(aLivemarkInfo) {
if (!aLivemarkInfo) {
throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
}
// Accept either a guid or an id.
let hasGuid = "guid" in aLivemarkInfo;
let hasId = "id" in aLivemarkInfo;
if ((hasGuid && !/^[a-zA-Z0-9\-_]{12}$/.test(aLivemarkInfo.guid)) ||
(hasId && aLivemarkInfo.id < 1) ||
(!hasId && !hasGuid)) {
throw new Components.Exception("Invalid arguments", Cr.NS_ERROR_INVALID_ARG);
}
return this._withLivemarksMap(async livemarksMap => {
if (!aLivemarkInfo.guid)
aLivemarkInfo.guid = await PlacesUtils.promiseItemGuid(aLivemarkInfo.id);
if (!livemarksMap.has(aLivemarkInfo.guid))
throw new Components.Exception("Invalid livemark", Cr.NS_ERROR_INVALID_ARG);
return livemarksMap.get(aLivemarkInfo.guid);
});
},
_invalidateCachedLivemarks({ keepReloading = true } = {}) {
// Cancel pending reloads, since any livemarks we're currently reloading
// might no longer be valid.
let wasReloading = this._reloading;
this._reloading = false;
let wasForceUpdating = this._forceUpdate;
this._forceUpdate = false;
if (this._reloadTimer) {
this._reloadTimer.cancel();
}
// Clear out the livemarks cache.
let promise = this._promiseLivemarksMapReady.then(() => {
let livemarksMap = this._livemarksMap;
this._livemarksMap = null;
if (livemarksMap) {
// Stop any ongoing network fetch.
for (let livemark of livemarksMap.values()) {
livemark.terminate();
}
}
});
this._promiseLivemarksMapReady = promise.catch(_ => {});
// Restart the timer if we were reloading before invalidating.
if (keepReloading) {
if (wasReloading) {
this.reloadLivemarks(wasForceUpdating);
}
} else {
delete this._reloadTimer;
}
return promise;
},
invalidateCachedLivemarks() {
return this._invalidateCachedLivemarks();
},
handlePlacesEvents(aEvents) {
if (!aEvents) {
throw new Components.Exception("Invalid arguments",
Cr.NS_ERROR_INVALID_ARG);
}
this._withLivemarksMap(livemarksMap => {
for (let event of aEvents) {
for (let livemark of livemarksMap.values()) {
livemark.updateURIVisitedStatus(event.url, true);
}
}
});
},
// nsINavBookmarkObserver
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemVisited() {},
onItemChanged(id, property, isAnno, value, lastModified, itemType, parentId,
guid, parentGuid) {
if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
return;
this._withLivemarksMap(livemarksMap => {
if (livemarksMap.has(guid)) {
let livemark = livemarksMap.get(guid);
if (property == "title") {
livemark.title = value;
}
livemark.lastModified = lastModified;
}
});
},
onItemMoved(id, parentId, oldIndex, newParentId, newIndex, itemType, guid,
oldParentGuid, newParentGuid) {
if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
return;
this._withLivemarksMap(livemarksMap => {
if (livemarksMap.has(guid)) {
let livemark = livemarksMap.get(guid);
livemark.parentId = newParentId;
livemark.parentGuid = newParentGuid;
livemark.index = newIndex;
}
});
},
onItemRemoved(id, parentId, index, itemType, uri, guid, parentGuid) {
if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
return;
this._withLivemarksMap(livemarksMap => {
if (livemarksMap.has(guid)) {
let livemark = livemarksMap.get(guid);
livemark.terminate();
livemarksMap.delete(guid);
}
});
},
skipDescendantsOnItemRemoval: false,
skipTags: true,
// nsINavHistoryObserver
onPageChanged() {},
onTitleChanged() {},
onDeleteVisits() {},
onClearHistory() {
this._withLivemarksMap(livemarksMap => {
for (let livemark of livemarksMap.values()) {
livemark.updateURIVisitedStatus(null, false);
}
});
},
onDeleteURI(aURI) {
this._withLivemarksMap(livemarksMap => {
for (let livemark of livemarksMap.values()) {
livemark.updateURIVisitedStatus(aURI.spec, false);
}
});
},
// nsISupports
classID: Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}"),
_xpcom_factory: XPCOMUtils.generateSingletonFactory(LivemarkService),
QueryInterface: ChromeUtils.generateQI([
Ci.mozIAsyncLivemarks,
Ci.nsINavBookmarkObserver,
Ci.nsINavHistoryObserver,
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]),
};
// Livemark
/**
* Object used internally to represent a livemark.
*
* @param aLivemarkInfo
* Object containing information on the livemark. If the livemark is
* not included in the object, a new livemark will be created.
*
* @note terminate() must be invoked before getting rid of this object.
*/
function Livemark(aLivemarkInfo) {
this.id = aLivemarkInfo.id;
this.guid = aLivemarkInfo.guid;
this.feedURI = aLivemarkInfo.feedURI;
this.siteURI = aLivemarkInfo.siteURI || null;
this.title = aLivemarkInfo.title;
this.parentId = aLivemarkInfo.parentId;
this.parentGuid = aLivemarkInfo.parentGuid;
this.index = aLivemarkInfo.index;
this.dateAdded = aLivemarkInfo.dateAdded;
this.lastModified = aLivemarkInfo.lastModified;
this._status = Ci.mozILivemark.STATUS_READY;
// Hash of resultObservers, hashed by container.
this._resultObservers = new Map();
// Sorted array of objects representing livemark children in the form
// { uri, title, visited }.
this._children = [];
// Keeps a separate array of nodes for each requesting container, hashed by
// the container itself.
this._nodes = new Map();
this.loadGroup = null;
this.expireTime = 0;
}
Livemark.prototype = {
get status() {
return this._status;
},
set status(val) {
if (this._status != val) {
this._status = val;
this._invalidateRegisteredContainers();
}
return this._status;
},
writeSiteURI(aSiteURI, aSource) {
if (!aSiteURI) {
PlacesUtils.annotations.removeItemAnnotation(this.id,
PlacesUtils.LMANNO_SITEURI,
aSource);
this.siteURI = null;
return;
}
// Security check the site URI against the feed URI principal.
let secMan = Services.scriptSecurityManager;
let feedPrincipal = secMan.createCodebasePrincipal(this.feedURI, {});
try {
secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
} catch (ex) {
return;
}
PlacesUtils.annotations
.setItemAnnotation(this.id, PlacesUtils.LMANNO_SITEURI,
aSiteURI.spec,
0, PlacesUtils.annotations.EXPIRE_NEVER,
aSource, true);
this.siteURI = aSiteURI;
},
/**
* Tries to updates the livemark if needed.
* The update process is asynchronous.
*
* @param [optional] aForceUpdate
* If true will try to update the livemark even if its contents have
* not yet expired.
*/
updateChildren(aForceUpdate) {
// Check if the livemark is already updating.
if (this.status == Ci.mozILivemark.STATUS_LOADING)
return;
// Check the TTL/expiration on this, to check if there is no need to update
// this livemark.
if (!aForceUpdate && this.children.length && this.expireTime > Date.now())
return;
this.status = Ci.mozILivemark.STATUS_LOADING;
// Setting the status notifies observers that may remove the livemark.
if (this._terminated)
return;
try {
// Create a load group for the request. This will allow us to
// automatically keep track of redirects, so we can always
// cancel the channel.
let loadgroup = Cc["@mozilla.org/network/load-group;1"].
createInstance(Ci.nsILoadGroup);
// Creating a CodeBasePrincipal and using it as the loadingPrincipal
// is *not* desired and is only tolerated within this file.
// TODO: Find the right OriginAttributes and pass something other
// than {} to .createCodeBasePrincipal().
let channel = NetUtil.newChannel({
uri: this.feedURI,
loadingPrincipal: Services.scriptSecurityManager.createCodebasePrincipal(this.feedURI, {}),
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_XMLHTTPREQUEST,
}).QueryInterface(Ci.nsIHttpChannel);
channel.loadGroup = loadgroup;
channel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND |
Ci.nsIRequest.LOAD_BYPASS_CACHE;
channel.requestMethod = "GET";
channel.setRequestHeader("X-Moz", "livebookmarks", false);
// Stream the result to the feed parser with this listener
let listener = new LivemarkLoadListener(this);
channel.notificationCallbacks = listener;
channel.asyncOpen2(listener);
this.loadGroup = loadgroup;
} catch (ex) {
this.status = Ci.mozILivemark.STATUS_FAILED;
}
},
reload(aForceUpdate) {
this.updateChildren(aForceUpdate);
},
get children() {
return this._children;
},
set children(val) {
this._children = val;
// Discard the previous cached nodes, new ones should be generated.
for (let container of this._resultObservers.keys()) {
this._nodes.delete(container);
}
// Update visited status for each entry.
for (let child of this._children) {
history.hasVisits(child.uri).then(isVisited => {
this.updateURIVisitedStatus(child.uri.spec, isVisited);
}).catch(Cu.reportError);
}
return this._children;
},
_isURIVisited(aURI) {
return this.children.some(child => child.uri.equals(aURI) && child.visited);
},
getNodesForContainer(aContainerNode) {
if (this._nodes.has(aContainerNode)) {
return this._nodes.get(aContainerNode);
}
let livemark = this;
let nodes = [];
let now = Date.now() * 1000;
for (let child of this.children) {
let node = {
// The QueryInterface is needed cause aContainerNode is a jsval.
// This is required to avoid issues with scriptable wrappers that would
// not allow the view to correctly set expandos.
get parent() {
return aContainerNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
},
get parentResult() {
return this.parent.parentResult;
},
get uri() {
return child.uri.spec;
},
get type() {
return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
},
get title() {
return child.title;
},
get accessCount() {
return Number(livemark._isURIVisited(NetUtil.newURI(this.uri)));
},
get time() {
return 0;
},
get icon() {
return "";
},
get indentLevel() {
return this.parent.indentLevel + 1;
},
get bookmarkIndex() {
return -1;
},
get itemId() {
return -1;
},
get dateAdded() {
return now;
},
get lastModified() {
return now;
},
get tags() {
return PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(this.uri)).join(", ");
},
QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryResultNode]),
};
nodes.push(node);
}
this._nodes.set(aContainerNode, nodes);
return nodes;
},
registerForUpdates(aContainerNode, aResultObserver) {
this._resultObservers.set(aContainerNode, aResultObserver);
},
unregisterForUpdates(aContainerNode) {
this._resultObservers.delete(aContainerNode);
this._nodes.delete(aContainerNode);
},
_invalidateRegisteredContainers() {
for (let [ container, observer ] of this._resultObservers) {
observer.invalidateContainer(container);
}
},
/**
* Updates the visited status of nodes observing this livemark.
*
* @param href
* If provided will update nodes having the given uri,
* otherwise any node.
* @param visitedStatus
* Whether the nodes should be set as visited.
*/
updateURIVisitedStatus(href, visitedStatus) {
let wasVisited = false;
for (let child of this.children) {
if (!href || child.uri.spec == href) {
wasVisited = child.visited;
child.visited = visitedStatus;
}
}
for (let [ container, observer ] of this._resultObservers) {
if (this._nodes.has(container)) {
let nodes = this._nodes.get(container);
for (let node of nodes) {
if (!href || node.uri == href) {
Services.tm.dispatchToMainThread(() => {
observer.nodeHistoryDetailsChanged(node, node.time, wasVisited);
});
}
}
}
}
},
/**
* Terminates the livemark entry, cancelling any ongoing load.
* Must be invoked before destroying the entry.
*/
terminate() {
// Avoid handling any updateChildren request from now on.
this._terminated = true;
this.abort();
},
/**
* Aborts the livemark loading if needed.
*/
abort() {
this.status = Ci.mozILivemark.STATUS_FAILED;
if (this.loadGroup) {
this.loadGroup.cancel(Cr.NS_BINDING_ABORTED);
this.loadGroup = null;
}
},
QueryInterface: ChromeUtils.generateQI([
Ci.mozILivemark,
]),
};
// LivemarkLoadListener
/**
* Object used internally to handle loading a livemark's contents.
*
* @param aLivemark
* The Livemark that is loading.
*/
function LivemarkLoadListener(aLivemark) {
this._livemark = aLivemark;
this._processor = null;
this._isAborted = false;
this._ttl = EXPIRE_TIME_MS;
}
LivemarkLoadListener.prototype = {
abort(aException) {
if (!this._isAborted) {
this._isAborted = true;
this._livemark.abort();
this._setResourceTTL(ONERROR_EXPIRE_TIME_MS);
}
},
// nsIFeedResultListener
handleResult(aResult) {
if (this._isAborted) {
return;
}
try {
// We need this to make sure the item links are safe
let feedPrincipal =
Services.scriptSecurityManager
.createCodebasePrincipal(this._livemark.feedURI, {});
// Enforce well-formedness because the existing code does
if (!aResult || !aResult.doc || aResult.bozo) {
throw new Components.Exception("", Cr.NS_ERROR_FAILURE);
}
let feed = aResult.doc.QueryInterface(Ci.nsIFeed);
let siteURI = this._livemark.siteURI;
if (feed.link && (!siteURI || !feed.link.equals(siteURI))) {
siteURI = feed.link;
this._livemark.writeSiteURI(siteURI);
}
// Insert feed items.
let livemarkChildren = [];
for (let i = 0; i < feed.items.length; ++i) {
let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
let uri = entry.link || siteURI;
if (!uri) {
continue;
}
try {
Services.scriptSecurityManager
.checkLoadURIWithPrincipal(feedPrincipal, uri,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
} catch (ex) {
continue;
}
let title = entry.title ? entry.title.plainText() : "";
livemarkChildren.push({ uri, title, visited: false });
}
this._livemark.children = livemarkChildren;
} catch (ex) {
Cu.reportError(ex);
this.abort(ex);
} finally {
this._processor.listener = null;
this._processor = null;
}
},
onDataAvailable(aRequest, aContext, aInputStream, aSourceOffset, aCount) {
if (this._processor) {
this._processor.onDataAvailable(aRequest, aContext, aInputStream,
aSourceOffset, aCount);
}
},
onStartRequest(aRequest, aContext) {
if (this._isAborted) {
throw new Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
}
let channel = aRequest.QueryInterface(Ci.nsIChannel);
try {
// Parse feed data as it comes in
this._processor = Cc["@mozilla.org/feed-processor;1"].
createInstance(Ci.nsIFeedProcessor);
this._processor.listener = this;
this._processor.parseAsync(null, channel.URI);
this._processor.onStartRequest(aRequest, aContext);
} catch (ex) {
Cu.reportError("Livemark Service: feed processor received an invalid channel for " + channel.URI.spec);
this.abort(ex);
}
},
onStopRequest(aRequest, aContext, aStatus) {
if (!Components.isSuccessCode(aStatus)) {
this.abort();
return;
}
// Set an expiration on the livemark, to reloading the data in future.
try {
if (this._processor) {
this._processor.onStopRequest(aRequest, aContext, aStatus);
}
// Calculate a new ttl
let channel = aRequest.QueryInterface(Ci.nsICachingChannel);
if (channel) {
let entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntry);
if (entryInfo) {
// nsICacheEntry returns value as seconds.
let expireTime = entryInfo.expirationTime * 1000;
let nowTime = Date.now();
// Note, expireTime can be 0, see bug 383538.
if (expireTime > nowTime) {
this._setResourceTTL(Math.max((expireTime - nowTime),
EXPIRE_TIME_MS));
return;
}
}
}
this._setResourceTTL(EXPIRE_TIME_MS);
} catch (ex) {
this.abort(ex);
} finally {
if (this._livemark.status == Ci.mozILivemark.STATUS_LOADING) {
this._livemark.status = Ci.mozILivemark.STATUS_READY;
}
this._livemark.locked = false;
this._livemark.loadGroup = null;
}
},
_setResourceTTL(aMilliseconds) {
this._livemark.expireTime = Date.now() + aMilliseconds;
},
// nsIInterfaceRequestor
getInterface(aIID) {
return this.QueryInterface(aIID);
},
// nsISupports
QueryInterface: ChromeUtils.generateQI([
Ci.nsIFeedResultListener,
Ci.nsIStreamListener,
Ci.nsIRequestObserver,
Ci.nsIInterfaceRequestor,
]),
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([LivemarkService]);

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

@ -38,7 +38,6 @@ PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
#define BOOKMARKS_ANNO_PREFIX "bookmarks/"
#define BOOKMARKS_TOOLBAR_FOLDER_ANNO NS_LITERAL_CSTRING(BOOKMARKS_ANNO_PREFIX "toolbarFolder")
#define FEED_URI_ANNO NS_LITERAL_CSTRING("livemark/feedURI")
#define SYNC_PARENT_ANNO "sync/parent"
#define SQLITE_MAX_VARIABLE_NUMBER 999
@ -821,18 +820,6 @@ nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aTitle,
return NS_OK;
}
bool nsNavBookmarks::IsLivemark(int64_t aFolderId)
{
nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
NS_ENSURE_TRUE(annosvc, false);
bool isLivemark;
nsresult rv = annosvc->ItemHasAnnotation(aFolderId,
FEED_URI_ANNO,
&isLivemark);
NS_ENSURE_SUCCESS(rv, false);
return isLivemark;
}
nsresult
nsNavBookmarks::GetDescendantChildren(int64_t aFolderId,
const nsACString& aFolderGuid,
@ -1722,13 +1709,6 @@ nsNavBookmarks::ProcessFolderNodeRow(
}
}
else if (itemType == TYPE_FOLDER) {
// ExcludeReadOnlyFolders currently means "ExcludeLivemarks" (to be fixed in
// bug 1072833)
if (aOptions->ExcludeReadOnlyFolders()) {
if (IsLivemark(id))
return NS_OK;
}
nsAutoCString title;
bool isNull;
rv = aRow->GetIsNull(nsNavHistory::kGetInfoIndex_Title, &isNull);

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

@ -235,15 +235,6 @@ private:
~nsNavBookmarks();
/**
* Checks whether or not aFolderId points to a live bookmark.
*
* @param aFolderId
* the item-id of the folder to check.
* @return true if aFolderId points to live bookmarks, false otherwise.
*/
bool IsLivemark(int64_t aFolderId);
nsresult AdjustIndices(int64_t aFolder,
int32_t aStartIndex,
int32_t aEndIndex,

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

@ -43,7 +43,7 @@ add_task(async function run_test() {
tagssvc.tagURI(uri5, ["bar cheese"]);
tagssvc.tagURI(uri6, ["foo bar cheese"]);
// exclude livemark items, search for "item", should get one result
// Search for "item", should get one result
var options = histsvc.getNewQueryOptions();
options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;

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

@ -17,7 +17,6 @@ var testServices = [
["nsINavBookmarksService", "nsINavHistoryObserver"],
["createFolder", "getObservers", "onFrecencyChanged", "onTitleChanged", "onDeleteURI"],
],
["browser/livemark-service;2", ["mozIAsyncLivemarks"], ["reloadLivemarks"]],
["browser/favicon-service;1", ["nsIFaviconService"], []],
["browser/tagging-service;1", ["nsITaggingService"], []],
];

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

@ -1,7 +1,3 @@
# nsLivemarkService.js
component {dca61eb5-c7cd-4df1-b0fb-d0722baba251} nsLivemarkService.js
contract @mozilla.org/browser/livemark-service;2 {dca61eb5-c7cd-4df1-b0fb-d0722baba251}
# nsTaggingService.js
component {bbc23860-2553-479d-8b78-94d9038334f7} nsTaggingService.js
contract @mozilla.org/browser/tagging-service;1 {bbc23860-2553-479d-8b78-94d9038334f7}