mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-12 12:55:46 +00:00
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:
parent
289a5bff70
commit
4eabeccb20
@ -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}
|
||||
|
Loading…
Reference in New Issue
Block a user