bookmarkProperties.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. /**
  6. * The panel is initialized based on data given in the js object passed
  7. * as window.arguments[0]. The object must have the following fields set:
  8. * @ action (String). Possible values:
  9. * - "add" - for adding a new item.
  10. * @ type (String). Possible values:
  11. * - "bookmark"
  12. * @ loadBookmarkInSidebar - optional, the default state for the
  13. * "Load this bookmark in the sidebar" field.
  14. * - "folder"
  15. * @ URIList (Array of nsIURI objects) - optional, list of uris to
  16. * be bookmarked under the new folder.
  17. * - "livemark"
  18. * @ uri (nsIURI object) - optional, the default uri for the new item.
  19. * The property is not used for the "folder with items" type.
  20. * @ title (String) - optional, the default title for the new item.
  21. * @ description (String) - optional, the default description for the new
  22. * item.
  23. * @ defaultInsertionPoint (InsertionPoint JS object) - optional, the
  24. * default insertion point for the new item.
  25. * @ keyword (String) - optional, the default keyword for the new item.
  26. * @ postData (String) - optional, POST data to accompany the keyword.
  27. * @ charSet (String) - optional, character-set to accompany the keyword.
  28. * Notes:
  29. * 1) If |uri| is set for a bookmark/livemark item and |title| isn't,
  30. * the dialog will query the history tables for the title associated
  31. * with the given uri. If the dialog is set to adding a folder with
  32. * bookmark items under it (see URIList), a default static title is
  33. * used ("[Folder Name]").
  34. * 2) The index field of the default insertion point is ignored if
  35. * the folder picker is shown.
  36. * - "edit" - for editing a bookmark item or a folder.
  37. * @ type (String). Possible values:
  38. * - "bookmark"
  39. * @ itemId (Integer) - the id of the bookmark item.
  40. * - "folder" (also applies to livemarks)
  41. * @ itemId (Integer) - the id of the folder.
  42. * @ hiddenRows (Strings array) - optional, list of rows to be hidden
  43. * regardless of the item edited or added by the dialog.
  44. * Possible values:
  45. * - "title"
  46. * - "location"
  47. * - "description"
  48. * - "keyword"
  49. * - "tags"
  50. * - "loadInSidebar"
  51. * - "feedLocation"
  52. * - "siteLocation"
  53. * - "folderPicker" - hides both the tree and the menu.
  54. * @ readOnly (Boolean) - optional, states if the panel should be read-only
  55. *
  56. * window.arguments[0].performed is set to true if any transaction has
  57. * been performed by the dialog.
  58. */
  59. Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
  60. XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
  61. "resource://gre/modules/PrivateBrowsingUtils.jsm");
  62. XPCOMUtils.defineLazyModuleGetter(this, "Task",
  63. "resource://gre/modules/Task.jsm");
  64. const BOOKMARK_ITEM = 0;
  65. const BOOKMARK_FOLDER = 1;
  66. const LIVEMARK_CONTAINER = 2;
  67. const ACTION_EDIT = 0;
  68. const ACTION_ADD = 1;
  69. var elementsHeight = new Map();
  70. var BookmarkPropertiesPanel = {
  71. /** UI Text Strings */
  72. __strings: null,
  73. get _strings() {
  74. if (!this.__strings) {
  75. this.__strings = document.getElementById("stringBundle");
  76. }
  77. return this.__strings;
  78. },
  79. _action: null,
  80. _itemType: null,
  81. _itemId: -1,
  82. _uri: null,
  83. _loadInSidebar: false,
  84. _title: "",
  85. _description: "",
  86. _URIs: [],
  87. _keyword: "",
  88. _postData: null,
  89. _charSet: "",
  90. _feedURI: null,
  91. _siteURI: null,
  92. _defaultInsertionPoint: null,
  93. _hiddenRows: [],
  94. _batching: false,
  95. _readOnly: false,
  96. /**
  97. * This method returns the correct label for the dialog's "accept"
  98. * button based on the variant of the dialog.
  99. */
  100. _getAcceptLabel: function() {
  101. if (this._action == ACTION_ADD) {
  102. if (this._URIs.length)
  103. return this._strings.getString("dialogAcceptLabelAddMulti");
  104. if (this._itemType == LIVEMARK_CONTAINER)
  105. return this._strings.getString("dialogAcceptLabelAddLivemark");
  106. if (this._dummyItem || this._loadInSidebar)
  107. return this._strings.getString("dialogAcceptLabelAddItem");
  108. return this._strings.getString("dialogAcceptLabelSaveItem");
  109. }
  110. return this._strings.getString("dialogAcceptLabelEdit");
  111. },
  112. /**
  113. * This method returns the correct title for the current variant
  114. * of this dialog.
  115. */
  116. _getDialogTitle: function() {
  117. if (this._action == ACTION_ADD) {
  118. if (this._itemType == BOOKMARK_ITEM)
  119. return this._strings.getString("dialogTitleAddBookmark");
  120. if (this._itemType == LIVEMARK_CONTAINER)
  121. return this._strings.getString("dialogTitleAddLivemark");
  122. // add folder
  123. NS_ASSERT(this._itemType == BOOKMARK_FOLDER, "Unknown item type");
  124. if (this._URIs.length)
  125. return this._strings.getString("dialogTitleAddMulti");
  126. return this._strings.getString("dialogTitleAddFolder");
  127. }
  128. if (this._action == ACTION_EDIT) {
  129. return this._strings.getFormattedString("dialogTitleEdit", [this._title]);
  130. }
  131. return "";
  132. },
  133. /**
  134. * Determines the initial data for the item edited or added by this dialog
  135. */
  136. _determineItemInfo: function() {
  137. var dialogInfo = window.arguments[0];
  138. this._action = dialogInfo.action == "add" ? ACTION_ADD : ACTION_EDIT;
  139. this._hiddenRows = dialogInfo.hiddenRows ? dialogInfo.hiddenRows : [];
  140. if (this._action == ACTION_ADD) {
  141. NS_ASSERT("type" in dialogInfo, "missing type property for add action");
  142. if ("title" in dialogInfo)
  143. this._title = dialogInfo.title;
  144. if ("defaultInsertionPoint" in dialogInfo) {
  145. this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint;
  146. }
  147. else
  148. this._defaultInsertionPoint =
  149. new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
  150. PlacesUtils.bookmarks.DEFAULT_INDEX,
  151. Ci.nsITreeView.DROP_ON);
  152. switch (dialogInfo.type) {
  153. case "bookmark":
  154. this._itemType = BOOKMARK_ITEM;
  155. if ("uri" in dialogInfo) {
  156. NS_ASSERT(dialogInfo.uri instanceof Ci.nsIURI,
  157. "uri property should be a uri object");
  158. this._uri = dialogInfo.uri;
  159. if (typeof(this._title) != "string") {
  160. this._title = this._getURITitleFromHistory(this._uri) ||
  161. this._uri.spec;
  162. }
  163. }
  164. else {
  165. this._uri = PlacesUtils._uri("about:blank");
  166. this._title = this._strings.getString("newBookmarkDefault");
  167. this._dummyItem = true;
  168. }
  169. if ("loadBookmarkInSidebar" in dialogInfo)
  170. this._loadInSidebar = dialogInfo.loadBookmarkInSidebar;
  171. if ("keyword" in dialogInfo) {
  172. this._keyword = dialogInfo.keyword;
  173. this._isAddKeywordDialog = true;
  174. if ("postData" in dialogInfo)
  175. this._postData = dialogInfo.postData;
  176. if ("charSet" in dialogInfo)
  177. this._charSet = dialogInfo.charSet;
  178. }
  179. break;
  180. case "folder":
  181. this._itemType = BOOKMARK_FOLDER;
  182. if (!this._title) {
  183. if ("URIList" in dialogInfo) {
  184. this._title = this._strings.getString("bookmarkAllTabsDefault");
  185. this._URIs = dialogInfo.URIList;
  186. }
  187. else
  188. this._title = this._strings.getString("newFolderDefault");
  189. this._dummyItem = true;
  190. }
  191. break;
  192. case "livemark":
  193. this._itemType = LIVEMARK_CONTAINER;
  194. if ("feedURI" in dialogInfo)
  195. this._feedURI = dialogInfo.feedURI;
  196. if ("siteURI" in dialogInfo)
  197. this._siteURI = dialogInfo.siteURI;
  198. if (!this._title) {
  199. if (this._feedURI) {
  200. this._title = this._getURITitleFromHistory(this._feedURI) ||
  201. this._feedURI.spec;
  202. }
  203. else
  204. this._title = this._strings.getString("newLivemarkDefault");
  205. }
  206. }
  207. if ("description" in dialogInfo)
  208. this._description = dialogInfo.description;
  209. }
  210. else { // edit
  211. NS_ASSERT("itemId" in dialogInfo);
  212. this._itemId = dialogInfo.itemId;
  213. this._title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
  214. this._readOnly = !!dialogInfo.readOnly;
  215. switch (dialogInfo.type) {
  216. case "bookmark":
  217. this._itemType = BOOKMARK_ITEM;
  218. this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
  219. // keyword
  220. this._keyword = PlacesUtils.bookmarks
  221. .getKeywordForBookmark(this._itemId);
  222. // Load In Sidebar
  223. this._loadInSidebar = PlacesUtils.annotations
  224. .itemHasAnnotation(this._itemId,
  225. PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
  226. break;
  227. case "folder":
  228. this._itemType = BOOKMARK_FOLDER;
  229. PlacesUtils.livemarks.getLivemark({ id: this._itemId })
  230. .then(aLivemark => {
  231. this._itemType = LIVEMARK_CONTAINER;
  232. this._feedURI = aLivemark.feedURI;
  233. this._siteURI = aLivemark.siteURI;
  234. this._fillEditProperties();
  235. let acceptButton = document.documentElement.getButton("accept");
  236. acceptButton.disabled = !this._inputIsValid();
  237. let newHeight = window.outerHeight +
  238. this._element("descriptionField").boxObject.height;
  239. window.resizeTo(window.outerWidth, newHeight);
  240. }, () => undefined);
  241. break;
  242. }
  243. // Description
  244. if (PlacesUtils.annotations
  245. .itemHasAnnotation(this._itemId, PlacesUIUtils.DESCRIPTION_ANNO)) {
  246. this._description = PlacesUtils.annotations
  247. .getItemAnnotation(this._itemId,
  248. PlacesUIUtils.DESCRIPTION_ANNO);
  249. }
  250. }
  251. },
  252. /**
  253. * This method returns the title string corresponding to a given URI.
  254. * If none is available from the bookmark service (probably because
  255. * the given URI doesn't appear in bookmarks or history), we synthesize
  256. * a title from the first 100 characters of the URI.
  257. *
  258. * @param aURI
  259. * nsIURI object for which we want the title
  260. *
  261. * @returns a title string
  262. */
  263. _getURITitleFromHistory: function(aURI) {
  264. NS_ASSERT(aURI instanceof Ci.nsIURI);
  265. // get the title from History
  266. return PlacesUtils.history.getPageTitle(aURI);
  267. },
  268. /**
  269. * This method should be called by the onload of the Bookmark Properties
  270. * dialog to initialize the state of the panel.
  271. */
  272. onDialogLoad: Task.async(function* () {
  273. this._determineItemInfo();
  274. document.title = this._getDialogTitle();
  275. var acceptButton = document.documentElement.getButton("accept");
  276. acceptButton.label = this._getAcceptLabel();
  277. // Do not use sizeToContent, otherwise, due to bug 90276, the dialog will
  278. // grow at every opening.
  279. // Since elements can be uncollapsed asynchronously, we must observe their
  280. // mutations and resize the dialog using a cached element size.
  281. this._height = window.outerHeight;
  282. this._mutationObserver = new MutationObserver(mutations => {
  283. for (let mutation of mutations) {
  284. let target = mutation.target;
  285. let id = target.id;
  286. if (!/^editBMPanel_.*(Row|Checkbox)$/.test(id))
  287. continue;
  288. let collapsed = target.getAttribute("collapsed") === "true";
  289. let wasCollapsed = mutation.oldValue === "true";
  290. if (collapsed == wasCollapsed)
  291. continue;
  292. if (collapsed) {
  293. this._height -= elementsHeight.get(id);
  294. elementsHeight.delete(id);
  295. } else {
  296. elementsHeight.set(id, target.boxObject.height);
  297. this._height += elementsHeight.get(id);
  298. }
  299. window.resizeTo(window.outerWidth, this._height);
  300. }
  301. });
  302. this._mutationObserver.observe(document,
  303. { subtree: true,
  304. attributeOldValue: true,
  305. attributeFilter: ["collapsed"] });
  306. // Some controls are flexible and we want to update their cached size when
  307. // the dialog is resized.
  308. window.addEventListener("resize", this);
  309. this._beginBatch();
  310. switch (this._action) {
  311. case ACTION_EDIT:
  312. this._fillEditProperties();
  313. acceptButton.disabled = this._readOnly;
  314. break;
  315. case ACTION_ADD:
  316. yield this._fillAddProperties();
  317. // if this is an uri related dialog disable accept button until
  318. // the user fills an uri value.
  319. if (this._itemType == BOOKMARK_ITEM)
  320. acceptButton.disabled = !this._inputIsValid();
  321. break;
  322. }
  323. if (!this._readOnly) {
  324. // Listen on uri fields to enable accept button if input is valid
  325. if (this._itemType == BOOKMARK_ITEM) {
  326. this._element("locationField")
  327. .addEventListener("input", this, false);
  328. if (this._isAddKeywordDialog) {
  329. this._element("keywordField")
  330. .addEventListener("input", this, false);
  331. }
  332. }
  333. else if (this._itemType == LIVEMARK_CONTAINER) {
  334. this._element("feedLocationField")
  335. .addEventListener("input", this, false);
  336. this._element("siteLocationField")
  337. .addEventListener("input", this, false);
  338. }
  339. }
  340. // Ensure the Name Picker textbox is focused on load
  341. var namePickerElem = document.getElementById('editBMPanel_namePicker');
  342. namePickerElem.focus();
  343. namePickerElem.select();
  344. }),
  345. // nsIDOMEventListener
  346. handleEvent: function(aEvent) {
  347. var target = aEvent.target;
  348. switch (aEvent.type) {
  349. case "input":
  350. if (target.id == "editBMPanel_locationField" ||
  351. target.id == "editBMPanel_feedLocationField" ||
  352. target.id == "editBMPanel_siteLocationField" ||
  353. target.id == "editBMPanel_keywordField") {
  354. // Check uri fields to enable accept button if input is valid
  355. document.documentElement
  356. .getButton("accept").disabled = !this._inputIsValid();
  357. }
  358. break;
  359. case "resize":
  360. for (let [id, oldHeight] of elementsHeight) {
  361. let newHeight = document.getElementById(id).boxObject.height;
  362. this._height += - oldHeight + newHeight;
  363. elementsHeight.set(id, newHeight);
  364. }
  365. break;
  366. }
  367. },
  368. _beginBatch: function() {
  369. if (this._batching)
  370. return;
  371. PlacesUtils.transactionManager.beginBatch(null);
  372. this._batching = true;
  373. },
  374. _endBatch: function() {
  375. if (!this._batching)
  376. return;
  377. PlacesUtils.transactionManager.endBatch(false);
  378. this._batching = false;
  379. },
  380. _fillEditProperties: function() {
  381. gEditItemOverlay.initPanel(this._itemId,
  382. { hiddenRows: this._hiddenRows,
  383. forceReadOnly: this._readOnly });
  384. },
  385. _fillAddProperties: Task.async(function* () {
  386. yield this._createNewItem();
  387. // Edit the new item
  388. gEditItemOverlay.initPanel(this._itemId,
  389. { hiddenRows: this._hiddenRows });
  390. // Empty location field if the uri is about:blank, this way inserting a new
  391. // url will be easier for the user, Accept button will be automatically
  392. // disabled by the input listener until the user fills the field.
  393. var locationField = this._element("locationField");
  394. if (locationField.value == "about:blank")
  395. locationField.value = "";
  396. }),
  397. // nsISupports
  398. QueryInterface: function(aIID) {
  399. if (aIID.equals(Ci.nsIDOMEventListener) ||
  400. aIID.equals(Ci.nsISupports))
  401. return this;
  402. throw Cr.NS_NOINTERFACE;
  403. },
  404. _element: function(aID) {
  405. return document.getElementById("editBMPanel_" + aID);
  406. },
  407. onDialogUnload: function() {
  408. // gEditItemOverlay does not exist anymore here, so don't rely on it.
  409. this._mutationObserver.disconnect();
  410. delete this._mutationObserver;
  411. window.removeEventListener("resize", this);
  412. // Calling removeEventListener with arguments which do not identify any
  413. // currently registered EventListener on the EventTarget has no effect.
  414. this._element("locationField")
  415. .removeEventListener("input", this, false);
  416. this._element("feedLocationField")
  417. .removeEventListener("input", this, false);
  418. this._element("siteLocationField")
  419. .removeEventListener("input", this, false);
  420. },
  421. onDialogAccept: function() {
  422. // We must blur current focused element to save its changes correctly
  423. document.commandDispatcher.focusedElement.blur();
  424. // The order here is important! We have to uninit the panel first, otherwise
  425. // late changes could force it to commit more transactions.
  426. gEditItemOverlay.uninitPanel(true);
  427. this._endBatch();
  428. window.arguments[0].performed = true;
  429. },
  430. onDialogCancel: function() {
  431. // The order here is important! We have to uninit the panel first, otherwise
  432. // changes done as part of Undo may change the panel contents and by
  433. // that force it to commit more transactions.
  434. gEditItemOverlay.uninitPanel(true);
  435. this._endBatch();
  436. PlacesUtils.transactionManager.undoTransaction();
  437. window.arguments[0].performed = false;
  438. },
  439. /**
  440. * This method checks to see if the input fields are in a valid state.
  441. *
  442. * @returns true if the input is valid, false otherwise
  443. */
  444. _inputIsValid: function() {
  445. if (this._itemType == BOOKMARK_ITEM &&
  446. !this._containsValidURI("locationField"))
  447. return false;
  448. if (this._isAddKeywordDialog && !this._element("keywordField").value.length)
  449. return false;
  450. return true;
  451. },
  452. /**
  453. * Determines whether the XUL textbox with the given ID contains a
  454. * string that can be converted into an nsIURI.
  455. *
  456. * @param aTextboxID
  457. * the ID of the textbox element whose contents we'll test
  458. *
  459. * @returns true if the textbox contains a valid URI string, false otherwise
  460. */
  461. _containsValidURI: function(aTextboxID) {
  462. try {
  463. var value = this._element(aTextboxID).value;
  464. if (value) {
  465. PlacesUIUtils.createFixedURI(value);
  466. return true;
  467. }
  468. } catch (e) { }
  469. return false;
  470. },
  471. /**
  472. * [New Item Mode] Get the insertion point details for the new item, given
  473. * dialog state and opening arguments.
  474. *
  475. * The container-identifier and insertion-index are returned separately in
  476. * the form of [containerIdentifier, insertionIndex]
  477. */
  478. _getInsertionPointDetails: function() {
  479. var containerId = this._defaultInsertionPoint.itemId;
  480. var indexInContainer = this._defaultInsertionPoint.index;
  481. return [containerId, indexInContainer];
  482. },
  483. /**
  484. * Returns a transaction for creating a new bookmark item representing the
  485. * various fields and opening arguments of the dialog.
  486. */
  487. _getCreateNewBookmarkTransaction:
  488. function(aContainer, aIndex) {
  489. var annotations = [];
  490. var childTransactions = [];
  491. if (this._description) {
  492. let annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
  493. type : Ci.nsIAnnotationService.TYPE_STRING,
  494. flags : 0,
  495. value : this._description,
  496. expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
  497. let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
  498. childTransactions.push(editItemTxn);
  499. }
  500. if (this._loadInSidebar) {
  501. let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
  502. value : true };
  503. let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
  504. childTransactions.push(setLoadTxn);
  505. }
  506. if (this._postData) {
  507. let postDataTxn = new PlacesEditBookmarkPostDataTransaction(-1, this._postData);
  508. childTransactions.push(postDataTxn);
  509. }
  510. //XXX TODO: this should be in a transaction!
  511. if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window))
  512. PlacesUtils.setCharsetForURI(this._uri, this._charSet);
  513. let createTxn = new PlacesCreateBookmarkTransaction(this._uri,
  514. aContainer,
  515. aIndex,
  516. this._title,
  517. this._keyword,
  518. annotations,
  519. childTransactions);
  520. return new PlacesAggregatedTransaction(this._getDialogTitle(),
  521. [createTxn]);
  522. },
  523. /**
  524. * Returns a childItems-transactions array representing the URIList with
  525. * which the dialog has been opened.
  526. */
  527. _getTransactionsForURIList: function() {
  528. var transactions = [];
  529. for (var i = 0; i < this._URIs.length; ++i) {
  530. var uri = this._URIs[i];
  531. var title = this._getURITitleFromHistory(uri);
  532. var createTxn = new PlacesCreateBookmarkTransaction(uri, -1,
  533. PlacesUtils.bookmarks.DEFAULT_INDEX,
  534. title);
  535. transactions.push(createTxn);
  536. }
  537. return transactions;
  538. },
  539. /**
  540. * Returns a transaction for creating a new folder item representing the
  541. * various fields and opening arguments of the dialog.
  542. */
  543. _getCreateNewFolderTransaction:
  544. function(aContainer, aIndex) {
  545. var annotations = [];
  546. var childItemsTransactions;
  547. if (this._URIs.length)
  548. childItemsTransactions = this._getTransactionsForURIList();
  549. if (this._description)
  550. annotations.push(this._getDescriptionAnnotation(this._description));
  551. return new PlacesCreateFolderTransaction(this._title, aContainer,
  552. aIndex, annotations,
  553. childItemsTransactions);
  554. },
  555. /**
  556. * Returns a transaction for creating a new live-bookmark item representing
  557. * the various fields and opening arguments of the dialog.
  558. */
  559. _getCreateNewLivemarkTransaction:
  560. function(aContainer, aIndex) {
  561. return new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
  562. this._title,
  563. aContainer, aIndex);
  564. },
  565. /**
  566. * Dialog-accept code-path for creating a new item (any type)
  567. */
  568. _createNewItem: Task.async(function* () {
  569. var [container, index] = this._getInsertionPointDetails();
  570. var txn;
  571. switch (this._itemType) {
  572. case BOOKMARK_FOLDER:
  573. txn = this._getCreateNewFolderTransaction(container, index);
  574. break;
  575. case LIVEMARK_CONTAINER:
  576. txn = this._getCreateNewLivemarkTransaction(container, index);
  577. break;
  578. default: // BOOKMARK_ITEM
  579. txn = this._getCreateNewBookmarkTransaction(container, index);
  580. }
  581. PlacesUtils.transactionManager.doTransaction(txn);
  582. // This is a temporary hack until we use PlacesTransactions.jsm
  583. if (txn._promise) {
  584. yield txn._promise;
  585. }
  586. let folderGuid = yield PlacesUtils.promiseItemGuid(container);
  587. let bm = yield PlacesUtils.bookmarks.fetch({
  588. parentGuid: folderGuid,
  589. index: index
  590. });
  591. this._itemId = yield PlacesUtils.promiseItemId(bm.guid);
  592. })
  593. };