UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

923 lines (818 loc) 23.4 kB
// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @interface */ UI.View = function() {}; UI.View.prototype = { /** * @return {string} */ viewId() {}, /** * @return {string} */ title() {}, /** * @return {boolean} */ isCloseable() {}, /** * @return {boolean} */ isTransient() {}, /** * @return {!Promise<!Array<!UI.ToolbarItem>>} */ toolbarItems() {}, /** * @return {!Promise<!UI.Widget>} */ widget() {}, disposeView() {} }; UI.View._symbol = Symbol('view'); UI.View._widgetSymbol = Symbol('widget'); /** * @implements {UI.View} * @unrestricted */ UI.SimpleView = class extends UI.VBox { /** * @param {string} title * @param {boolean=} isWebComponent */ constructor(title, isWebComponent) { super(isWebComponent); this._title = title; /** @type {!Array<!UI.ToolbarItem>} */ this._toolbarItems = []; this[UI.View._symbol] = this; } /** * @override * @return {string} */ viewId() { return this._title; } /** * @override * @return {string} */ title() { return this._title; } /** * @override * @return {boolean} */ isCloseable() { return false; } /** * @override * @return {boolean} */ isTransient() { return false; } /** * @override * @return {!Promise<!Array<!UI.ToolbarItem>>} */ toolbarItems() { return Promise.resolve(this.syncToolbarItems()); } /** * @return {!Array<!UI.ToolbarItem>} */ syncToolbarItems() { return this._toolbarItems; } /** * @override * @return {!Promise<!UI.Widget>} */ widget() { return /** @type {!Promise<!UI.Widget>} */ (Promise.resolve(this)); } /** * @param {!UI.ToolbarItem} item */ addToolbarItem(item) { this._toolbarItems.push(item); } /** * @return {!Promise} */ revealView() { return UI.viewManager.revealView(this); } /** * @override */ disposeView() { } }; /** * @implements {UI.View} * @unrestricted */ UI.ProvidedView = class { /** * @param {!Runtime.Extension} extension */ constructor(extension) { this._extension = extension; } /** * @override * @return {string} */ viewId() { return this._extension.descriptor()['id']; } /** * @override * @return {string} */ title() { return this._extension.title(); } /** * @override * @return {boolean} */ isCloseable() { return this._extension.descriptor()['persistence'] === 'closeable'; } /** * @override * @return {boolean} */ isTransient() { return this._extension.descriptor()['persistence'] === 'transient'; } /** * @override * @return {!Promise<!Array<!UI.ToolbarItem>>} */ toolbarItems() { const actionIds = this._extension.descriptor()['actionIds']; if (actionIds) { const result = actionIds.split(',').map(id => UI.Toolbar.createActionButtonForId(id.trim())); return Promise.resolve(result); } if (this._extension.descriptor()['hasToolbar']) return this.widget().then(widget => /** @type {!UI.ToolbarItem.ItemsProvider} */ (widget).toolbarItems()); return Promise.resolve([]); } /** * @override * @return {!Promise<!UI.Widget>} */ async widget() { this._widgetRequested = true; const widget = await this._extension.instance(); if (!(widget instanceof UI.Widget)) throw new Error('view className should point to a UI.Widget'); widget[UI.View._symbol] = this; return /** @type {!UI.Widget} */ (widget); } /** * @override */ async disposeView() { if (!this._widgetRequested) return; const widget = await this.widget(); widget.ownerViewDisposed(); } }; /** * @interface */ UI.ViewLocation = function() {}; UI.ViewLocation.prototype = { /** * @param {string} locationName */ appendApplicableItems(locationName) {}, /** * @param {!UI.View} view * @param {?UI.View=} insertBefore */ appendView(view, insertBefore) {}, /** * @param {!UI.View} view * @param {?UI.View=} insertBefore * @param {boolean=} userGesture * @return {!Promise} */ showView(view, insertBefore, userGesture) {}, /** * @param {!UI.View} view */ removeView(view) {}, /** * @return {!UI.Widget} */ widget() {} }; /** * @interface * @extends {UI.ViewLocation} */ UI.TabbedViewLocation = function() {}; UI.TabbedViewLocation.prototype = { /** * @return {!UI.TabbedPane} */ tabbedPane() {}, enableMoreTabsButton() {} }; /** * @interface */ UI.ViewLocationResolver = function() {}; UI.ViewLocationResolver.prototype = { /** * @param {string} location * @return {?UI.ViewLocation} */ resolveLocation(location) {} }; /** * @unrestricted */ UI.ViewManager = class { constructor() { /** @type {!Map<string, !UI.View>} */ this._views = new Map(); /** @type {!Map<string, string>} */ this._locationNameByViewId = new Map(); for (const extension of self.runtime.extensions('view')) { const descriptor = extension.descriptor(); this._views.set(descriptor['id'], new UI.ProvidedView(extension)); this._locationNameByViewId.set(descriptor['id'], descriptor['location']); } } /** * @param {!Element} element * @param {!Array<!UI.ToolbarItem>} toolbarItems */ static _populateToolbar(element, toolbarItems) { if (!toolbarItems.length) return; const toolbar = new UI.Toolbar(''); element.insertBefore(toolbar.element, element.firstChild); for (const item of toolbarItems) toolbar.appendToolbarItem(item); } /** * @param {!UI.View} view * @return {!Promise} */ revealView(view) { const location = /** @type {?UI.ViewManager._Location} */ (view[UI.ViewManager._Location.symbol]); if (!location) return Promise.resolve(); location._reveal(); return location.showView(view); } /** * @param {string} viewId * @return {?UI.View} */ view(viewId) { return this._views.get(viewId); } /** * @param {string} viewId * @return {?UI.Widget} */ materializedWidget(viewId) { const view = this.view(viewId); return view ? view[UI.View._widgetSymbol] : null; } /** * @param {string} viewId * @param {boolean=} userGesture * @param {boolean=} omitFocus * @return {!Promise} */ showView(viewId, userGesture, omitFocus) { const view = this._views.get(viewId); if (!view) { console.error('Could not find view for id: \'' + viewId + '\' ' + new Error().stack); return Promise.resolve(); } const locationName = this._locationNameByViewId.get(viewId); const location = view[UI.ViewManager._Location.symbol]; if (location) { location._reveal(); return location.showView(view, undefined, userGesture, omitFocus); } return this.resolveLocation(locationName).then(location => { if (!location) throw new Error('Could not resolve location for view: ' + viewId); location._reveal(); return location.showView(view, undefined, userGesture, omitFocus); }); } /** * @param {string=} location * @return {!Promise<?UI.ViewManager._Location>} */ resolveLocation(location) { if (!location) return /** @type {!Promise<?UI.ViewManager._Location>} */ (Promise.resolve(null)); const resolverExtensions = self.runtime.extensions(UI.ViewLocationResolver) .filter(extension => extension.descriptor()['name'] === location); if (!resolverExtensions.length) throw new Error('Unresolved location: ' + location); const resolverExtension = resolverExtensions[0]; return resolverExtension.instance().then( resolver => /** @type {?UI.ViewManager._Location} */ (resolver.resolveLocation(location))); } /** * @param {function()=} revealCallback * @param {string=} location * @param {boolean=} restoreSelection * @param {boolean=} allowReorder * @param {?string=} defaultTab * @return {!UI.TabbedViewLocation} */ createTabbedLocation(revealCallback, location, restoreSelection, allowReorder, defaultTab) { return new UI.ViewManager._TabbedLocation( this, revealCallback, location, restoreSelection, allowReorder, defaultTab); } /** * @param {function()=} revealCallback * @param {string=} location * @return {!UI.ViewLocation} */ createStackLocation(revealCallback, location) { return new UI.ViewManager._StackLocation(this, revealCallback, location); } /** * @param {string} location * @return {boolean} */ hasViewsForLocation(location) { return !!this._viewsForLocation(location).length; } /** * @param {string} location * @return {!Array<!UI.View>} */ _viewsForLocation(location) { const result = []; for (const id of this._views.keys()) { if (this._locationNameByViewId.get(id) === location) result.push(this._views.get(id)); } return result; } }; /** * @unrestricted */ UI.ViewManager._ContainerWidget = class extends UI.VBox { /** * @param {!UI.View} view */ constructor(view) { super(); this.element.classList.add('flex-auto', 'view-container', 'overflow-auto'); this._view = view; this.element.tabIndex = -1; this.setDefaultFocusedElement(this.element); } /** * @return {!Promise} */ _materialize() { if (this._materializePromise) return this._materializePromise; const promises = []; promises.push(this._view.toolbarItems().then(UI.ViewManager._populateToolbar.bind(UI.ViewManager, this.element))); promises.push(this._view.widget().then(widget => { // Move focus from |this| to loaded |widget| if any. const shouldFocus = this.element.hasFocus(); this.setDefaultFocusedElement(null); this._view[UI.View._widgetSymbol] = widget; widget.show(this.element); if (shouldFocus) widget.focus(); })); this._materializePromise = Promise.all(promises); return this._materializePromise; } /** * @override */ wasShown() { this._materialize().then(() => { this._wasShownForTest(); }); } _wasShownForTest() { // This method is sniffed in tests. } }; /** * @unrestricted */ UI.ViewManager._ExpandableContainerWidget = class extends UI.VBox { /** * @param {!UI.View} view */ constructor(view) { super(true); this.element.classList.add('flex-none'); this.registerRequiredCSS('ui/viewContainers.css'); this._titleElement = createElementWithClass('div', 'expandable-view-title'); UI.ARIAUtils.markAsLink(this._titleElement); this._titleExpandIcon = UI.Icon.create('smallicon-triangle-right', 'title-expand-icon'); this._titleElement.appendChild(this._titleExpandIcon); this._titleElement.createTextChild(view.title()); this._titleElement.tabIndex = 0; this._titleElement.addEventListener('click', this._toggleExpanded.bind(this), false); this._titleElement.addEventListener('keydown', this._onTitleKeyDown.bind(this), false); this.contentElement.insertBefore(this._titleElement, this.contentElement.firstChild); this.contentElement.createChild('slot'); this._view = view; view[UI.ViewManager._ExpandableContainerWidget._symbol] = this; } /** * @return {!Promise} */ _materialize() { if (this._materializePromise) return this._materializePromise; const promises = []; promises.push( this._view.toolbarItems().then(UI.ViewManager._populateToolbar.bind(UI.ViewManager, this._titleElement))); promises.push(this._view.widget().then(widget => { this._widget = widget; this._view[UI.View._widgetSymbol] = widget; widget.show(this.element); })); this._materializePromise = Promise.all(promises); return this._materializePromise; } /** * @return {!Promise} */ _expand() { if (this._titleElement.classList.contains('expanded')) return this._materialize(); this._titleElement.classList.add('expanded'); UI.ARIAUtils.setExpanded(this._titleElement, true); this._titleExpandIcon.setIconType('smallicon-triangle-down'); return this._materialize().then(() => this._widget.show(this.element)); } _collapse() { if (!this._titleElement.classList.contains('expanded')) return; this._titleElement.classList.remove('expanded'); UI.ARIAUtils.setExpanded(this._titleElement, false); this._titleExpandIcon.setIconType('smallicon-triangle-right'); this._materialize().then(() => this._widget.detach()); } _toggleExpanded() { if (this._titleElement.classList.contains('expanded')) this._collapse(); else this._expand(); } /** * @param {!Event} event */ _onTitleKeyDown(event) { if (isEnterKey(event) || event.keyCode === UI.KeyboardShortcut.Keys.Space.code) { this._toggleExpanded(); } else if (event.key === 'ArrowLeft') { this._collapse(); } else if (event.key === 'ArrowRight') { if (!this._titleElement.classList.contains('expanded')) this._expand(); else if (this._widget) this._widget.focus(); } } }; UI.ViewManager._ExpandableContainerWidget._symbol = Symbol('container'); /** * @unrestricted */ UI.ViewManager._Location = class { /** * @param {!UI.ViewManager} manager * @param {!UI.Widget} widget * @param {function()=} revealCallback */ constructor(manager, widget, revealCallback) { this._manager = manager; this._revealCallback = revealCallback; this._widget = widget; } /** * @return {!UI.Widget} */ widget() { return this._widget; } _reveal() { if (this._revealCallback) this._revealCallback(); } }; UI.ViewManager._Location.symbol = Symbol('location'); /** * @implements {UI.TabbedViewLocation} * @unrestricted */ UI.ViewManager._TabbedLocation = class extends UI.ViewManager._Location { /** * @param {!UI.ViewManager} manager * @param {function()=} revealCallback * @param {string=} location * @param {boolean=} restoreSelection * @param {boolean=} allowReorder * @param {?string=} defaultTab */ constructor(manager, revealCallback, location, restoreSelection, allowReorder, defaultTab) { const tabbedPane = new UI.TabbedPane(); if (allowReorder) tabbedPane.setAllowTabReorder(true); super(manager, tabbedPane, revealCallback); this._tabbedPane = tabbedPane; this._allowReorder = allowReorder; this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabSelected, this._tabSelected, this); this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabClosed, this._tabClosed, this); this._closeableTabSetting = Common.settings.createSetting(location + '-closeableTabs', {}); this._tabOrderSetting = Common.settings.createSetting(location + '-tabOrder', {}); this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabOrderChanged, this._persistTabOrder, this); if (restoreSelection) this._lastSelectedTabSetting = Common.settings.createSetting(location + '-selectedTab', ''); this._defaultTab = defaultTab; /** @type {!Map.<string, !UI.View>} */ this._views = new Map(); if (location) this.appendApplicableItems(location); } /** * @override * @return {!UI.Widget} */ widget() { return this._tabbedPane; } /** * @override * @return {!UI.TabbedPane} */ tabbedPane() { return this._tabbedPane; } /** * @override */ enableMoreTabsButton() { this._tabbedPane.leftToolbar().appendToolbarItem(new UI.ToolbarMenuButton(this._appendTabsToMenu.bind(this))); this._tabbedPane.disableOverflowMenu(); } /** * @override * @param {string} locationName */ appendApplicableItems(locationName) { const views = this._manager._viewsForLocation(locationName); if (this._allowReorder) { let i = 0; const persistedOrders = this._tabOrderSetting.get(); const orders = new Map(); for (const view of views) orders.set(view.viewId(), persistedOrders[view.viewId()] || (++i) * UI.ViewManager._TabbedLocation.orderStep); views.sort((a, b) => orders.get(a.viewId()) - orders.get(b.viewId())); } for (const view of views) { const id = view.viewId(); this._views.set(id, view); view[UI.ViewManager._Location.symbol] = this; if (view.isTransient()) continue; if (!view.isCloseable()) this._appendTab(view); else if (this._closeableTabSetting.get()[id]) this._appendTab(view); } if (this._defaultTab && this._tabbedPane.hasTab(this._defaultTab)) this._tabbedPane.selectTab(this._defaultTab); else if (this._lastSelectedTabSetting && this._tabbedPane.hasTab(this._lastSelectedTabSetting.get())) this._tabbedPane.selectTab(this._lastSelectedTabSetting.get()); } /** * @param {!UI.ContextMenu} contextMenu */ _appendTabsToMenu(contextMenu) { const views = Array.from(this._views.values()); views.sort((viewa, viewb) => viewa.title().localeCompare(viewb.title())); for (const view of views) { const title = Common.UIString(view.title()); contextMenu.defaultSection().appendItem(title, this.showView.bind(this, view, undefined, true)); } } /** * @param {!UI.View} view * @param {number=} index */ _appendTab(view, index) { this._tabbedPane.appendTab( view.viewId(), view.title(), new UI.ViewManager._ContainerWidget(view), undefined, false, view.isCloseable() || view.isTransient(), index); } /** * @override * @param {!UI.View} view * @param {?UI.View=} insertBefore */ appendView(view, insertBefore) { if (this._tabbedPane.hasTab(view.viewId())) return; const oldLocation = view[UI.ViewManager._Location.symbol]; if (oldLocation && oldLocation !== this) oldLocation.removeView(view); view[UI.ViewManager._Location.symbol] = this; this._manager._views.set(view.viewId(), view); this._views.set(view.viewId(), view); let index = undefined; const tabIds = this._tabbedPane.tabIds(); if (this._allowReorder) { const orderSetting = this._tabOrderSetting.get(); const order = orderSetting[view.viewId()]; for (let i = 0; order && i < tabIds.length; ++i) { if (orderSetting[tabIds[i]] && orderSetting[tabIds[i]] > order) { index = i; break; } } } else if (insertBefore) { for (let i = 0; i < tabIds.length; ++i) { if (tabIds[i] === insertBefore.viewId()) { index = i; break; } } } this._appendTab(view, index); if (view.isCloseable()) { const tabs = this._closeableTabSetting.get(); const tabId = view.viewId(); if (!tabs[tabId]) { tabs[tabId] = true; this._closeableTabSetting.set(tabs); } } this._persistTabOrder(); } /** * @override * @param {!UI.View} view * @param {?UI.View=} insertBefore * @param {boolean=} userGesture * @param {boolean=} omitFocus * @return {!Promise} */ showView(view, insertBefore, userGesture, omitFocus) { this.appendView(view, insertBefore); this._tabbedPane.selectTab(view.viewId(), userGesture); if (!omitFocus) this._tabbedPane.focus(); const widget = /** @type {!UI.ViewManager._ContainerWidget} */ (this._tabbedPane.tabView(view.viewId())); return widget._materialize(); } /** * @param {!UI.View} view * @override */ removeView(view) { if (!this._tabbedPane.hasTab(view.viewId())) return; delete view[UI.ViewManager._Location.symbol]; this._manager._views.delete(view.viewId()); this._tabbedPane.closeTab(view.viewId()); this._views.delete(view.viewId()); } /** * @param {!Common.Event} event */ _tabSelected(event) { const tabId = /** @type {string} */ (event.data.tabId); if (this._lastSelectedTabSetting && event.data['isUserGesture']) this._lastSelectedTabSetting.set(tabId); } /** * @param {!Common.Event} event */ _tabClosed(event) { const id = /** @type {string} */ (event.data['tabId']); const tabs = this._closeableTabSetting.get(); if (tabs[id]) { delete tabs[id]; this._closeableTabSetting.set(tabs); } this._views.get(id).disposeView(); } _persistTabOrder() { const tabIds = this._tabbedPane.tabIds(); const tabOrders = {}; for (let i = 0; i < tabIds.length; i++) tabOrders[tabIds[i]] = (i + 1) * UI.ViewManager._TabbedLocation.orderStep; this._tabOrderSetting.set(tabOrders); } }; UI.ViewManager._TabbedLocation.orderStep = 10; // Keep in sync with descriptors. /** * @implements {UI.ViewLocation} * @unrestricted */ UI.ViewManager._StackLocation = class extends UI.ViewManager._Location { /** * @param {!UI.ViewManager} manager * @param {function()=} revealCallback * @param {string=} location */ constructor(manager, revealCallback, location) { const vbox = new UI.VBox(); super(manager, vbox, revealCallback); this._vbox = vbox; /** @type {!Map<string, !UI.ViewManager._ExpandableContainerWidget>} */ this._expandableContainers = new Map(); if (location) this.appendApplicableItems(location); } /** * @override * @param {!UI.View} view * @param {?UI.View=} insertBefore */ appendView(view, insertBefore) { const oldLocation = view[UI.ViewManager._Location.symbol]; if (oldLocation && oldLocation !== this) oldLocation.removeView(view); let container = this._expandableContainers.get(view.viewId()); if (!container) { view[UI.ViewManager._Location.symbol] = this; this._manager._views.set(view.viewId(), view); container = new UI.ViewManager._ExpandableContainerWidget(view); let beforeElement = null; if (insertBefore) { const beforeContainer = insertBefore[UI.ViewManager._ExpandableContainerWidget._symbol]; beforeElement = beforeContainer ? beforeContainer.element : null; } container.show(this._vbox.contentElement, beforeElement); this._expandableContainers.set(view.viewId(), container); } } /** * @override * @param {!UI.View} view * @param {?UI.View=} insertBefore * @return {!Promise} */ showView(view, insertBefore) { this.appendView(view, insertBefore); const container = this._expandableContainers.get(view.viewId()); return container._expand(); } /** * @param {!UI.View} view * @override */ removeView(view) { const container = this._expandableContainers.get(view.viewId()); if (!container) return; container.detach(); this._expandableContainers.delete(view.viewId()); delete view[UI.ViewManager._Location.symbol]; this._manager._views.delete(view.viewId()); } /** * @override * @param {string} locationName */ appendApplicableItems(locationName) { for (const view of this._manager._viewsForLocation(locationName)) this.appendView(view); } }; /** * @type {!UI.ViewManager} */ UI.viewManager;