dbusMenu: Populate menus in chunked idles
Creating menu items and populating them can be quite expensive in GNOME
shell, since we may create many of them during the extension initialization
we could end up blocking the UI.
To avoid this, we can create multiple idles for each menu addition, one with
each priority that depends on the number of elements already queued for
addition, in this way the items are added in different main loop idle cycles
avoiding to fill our job queue with too many things to do each frame.
Helps with: #295
Marco Trevisan (TreviƱo) authored 1 year, 2 months ago
Marco Trevisan committed 1 year, 2 months ago
843 | 843 | attachToMenu(menu) { |
844 | 844 | this._rootMenu = menu; |
845 | 845 | this._rootItem = this._client.getRoot(); |
846 | this._itemsBeingAdded = new Set(); | |
846 | 847 | |
847 | 848 | // cleanup: remove existing children (just in case) |
848 | 849 | this._rootMenu.removeAll(); |
862 | 863 | this._rootItem.sendAboutToShow(); |
863 | 864 | |
864 | 865 | // fill the menu for the first time |
865 | this._rootItem.getChildren().forEach(child => | |
866 | this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child))); | |
866 | const children = this._rootItem.getChildren(); | |
867 | children.forEach(child => | |
868 | this._onRootChildAdded(this._rootItem, child)); | |
867 | 869 | } |
868 | 870 | |
869 | 871 | _setOpenedSubmenu(submenu) { |
883 | 885 | } |
884 | 886 | |
885 | 887 | _onRootChildAdded(dbusItem, child, position) { |
886 | this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position); | |
888 | // Menu additions can be expensive, so let's do it in different chunks | |
889 | const basePriority = this.isOpen ? GLib.PRIORITY_DEFAULT : GLib.PRIORITY_LOW; | |
890 | const idlePromise = new PromiseUtils.IdlePromise( | |
891 | basePriority + this._itemsBeingAdded.size, this.cancellable); | |
892 | this._itemsBeingAdded.add(child); | |
893 | ||
894 | idlePromise.then(() => { | |
895 | if (!this._itemsBeingAdded.has(child)) | |
896 | return; | |
897 | ||
898 | this._rootMenu.addMenuItem( | |
899 | MenuItemFactory.createItem(this, child), position); | |
900 | }).catch(e => { | |
901 | if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) | |
902 | logError(e); | |
903 | }).finally(() => this._itemsBeingAdded.delete(child)); | |
887 | 904 | } |
888 | 905 | |
889 | 906 | _onRootChildRemoved(dbusItem, child) { |
890 | 907 | // children like to play hide and seek |
891 | 908 | // but we know how to find it for sure! |
892 | this._rootMenu._getMenuItems().forEach(item => { | |
893 | if (item._dbusItem === child) | |
894 | item.destroy(); | |
895 | }); | |
909 | const item = this._rootMenu._getMenuItems().find(it => | |
910 | it._dbusItem === child); | |
911 | ||
912 | if (item) | |
913 | item.destroy(); | |
914 | else | |
915 | this._itemsBeingAdded.delete(child); | |
916 | ||
896 | 917 | } |
897 | 918 | |
898 | 919 | _onRootChildMoved(dbusItem, child, oldpos, newpos) { |
926 | 947 | this._rootItem = null; |
927 | 948 | this._rootMenu = null; |
928 | 949 | this.indicator = null; |
950 | this._itemsBeingAdded = null; | |
929 | 951 | } |
930 | 952 | }; |
931 | 953 | Signals.addSignalMethods(Client.prototype); |