1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5/* This is a JavaScript module (JSM) to be imported via
6 * Components.utils.import() and acts as a singleton. Only the following
7 * listed symbols will exposed on import, and only when and where imported.
8 */
9
10var EXPORTED_SYMBOLS = [
11  "PlacesItem",
12  "Bookmark",
13  "Separator",
14  "BookmarkFolder",
15  "DumpBookmarks",
16];
17
18const { PlacesBackups } = ChromeUtils.import(
19  "resource://gre/modules/PlacesBackups.jsm"
20);
21const { PlacesSyncUtils } = ChromeUtils.import(
22  "resource://gre/modules/PlacesSyncUtils.jsm"
23);
24const { PlacesUtils } = ChromeUtils.import(
25  "resource://gre/modules/PlacesUtils.jsm"
26);
27const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
28const { Logger } = ChromeUtils.import("resource://tps/logger.jsm");
29
30async function DumpBookmarks() {
31  let [bookmarks] = await PlacesBackups.getBookmarksTree();
32  Logger.logInfo(
33    "Dumping Bookmarks...\n" + JSON.stringify(bookmarks, undefined, 2) + "\n\n"
34  );
35}
36
37/**
38 * extend, causes a child object to inherit from a parent
39 */
40function extend(child, supertype) {
41  child.prototype.__proto__ = supertype.prototype;
42}
43/**
44 * PlacesItemProps object, holds properties for places items
45 */
46function PlacesItemProps(props) {
47  this.location = null;
48  this.uri = null;
49  this.keyword = null;
50  this.title = null;
51  this.after = null;
52  this.before = null;
53  this.folder = null;
54  this.position = null;
55  this.delete = false;
56  this.tags = null;
57  this.last_item_pos = null;
58  this.type = null;
59
60  for (var prop in props) {
61    if (prop in this) {
62      this[prop] = props[prop];
63    }
64  }
65}
66
67/**
68 * PlacesItem object.  Base class for places items.
69 */
70function PlacesItem(props) {
71  this.props = new PlacesItemProps(props);
72  if (this.props.location == null) {
73    this.props.location = "menu";
74  }
75  if ("changes" in props) {
76    this.updateProps = new PlacesItemProps(props.changes);
77  } else {
78    this.updateProps = null;
79  }
80}
81
82/**
83 * Instance methods for generic places items.
84 */
85PlacesItem.prototype = {
86  // an array of possible root folders for places items
87  _bookmarkFolders: {
88    places: PlacesUtils.bookmarks.rootGuid,
89    menu: PlacesUtils.bookmarks.menuGuid,
90    tags: PlacesUtils.bookmarks.tagsGuid,
91    unfiled: PlacesUtils.bookmarks.unfiledGuid,
92    toolbar: PlacesUtils.bookmarks.toolbarGuid,
93    mobile: PlacesUtils.bookmarks.mobileGuid,
94  },
95
96  _typeMap: new Map([
97    [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER, PlacesUtils.bookmarks.TYPE_FOLDER],
98    [
99      PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
100      PlacesUtils.bookmarks.TYPE_SEPARATOR,
101    ],
102    [PlacesUtils.TYPE_X_MOZ_PLACE, PlacesUtils.bookmarks.TYPE_BOOKMARK],
103  ]),
104
105  toString() {
106    var that = this;
107    var props = ["uri", "title", "location", "folder"];
108    var string =
109      (this.props.type ? this.props.type + " " : "") +
110      "(" +
111      (function() {
112        var ret = [];
113        for (var i in props) {
114          if (that.props[props[i]]) {
115            ret.push(props[i] + ": " + that.props[props[i]]);
116          }
117        }
118        return ret;
119      })().join(", ") +
120      ")";
121    return string;
122  },
123
124  /**
125   * GetPlacesChildGuid
126   *
127   * Finds the guid of the an item with the specified properties in the places
128   * database under the specified parent.
129   *
130   * @param folder The guid of the folder to search
131   * @param type The type of the item to find, or null to match any item;
132   *        this is one of the PlacesUtils.bookmarks.TYPE_* values
133   * @param title The title of the item to find, or null to match any title
134   * @param uri The uri of the item to find, or null to match any uri
135   *
136   * @return the node id if the item was found, otherwise null
137   */
138  async GetPlacesChildGuid(folder, type, title, uri) {
139    let children = (await PlacesUtils.promiseBookmarksTree(folder)).children;
140    if (!children) {
141      return null;
142    }
143    let guid = null;
144    for (let node of children) {
145      if (node.title == title) {
146        let nodeType = this._typeMap.get(node.type);
147        if (type == null || type == undefined || nodeType == type) {
148          if (uri == undefined || uri == null || node.uri.spec == uri.spec) {
149            // Note that this is suspect as we return the *last* matching
150            // child, which some tests rely on (ie, an early-return here causes
151            // at least 1 test to fail). But that's a yak for another day.
152            guid = node.guid;
153          }
154        }
155      }
156    }
157    return guid;
158  },
159
160  /**
161   * IsAdjacentTo
162   *
163   * Determines if this object is immediately adjacent to another.
164   *
165   * @param itemName The name of the other object; this may be any kind of
166   *        places item
167   * @param relativePos The relative position of the other object.  If -1,
168   *        it means the other object should precede this one, if +1,
169   *        the other object should come after this one
170   * @return true if this object is immediately adjacent to the other object,
171   *         otherwise false
172   */
173  async IsAdjacentTo(itemName, relativePos) {
174    Logger.AssertTrue(
175      this.props.folder_id != -1 && this.props.guid != null,
176      "Either folder_id or guid was invalid"
177    );
178    let otherGuid = await this.GetPlacesChildGuid(
179      this.props.parentGuid,
180      null,
181      itemName
182    );
183    Logger.AssertTrue(otherGuid, "item " + itemName + " not found");
184    let other_pos = (await PlacesUtils.bookmarks.fetch(otherGuid)).index;
185    let this_pos = (await PlacesUtils.bookmarks.fetch(this.props.guid)).index;
186    if (other_pos + relativePos != this_pos) {
187      Logger.logPotentialError(
188        "Invalid position - " +
189          (this.props.title ? this.props.title : this.props.folder) +
190          " not " +
191          (relativePos == 1 ? "after " : "before ") +
192          itemName +
193          " for " +
194          this.toString()
195      );
196      return false;
197    }
198    return true;
199  },
200
201  /**
202   * GetItemIndex
203   *
204   * Gets the item index for this places item.
205   *
206   * @return the item index, or -1 if there's an error
207   */
208  async GetItemIndex() {
209    if (this.props.guid == null) {
210      return -1;
211    }
212    return (await PlacesUtils.bookmarks.fetch(this.props.guid)).index;
213  },
214
215  /**
216   * GetFolder
217   *
218   * Gets the folder guid for the specified bookmark folder
219   *
220   * @param location The full path of the folder, which must begin
221   *        with one of the bookmark root folders
222   * @return the folder guid if the folder is found, otherwise null
223   */
224  async GetFolder(location) {
225    let folder_parts = location.split("/");
226    if (!(folder_parts[0] in this._bookmarkFolders)) {
227      return null;
228    }
229    let folderGuid = this._bookmarkFolders[folder_parts[0]];
230    for (let i = 1; i < folder_parts.length; i++) {
231      let guid = await this.GetPlacesChildGuid(
232        folderGuid,
233        PlacesUtils.bookmarks.TYPE_FOLDER,
234        folder_parts[i]
235      );
236      if (guid == null) {
237        return null;
238      }
239      folderGuid = guid;
240    }
241    return folderGuid;
242  },
243
244  /**
245   * CreateFolder
246   *
247   * Creates a bookmark folder.
248   *
249   * @param location The full path of the folder, which must begin
250   *        with one of the bookmark root folders
251   * @return the folder id if the folder was created, otherwise -1
252   */
253  async CreateFolder(location) {
254    let folder_parts = location.split("/");
255    if (!(folder_parts[0] in this._bookmarkFolders)) {
256      return -1;
257    }
258    let folderGuid = this._bookmarkFolders[folder_parts[0]];
259    for (let i = 1; i < folder_parts.length; i++) {
260      let subfolderGuid = await this.GetPlacesChildGuid(
261        folderGuid,
262        PlacesUtils.bookmarks.TYPE_FOLDER,
263        folder_parts[i]
264      );
265      if (subfolderGuid == null) {
266        let { guid } = await PlacesUtils.bookmarks.insert({
267          parentGuid: folderGuid,
268          name: folder_parts[i],
269          type: PlacesUtils.bookmarks.TYPE_FOLDER,
270        });
271        folderGuid = guid;
272      } else {
273        folderGuid = subfolderGuid;
274      }
275    }
276    return folderGuid;
277  },
278
279  /**
280   * GetOrCreateFolder
281   *
282   * Locates the specified folder; if not found it is created.
283   *
284   * @param location The full path of the folder, which must begin
285   *        with one of the bookmark root folders
286   * @return the folder id if the folder was found or created, otherwise -1
287   */
288  async GetOrCreateFolder(location) {
289    let parentGuid = await this.GetFolder(location);
290    if (parentGuid == null) {
291      parentGuid = await this.CreateFolder(location);
292    }
293    return parentGuid;
294  },
295
296  /**
297   * CheckPosition
298   *
299   * Verifies the position of this places item.
300   *
301   * @param before The name of the places item that this item should be
302            before, or null if this check should be skipped
303   * @param after The name of the places item that this item should be
304            after, or null if this check should be skipped
305   * @param last_item_pos The index of the places item above this one,
306   *        or null if this check should be skipped
307   * @return true if this item is in the correct position, otherwise false
308   */
309  async CheckPosition(before, after, last_item_pos) {
310    if (after) {
311      if (!(await this.IsAdjacentTo(after, 1))) {
312        return false;
313      }
314    }
315    if (before) {
316      if (!(await this.IsAdjacentTo(before, -1))) {
317        return false;
318      }
319    }
320    if (last_item_pos != null && last_item_pos > -1) {
321      let index = await this.GetItemIndex();
322      if (index != last_item_pos + 1) {
323        Logger.logPotentialError(
324          "Item not found at the expected index, got " +
325            index +
326            ", expected " +
327            (last_item_pos + 1) +
328            " for " +
329            this.toString()
330        );
331        return false;
332      }
333    }
334    return true;
335  },
336
337  /**
338   * SetLocation
339   *
340   * Moves this places item to a different folder.
341   *
342   * @param location The full path of the folder to which to move this
343   *        places item, which must begin with one of the bookmark root
344   *        folders; if null, no changes are made
345   * @return nothing if successful, otherwise an exception is thrown
346   */
347  async SetLocation(location) {
348    if (location != null) {
349      let newfolderGuid = await this.GetOrCreateFolder(location);
350      Logger.AssertTrue(
351        newfolderGuid,
352        "Location " + location + " doesn't exist; can't change item's location"
353      );
354      await PlacesUtils.bookmarks.update({
355        guid: this.props.guid,
356        parentGuid: newfolderGuid,
357        index: PlacesUtils.bookmarks.DEFAULT_INDEX,
358      });
359      this.props.parentGuid = newfolderGuid;
360    }
361  },
362
363  /**
364   * SetPosition
365   *
366   * Updates the position of this places item within this item's current
367   * folder.  Use SetLocation to change folders.
368   *
369   * @param position The new index this item should be moved to; if null,
370   *        no changes are made; if -1, this item is moved to the bottom of
371   *        the current folder. Otherwise, must be a string which is the
372   *        title of an existing item in the folder, who's current position
373   *        is used as the index.
374   * @return nothing if successful, otherwise an exception is thrown
375   */
376  async SetPosition(position) {
377    if (position == null) {
378      return;
379    }
380    let index = -1;
381    if (position != -1) {
382      let existingGuid = await this.GetPlacesChildGuid(
383        this.props.parentGuid,
384        null,
385        position
386      );
387      if (existingGuid) {
388        index = (await PlacesUtils.bookmarks.fetch(existingGuid)).index;
389      }
390      Logger.AssertTrue(
391        index != -1,
392        "position " + position + " is invalid; unable to change position"
393      );
394    }
395    await PlacesUtils.bookmarks.update({ guid: this.props.guid, index });
396  },
397
398  /**
399   * Update the title of this places item
400   *
401   * @param title The new title to set for this item; if null, no changes
402   *        are made
403   * @return nothing
404   */
405  async SetTitle(title) {
406    if (title != null) {
407      await PlacesUtils.bookmarks.update({ guid: this.props.guid, title });
408    }
409  },
410};
411
412/**
413 * Bookmark class constructor.  Initializes instance properties.
414 */
415function Bookmark(props) {
416  PlacesItem.call(this, props);
417  if (this.props.title == null) {
418    this.props.title = this.props.uri;
419  }
420  this.props.type = "bookmark";
421}
422
423/**
424 * Bookmark instance methods.
425 */
426Bookmark.prototype = {
427  /**
428   * SetKeyword
429   *
430   * Update this bookmark's keyword.
431   *
432   * @param keyword The keyword to set for this bookmark; if null, no
433   *        changes are made
434   * @return nothing
435   */
436  async SetKeyword(keyword) {
437    if (keyword != null) {
438      // Mirror logic from PlacesSyncUtils's updateBookmarkMetadata
439      let entry = await PlacesUtils.keywords.fetch({ url: this.props.uri });
440      if (entry) {
441        await PlacesUtils.keywords.remove(entry);
442      }
443      await PlacesUtils.keywords.insert({ keyword, url: this.props.uri });
444    }
445  },
446
447  /**
448   * SetUri
449   *
450   * Updates this bookmark's URI.
451   *
452   * @param uri The new URI to set for this boomark; if null, no changes
453   *        are made
454   * @return nothing
455   */
456  async SetUri(uri) {
457    if (uri) {
458      let url = Services.io.newURI(uri);
459      await PlacesUtils.bookmarks.update({ guid: this.props.guid, url });
460    }
461  },
462
463  /**
464   * SetTags
465   *
466   * Updates this bookmark's tags.
467   *
468   * @param tags An array of tags which should be associated with this
469   *        bookmark; any previous tags are removed; if this param is null,
470   *        no changes are made.  If this param is an empty array, all
471   *        tags are removed from this bookmark.
472   * @return nothing
473   */
474  SetTags(tags) {
475    if (tags != null) {
476      let URI = Services.io.newURI(this.props.uri);
477      PlacesUtils.tagging.untagURI(URI, null);
478      if (tags.length > 0) {
479        PlacesUtils.tagging.tagURI(URI, tags);
480      }
481    }
482  },
483
484  /**
485   * Create
486   *
487   * Creates the bookmark described by this object's properties.
488   *
489   * @return the id of the created bookmark
490   */
491  async Create() {
492    this.props.parentGuid = await this.GetOrCreateFolder(this.props.location);
493    Logger.AssertTrue(
494      this.props.parentGuid,
495      "Unable to create " +
496        "bookmark, error creating folder " +
497        this.props.location
498    );
499    let bookmarkURI = Services.io.newURI(this.props.uri);
500    let { guid } = await PlacesUtils.bookmarks.insert({
501      parentGuid: this.props.parentGuid,
502      url: bookmarkURI,
503      title: this.props.title,
504    });
505    this.props.guid = guid;
506    await this.SetKeyword(this.props.keyword);
507    await this.SetTags(this.props.tags);
508    return this.props.guid;
509  },
510
511  /**
512   * Update
513   *
514   * Updates this bookmark's properties according the properties on this
515   * object's 'updateProps' property.
516   *
517   * @return nothing
518   */
519  async Update() {
520    Logger.AssertTrue(this.props.guid, "Invalid guid during Update");
521    await this.SetTitle(this.updateProps.title);
522    await this.SetUri(this.updateProps.uri);
523    await this.SetKeyword(this.updateProps.keyword);
524    await this.SetTags(this.updateProps.tags);
525    await this.SetLocation(this.updateProps.location);
526    await this.SetPosition(this.updateProps.position);
527  },
528
529  /**
530   * Find
531   *
532   * Locates the bookmark which corresponds to this object's properties.
533   *
534   * @return the bookmark guid if the bookmark was found, otherwise null
535   */
536  async Find() {
537    this.props.parentGuid = await this.GetFolder(this.props.location);
538
539    if (this.props.parentGuid == null) {
540      Logger.logError("Unable to find folder " + this.props.location);
541      return null;
542    }
543    let bookmarkTitle = this.props.title;
544    this.props.guid = await this.GetPlacesChildGuid(
545      this.props.parentGuid,
546      null,
547      bookmarkTitle,
548      this.props.uri
549    );
550
551    if (!this.props.guid) {
552      Logger.logPotentialError(this.toString() + " not found");
553      return null;
554    }
555    if (this.props.keyword != null) {
556      let { keyword } = await PlacesSyncUtils.bookmarks.fetch(this.props.guid);
557      if (keyword != this.props.keyword) {
558        Logger.logPotentialError(
559          "Incorrect keyword - expected: " +
560            this.props.keyword +
561            ", actual: " +
562            keyword +
563            " for " +
564            this.toString()
565        );
566        return null;
567      }
568    }
569    if (this.props.tags != null) {
570      try {
571        let URI = Services.io.newURI(this.props.uri);
572        let tags = PlacesUtils.tagging.getTagsForURI(URI);
573        tags.sort();
574        this.props.tags.sort();
575        if (JSON.stringify(tags) != JSON.stringify(this.props.tags)) {
576          Logger.logPotentialError(
577            "Wrong tags - expected: " +
578              JSON.stringify(this.props.tags) +
579              ", actual: " +
580              JSON.stringify(tags) +
581              " for " +
582              this.toString()
583          );
584          return null;
585        }
586      } catch (e) {
587        Logger.logPotentialError("error processing tags " + e);
588        return null;
589      }
590    }
591    if (
592      !(await this.CheckPosition(
593        this.props.before,
594        this.props.after,
595        this.props.last_item_pos
596      ))
597    ) {
598      return null;
599    }
600    return this.props.guid;
601  },
602
603  /**
604   * Remove
605   *
606   * Removes this bookmark.  The bookmark should have been located previously
607   * by a call to Find.
608   *
609   * @return nothing
610   */
611  async Remove() {
612    Logger.AssertTrue(this.props.guid, "Invalid guid during Remove");
613    await PlacesUtils.bookmarks.remove(this.props.guid);
614  },
615};
616
617extend(Bookmark, PlacesItem);
618
619/**
620 * BookmarkFolder class constructor. Initializes instance properties.
621 */
622function BookmarkFolder(props) {
623  PlacesItem.call(this, props);
624  this.props.type = "folder";
625}
626
627/**
628 * BookmarkFolder instance methods
629 */
630BookmarkFolder.prototype = {
631  /**
632   * Create
633   *
634   * Creates the bookmark folder described by this object's properties.
635   *
636   * @return the id of the created bookmark folder
637   */
638  async Create() {
639    this.props.parentGuid = await this.GetOrCreateFolder(this.props.location);
640    Logger.AssertTrue(
641      this.props.parentGuid,
642      "Unable to create " +
643        "folder, error creating parent folder " +
644        this.props.location
645    );
646    let { guid } = await PlacesUtils.bookmarks.insert({
647      parentGuid: this.props.parentGuid,
648      title: this.props.folder,
649      index: PlacesUtils.bookmarks.DEFAULT_INDEX,
650      type: PlacesUtils.bookmarks.TYPE_FOLDER,
651    });
652    this.props.guid = guid;
653    return this.props.parentGuid;
654  },
655
656  /**
657   * Find
658   *
659   * Locates the bookmark folder which corresponds to this object's
660   * properties.
661   *
662   * @return the folder guid if the folder was found, otherwise null
663   */
664  async Find() {
665    this.props.parentGuid = await this.GetFolder(this.props.location);
666    if (this.props.parentGuid == null) {
667      Logger.logError("Unable to find folder " + this.props.location);
668      return null;
669    }
670    this.props.guid = await this.GetPlacesChildGuid(
671      this.props.parentGuid,
672      PlacesUtils.bookmarks.TYPE_FOLDER,
673      this.props.folder
674    );
675    if (this.props.guid == null) {
676      return null;
677    }
678    if (
679      !(await this.CheckPosition(
680        this.props.before,
681        this.props.after,
682        this.props.last_item_pos
683      ))
684    ) {
685      return null;
686    }
687    return this.props.guid;
688  },
689
690  /**
691   * Remove
692   *
693   * Removes this folder.  The folder should have been located previously
694   * by a call to Find.
695   *
696   * @return nothing
697   */
698  async Remove() {
699    Logger.AssertTrue(this.props.guid, "Invalid guid during Remove");
700    await PlacesUtils.bookmarks.remove(this.props.guid);
701  },
702
703  /**
704   * Update
705   *
706   * Updates this bookmark's properties according the properties on this
707   * object's 'updateProps' property.
708   *
709   * @return nothing
710   */
711  async Update() {
712    Logger.AssertTrue(this.props.guid, "Invalid guid during Update");
713    await this.SetLocation(this.updateProps.location);
714    await this.SetPosition(this.updateProps.position);
715    await this.SetTitle(this.updateProps.folder);
716  },
717};
718
719extend(BookmarkFolder, PlacesItem);
720
721/**
722 * Separator class constructor. Initializes instance properties.
723 */
724function Separator(props) {
725  PlacesItem.call(this, props);
726  this.props.type = "separator";
727}
728
729/**
730 * Separator instance methods.
731 */
732Separator.prototype = {
733  /**
734   * Create
735   *
736   * Creates the bookmark separator described by this object's properties.
737   *
738   * @return the id of the created separator
739   */
740  async Create() {
741    this.props.parentGuid = await this.GetOrCreateFolder(this.props.location);
742    Logger.AssertTrue(
743      this.props.parentGuid,
744      "Unable to create " +
745        "folder, error creating parent folder " +
746        this.props.location
747    );
748    let { guid } = await PlacesUtils.bookmarks.insert({
749      parentGuid: this.props.parentGuid,
750      type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
751    });
752    this.props.guid = guid;
753    return guid;
754  },
755
756  /**
757   * Find
758   *
759   * Locates the bookmark separator which corresponds to this object's
760   * properties.
761   *
762   * @return the item guid if the separator was found, otherwise null
763   */
764  async Find() {
765    this.props.parentGuid = await this.GetFolder(this.props.location);
766    if (this.props.parentGuid == null) {
767      Logger.logError("Unable to find folder " + this.props.location);
768      return null;
769    }
770    if (this.props.before == null && this.props.last_item_pos == null) {
771      Logger.logPotentialError(
772        "Separator requires 'before' attribute if it's the" +
773          "first item in the list"
774      );
775      return null;
776    }
777    let expected_pos = -1;
778    if (this.props.before) {
779      let otherGuid = this.GetPlacesChildGuid(
780        this.props.parentGuid,
781        null,
782        this.props.before
783      );
784      if (otherGuid == null) {
785        Logger.logPotentialError(
786          "Can't find places item " +
787            this.props.before +
788            " for locating separator"
789        );
790        return null;
791      }
792      expected_pos = (await PlacesUtils.bookmarks.fetch(otherGuid)).index - 1;
793    } else {
794      expected_pos = this.props.last_item_pos + 1;
795    }
796    // Note these are IDs instead of GUIDs.
797    let children = await PlacesSyncUtils.bookmarks.fetchChildRecordIds(
798      this.props.parentGuid
799    );
800    this.props.guid = children[expected_pos];
801    if (this.props.guid == null) {
802      Logger.logPotentialError(
803        "No separator found at position " + expected_pos
804      );
805      return null;
806    }
807    let info = await PlacesUtils.bookmarks.fetch(this.props.guid);
808    if (info.type != PlacesUtils.bookmarks.TYPE_SEPARATOR) {
809      Logger.logPotentialError(
810        "Places item at position " + expected_pos + " is not a separator"
811      );
812      return null;
813    }
814    return this.props.guid;
815  },
816
817  /**
818   * Update
819   *
820   * Updates this separator's properties according the properties on this
821   * object's 'updateProps' property.
822   *
823   * @return nothing
824   */
825  async Update() {
826    Logger.AssertTrue(this.props.guid, "Invalid guid during Update");
827    await this.SetLocation(this.updateProps.location);
828    await this.SetPosition(this.updateProps.position);
829    return true;
830  },
831
832  /**
833   * Remove
834   *
835   * Removes this separator.  The separator should have been located
836   * previously by a call to Find.
837   *
838   * @return nothing
839   */
840  async Remove() {
841    Logger.AssertTrue(this.props.guid, "Invalid guid during Update");
842    await PlacesUtils.bookmarks.remove(this.props.guid);
843  },
844};
845
846extend(Separator, PlacesItem);
847