diff --git a/resources/unpacked/devtools/front_end/dirac/dirac.js b/resources/unpacked/devtools/front_end/dirac/dirac.js index 3f2c68437a..3caf519b52 100644 --- a/resources/unpacked/devtools/front_end/dirac/dirac.js +++ b/resources/unpacked/devtools/front_end/dirac/dirac.js @@ -149,443 +149,49 @@ Object.assign(window.dirac, (function() { evalInContext(lookupDefaultContext(), code, callback); } -// --- scope info ----------------------------------------------------------------------------------------------------------- - - function getScopeTitle(scope) { - var title = null; - - switch (scope.type()) { - case DebuggerAgent.ScopeType.Local: - title = WebInspector.UIString("Local"); - break; - case DebuggerAgent.ScopeType.Closure: - var scopeName = scope.name(); - if (scopeName) - title = WebInspector.UIString("Closure (%s)", WebInspector.beautifyFunctionName(scopeName)); - else - title = WebInspector.UIString("Closure"); - break; - case DebuggerAgent.ScopeType.Catch: - title = WebInspector.UIString("Catch"); - break; - case DebuggerAgent.ScopeType.Block: - title = WebInspector.UIString("Block"); - break; - case DebuggerAgent.ScopeType.Script: - title = WebInspector.UIString("Script"); - break; - case DebuggerAgent.ScopeType.With: - title = WebInspector.UIString("With Block"); - break; - case DebuggerAgent.ScopeType.Global: - title = WebInspector.UIString("Global"); - break; - } - - return title; + function loadLazyDirac() { + return window.runtime.loadModulePromise("dirac_lazy"); } - function extractNamesFromScopePromise(scope) { - var title = getScopeTitle(scope); - var remoteObject = WebInspector.SourceMapNamesResolver.resolveScopeInObject(scope); - - var result = {title: title}; - - return new Promise(function(resolve) { - - /** - * @param {?Array} properties - */ - function processProperties(properties) { - if (properties) { - result.props = properties.map(function(property) { - var propertyRecord = {name: property.name}; - if (property.resolutionSourceProperty) { - var identifier = property.resolutionSourceProperty.name; - if (identifier != property.name) { - propertyRecord.identifier = identifier; - } - } - return propertyRecord; - }); - } - - resolve(result); - } - - remoteObject.getAllProperties(false, processProperties); - }); - } - - function extractScopeInfoFromScopeChainAsync(callFrame) { - if (!callFrame) { - return Promise.resolve(null); - } - - return new Promise(function(resolve) { - var scopeNamesPromises = []; - - var scopeChain = callFrame.scopeChain(); - for (var i = 0; i < scopeChain.length; ++i) { - var scope = scopeChain[i]; - if (scope.type() === DebuggerAgent.ScopeType.Global) { - continue; - } - - scopeNamesPromises.unshift(extractNamesFromScopePromise(scope)); - } + // --- lazy APIs -------------------------------------------------------------------------------------------------------- + // calling any of these functions will trigger loading dirac_lazy overlay + // which will eventually overwrite those functions when fully loaded - Promise.all(scopeNamesPromises).then(function(frames) { - var result = {frames: frames}; - resolve(result); - }); - }); + function startListeningForWorkspaceChanges(...args) { + return loadLazyDirac().then(() => window.dirac.startListeningForWorkspaceChanges(...args)); } -// --- helpers -------------------------------------------------------------------------------------------------------------- - - var namespacesSymbolsCache = new Map(); - - /** - * @param {string} namespaceName - * @return {function(string)} - */ - function prepareUrlMatcher(namespaceName) { - var relativeNSPath = dirac.nsToRelpath(namespaceName, "js"); - return /** @suppressGlobalPropertiesCheck */ function(url) { - var parser = document.createElement('a'); - parser.href = url; - return parser.pathname.endsWith(relativeNSPath); - }; + function stopListeningForWorkspaceChanges(...args) { + return loadLazyDirac().then(() => window.dirac.stopListeningForWorkspaceChanges(...args)); } - function unique(a) { - return Array.from(new Set(a)); + function extractScopeInfoFromScopeChainAsync(...args) { + return loadLazyDirac().then(() => window.dirac.extractScopeInfoFromScopeChainAsync(...args)); } - function getRelevantSourceCodes(workspace) { - return workspace.uiSourceCodes().filter(sc => sc.project().type() === WebInspector.projectTypes.Network); + function extractNamespaceSymbolsAsync(...args) { + return loadLazyDirac().then(() => window.dirac.extractNamespaceSymbolsAsync(...args)); } -// --- parsing namespaces --------------------------------------------------------------------------------------------------- - - /** - * @param {string} url - * @param {string} cljsSourceCode - * @return {?dirac.NamespaceDescriptor} - */ - function parseClojureScriptNamespace(url, cljsSourceCode) { - var descriptor = dirac.parseNsFromSource(cljsSourceCode); - if (!descriptor) { - return null; - } - - descriptor.url = url; - return descriptor; + function invalidateNamespaceSymbolsCache(...args) { + return loadLazyDirac().then(() => window.dirac.invalidateNamespaceSymbolsCache(...args)); } - /** - * @param {!WebInspector.Script} script - * @return {!Promise>} - * @suppressGlobalPropertiesCheck - */ - function parseNamespacesDescriptorsAsync(script) { - const sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(script); - if (!sourceMap) { - return Promise.resolve([]); - } - - var promises = []; - for (let url of sourceMap.sourceURLs()) { - // take only .cljs or .cljc urls, make sure url params and fragments get matched properly - // examples: - // http://localhost:9977/_compiled/demo/clojure/browser/event.cljs?rel=1463085025939 - // http://localhost:9977/_compiled/demo/dirac_sample/demo.cljs?rel=1463085026941 - const parser = document.createElement('a'); - parser.href = url; - if (!parser.pathname.match(/\.clj.$/)) { - continue; - } - const contentProvider = sourceMap.sourceContentProvider(url, WebInspector.resourceTypes.SourceMapScript); - const namespaceDescriptorPromise = contentProvider.requestContent().then(cljsSourceCode => parseClojureScriptNamespace(url, cljsSourceCode || "")); - promises.push(namespaceDescriptorPromise); - } - - return Promise.all(promises); + function extractNamespacesAsync(...args) { + return loadLazyDirac().then(() => window.dirac.extractNamespacesAsync(...args)); } -// --- changes -------------------------------------------------------------------------------------------------------------- -// this is to reflect dynamically updated files e.g. by Figwheel - - var listeningForWorkspaceChanges = false; - - function invalidateNamespaceSymbolsMatchingUrl(url) { - for (let namespaceName of namespacesSymbolsCache.keys()) { - var matcherFn = prepareUrlMatcher(namespaceName); - if (matcherFn(url)) { - dirac.invalidateNamespaceSymbolsCache(namespaceName); - } - } - } - - function handleSourceCodeAdded(event) { - if (dirac._DEBUG_COMPLETIONS) { - console.log("handleSourceCodeAdded", event); - } - - dirac.invalidateNamespacesCache(); - var uiSourceCode = event.data; - if (uiSourceCode) { - invalidateNamespaceSymbolsMatchingUrl(uiSourceCode.url()); - } - } - - function handleSourceCodeRemoved(event) { - if (dirac._DEBUG_COMPLETIONS) { - console.log("handleSourceCodeRemoved", event); - } - - dirac.invalidateNamespacesCache(); - var uiSourceCode = event.data; - if (uiSourceCode) { - invalidateNamespaceSymbolsMatchingUrl(uiSourceCode.url()); - } - } - - function startListeningForWorkspaceChanges() { - if (listeningForWorkspaceChanges) { - return; - } - - if (dirac._DEBUG_COMPLETIONS) { - console.log("startListeningForWorkspaceChanges"); - } - - var workspace = WebInspector.workspace; - if (!workspace) { - console.error("unable to locate WebInspector.workspace in startListeningForWorkspaceChanges"); - return; - } - - workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, handleSourceCodeAdded, dirac); - workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, handleSourceCodeRemoved, dirac); - - listeningForWorkspaceChanges = true; - } - - function stopListeningForWorkspaceChanges() { - if (!listeningForWorkspaceChanges) { - return; - } - - if (dirac._DEBUG_COMPLETIONS) { - console.log("stopListeningForWorkspaceChanges"); - } - - var workspace = WebInspector.workspace; - if (!workspace) { - console.error("unable to locate WebInspector.workspace in startListeningForWorkspaceChanges"); - return; - } - - workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, handleSourceCodeAdded, dirac); - workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, handleSourceCodeRemoved, dirac); - - listeningForWorkspaceChanges = false; - } - -// --- namespace symbols ---------------------------------------------------------------------------------------------------- - - /** - * @param {!Array} uiSourceCodes - * @param {function(string)} urlMatcherFn - * @return {!Array} - */ - function findMatchingSourceCodes(uiSourceCodes, urlMatcherFn) { - var matching = []; - for (var i = 0; i < uiSourceCodes.length; i++) { - var uiSourceCode = uiSourceCodes[i]; - if (urlMatcherFn(uiSourceCode.url())) { - matching.push(uiSourceCode); - } - } - return matching; - } - - /** - * @param {!Array} names - * @param {string} namespaceName - * @return {!Array} - */ - function filterNamesForNamespace(names, namespaceName) { - var prefix = namespaceName + "/"; - var prefixLength = prefix.length; - - return names.filter(name => name.startsWith(prefix)).map(name => name.substring(prefixLength)); - } - - /** - * @param {!WebInspector.UISourceCode} uiSourceCode - * @return {?WebInspector.Script} - */ - function getScriptFromSourceCode(uiSourceCode) { - return WebInspector.NetworkProject.getScriptFromSourceCode(uiSourceCode); - } - - function extractNamesFromSourceMap(uiSourceCode, namespaceName) { - const script = getScriptFromSourceCode(uiSourceCode); - if (!script) { - console.error("unable to locate script when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); - return []; - } - const sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(/** @type {!WebInspector.Script} */(script)); - if (!sourceMap) { - console.error("unable to locate sourceMap when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); - return []; - } - const payload = sourceMap.payload(); - if (!payload) { - console.error("unable to locate payload when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); - return []; - } - return payload.names || []; - } - - function extractNamespaceSymbolsAsyncWorker(namespaceName) { - var workspace = WebInspector.workspace; - if (!workspace) { - console.error("unable to locate WebInspector.workspace when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); - return Promise.resolve([]); - } - - return new Promise(resolve => { - var urlMatcherFn = prepareUrlMatcher(namespaceName); - var uiSourceCodes = getRelevantSourceCodes(workspace); - - // not there may be multiple matching sources for given namespaceName - // figwheel reloading is just adding new files and not removing old ones - var matchingSourceCodes = findMatchingSourceCodes(uiSourceCodes, urlMatcherFn); - if (!matchingSourceCodes.length) { - if (dirac._DEBUG_COMPLETIONS) { - console.warn("cannot find any matching source file for ClojureScript namespace '" + namespaceName + "'"); - } - resolve([]); - return; - } - - // we simply extract names from all matching source maps and then we filter then to match our namespace name and - // deduplicate them - var results = []; - for (let uiSourceCode of matchingSourceCodes) { - results.push(extractNamesFromSourceMap(uiSourceCode, namespaceName)); - } - var allNames = [].concat.apply([], results); - var filteredNames = unique(filterNamesForNamespace(allNames, namespaceName)); - - if (dirac._DEBUG_COMPLETIONS) { - console.log("extracted " + filteredNames.length + " symbol names for namespace", namespaceName, matchingSourceCodes.map(i => i.url())); - } - - resolve(filteredNames); - }); - } - - function extractNamespaceSymbolsAsync(namespaceName) { - if (!namespaceName) { - return Promise.resolve([]); - } - if (namespacesSymbolsCache.has(namespaceName)) { - return Promise.resolve(namespacesSymbolsCache.get(namespaceName)); - } - - return new Promise(resolve => { - extractNamespaceSymbolsAsyncWorker(namespaceName).then(result => { - namespacesSymbolsCache.set(namespaceName, result); - startListeningForWorkspaceChanges(); - resolve(result); - }); - }); - } - - function invalidateNamespaceSymbolsCache(namespaceName) { - if (dirac._DEBUG_COMPLETIONS) { - console.log("invalidateNamespaceSymbolsCache", namespaceName); - } - namespacesSymbolsCache.delete(namespaceName); - } - -// --- namespace names ------------------------------------------------------------------------------------------------------ - - function extractNamespacesAsyncWorker() { - var workspace = WebInspector.workspace; - if (!workspace) { - console.error("unable to locate WebInspector.workspace when extracting all ClojureScript namespace names"); - return Promise.resolve([]); - } - - return new Promise(resolve => { - const uiSourceCodes = getRelevantSourceCodes(workspace); - const promises = []; - for (var i = 0; i < uiSourceCodes.length; i++) { - const uiSourceCode = uiSourceCodes[i]; - if (!uiSourceCode) { - continue; - } - const script = getScriptFromSourceCode(uiSourceCode); - if (!script) { - continue; - } - promises.push(parseNamespacesDescriptorsAsync(/** @type {!WebInspector.Script} */(script))); - } - - const concatResults = results => { - return [].concat.apply([], results); - }; - - const extractNamespaceNames = - /** - * - * @param {!Array} namespaceDescriptors - * @return {!Array} - */ - (namespaceDescriptors) => { - return namespaceDescriptors.filter(desc => !!desc).map(desc => desc.name); - }; - - Promise.all(promises).then(concatResults).then(extractNamespaceNames).then(resolve); - }); - } - - function extractNamespacesAsync() { - if (dirac._namespacesCache) { - return Promise.resolve(dirac._namespacesCache); - } - - return new Promise(resolve => { - extractNamespacesAsyncWorker().then(result => { - dirac._namespacesCache = result; - startListeningForWorkspaceChanges(); - resolve(result); - }); - }); - } - - function invalidateNamespacesCache() { - if (dirac._DEBUG_COMPLETIONS) { - console.log("invalidateNamespacesCache"); - } - dirac._namespacesCache = null; + function invalidateNamespacesCache(...args) { + return loadLazyDirac().then(() => window.dirac.invalidateNamespacesCache(...args)); } // --- exported interface --------------------------------------------------------------------------------------------------- -// don't forget to update externs.js too + // don't forget to update externs.js too return { _DEBUG_EVAL: false, _DEBUG_COMPLETIONS: false, _DEBUG_KEYSIM: false, - _namespacesSymbolsCache: namespacesSymbolsCache, - _namespacesCache: null, hasREPL: hasFeature("enable-repl"), hasParinfer: hasFeature("enable-parinfer"), hasFriendlyLocals: hasFeature("enable-friendly-locals"), @@ -594,19 +200,22 @@ Object.assign(window.dirac, (function() { hasFeature: hasFeature, codeAsString: codeAsString, stringEscape: stringEscape, - startListeningForWorkspaceChanges: startListeningForWorkspaceChanges, - stopListeningForWorkspaceChanges: stopListeningForWorkspaceChanges, evalInCurrentContext: evalInCurrentContext, - extractScopeInfoFromScopeChainAsync: extractScopeInfoFromScopeChainAsync, - extractNamespaceSymbolsAsync: extractNamespaceSymbolsAsync, - invalidateNamespaceSymbolsCache: invalidateNamespaceSymbolsCache, - extractNamespacesAsync: extractNamespacesAsync, - invalidateNamespacesCache: invalidateNamespacesCache, hasCurrentContext: hasCurrentContext, evalInDefaultContext: evalInDefaultContext, - hasDefaultContext: hasDefaultContext + hasDefaultContext: hasDefaultContext, // note: there will be more functions added to this object dynamically by dirac.implant init code // see externs.js for full list of avail functions + // also below functions can be lazy loaded + + // ---- LAZY INTERFACE ---------------------------------------------------------------------------------------------- + startListeningForWorkspaceChanges: startListeningForWorkspaceChanges, + stopListeningForWorkspaceChanges: stopListeningForWorkspaceChanges, + extractScopeInfoFromScopeChainAsync: extractScopeInfoFromScopeChainAsync, + extractNamespaceSymbolsAsync: extractNamespaceSymbolsAsync, + extractNamespacesAsync: extractNamespacesAsync, + invalidateNamespaceSymbolsCache: invalidateNamespaceSymbolsCache, + invalidateNamespacesCache: invalidateNamespacesCache }; })()); \ No newline at end of file diff --git a/resources/unpacked/devtools/front_end/dirac/module.json b/resources/unpacked/devtools/front_end/dirac/module.json index adb29e6663..535d05e207 100644 --- a/resources/unpacked/devtools/front_end/dirac/module.json +++ b/resources/unpacked/devtools/front_end/dirac/module.json @@ -4,7 +4,6 @@ "common", "host", "sdk", - "sources", "ui" ], "scripts": [ diff --git a/resources/unpacked/devtools/front_end/dirac_lazy/dirac_lazy.js b/resources/unpacked/devtools/front_end/dirac_lazy/dirac_lazy.js new file mode 100644 index 0000000000..878179c824 --- /dev/null +++ b/resources/unpacked/devtools/front_end/dirac_lazy/dirac_lazy.js @@ -0,0 +1,452 @@ +if (!window.dirac) { + console.error("window.dirac was expected to exist when loading dirac_lazy overlay"); + throw new Error("window.dirac was expected to exist when loading dirac_lazy overlay"); +} + +Object.assign(window.dirac, (function() { + +// --- scope info ----------------------------------------------------------------------------------------------------------- + + function getScopeTitle(scope) { + var title = null; + + switch (scope.type()) { + case DebuggerAgent.ScopeType.Local: + title = WebInspector.UIString("Local"); + break; + case DebuggerAgent.ScopeType.Closure: + var scopeName = scope.name(); + if (scopeName) + title = WebInspector.UIString("Closure (%s)", WebInspector.beautifyFunctionName(scopeName)); + else + title = WebInspector.UIString("Closure"); + break; + case DebuggerAgent.ScopeType.Catch: + title = WebInspector.UIString("Catch"); + break; + case DebuggerAgent.ScopeType.Block: + title = WebInspector.UIString("Block"); + break; + case DebuggerAgent.ScopeType.Script: + title = WebInspector.UIString("Script"); + break; + case DebuggerAgent.ScopeType.With: + title = WebInspector.UIString("With Block"); + break; + case DebuggerAgent.ScopeType.Global: + title = WebInspector.UIString("Global"); + break; + } + + return title; + } + + function extractNamesFromScopePromise(scope) { + var title = getScopeTitle(scope); + var remoteObject = WebInspector.SourceMapNamesResolver.resolveScopeInObject(scope); + + var result = {title: title}; + + return new Promise(function(resolve) { + + /** + * @param {?Array} properties + */ + function processProperties(properties) { + if (properties) { + result.props = properties.map(function(property) { + var propertyRecord = {name: property.name}; + if (property.resolutionSourceProperty) { + var identifier = property.resolutionSourceProperty.name; + if (identifier != property.name) { + propertyRecord.identifier = identifier; + } + } + return propertyRecord; + }); + } + + resolve(result); + } + + remoteObject.getAllProperties(false, processProperties); + }); + } + + function extractScopeInfoFromScopeChainAsync(callFrame) { + if (!callFrame) { + return Promise.resolve(null); + } + + return new Promise(function(resolve) { + var scopeNamesPromises = []; + + var scopeChain = callFrame.scopeChain(); + for (var i = 0; i < scopeChain.length; ++i) { + var scope = scopeChain[i]; + if (scope.type() === DebuggerAgent.ScopeType.Global) { + continue; + } + + scopeNamesPromises.unshift(extractNamesFromScopePromise(scope)); + } + + Promise.all(scopeNamesPromises).then(function(frames) { + var result = {frames: frames}; + resolve(result); + }); + }); + } + +// --- helpers -------------------------------------------------------------------------------------------------------------- + + var namespacesSymbolsCache = new Map(); + + /** + * @param {string} namespaceName + * @return {function(string)} + */ + function prepareUrlMatcher(namespaceName) { + var relativeNSPath = dirac.nsToRelpath(namespaceName, "js"); + return /** @suppressGlobalPropertiesCheck */ function(url) { + var parser = document.createElement('a'); + parser.href = url; + return parser.pathname.endsWith(relativeNSPath); + }; + } + + function unique(a) { + return Array.from(new Set(a)); + } + + function getRelevantSourceCodes(workspace) { + return workspace.uiSourceCodes().filter(sc => sc.project().type() === WebInspector.projectTypes.Network); + } + +// --- parsing namespaces --------------------------------------------------------------------------------------------------- + + /** + * @param {string} url + * @param {string} cljsSourceCode + * @return {?dirac.NamespaceDescriptor} + */ + function parseClojureScriptNamespace(url, cljsSourceCode) { + var descriptor = dirac.parseNsFromSource(cljsSourceCode); + if (!descriptor) { + return null; + } + + descriptor.url = url; + return descriptor; + } + + /** + * @param {!WebInspector.Script} script + * @return {!Promise>} + * @suppressGlobalPropertiesCheck + */ + function parseNamespacesDescriptorsAsync(script) { + const sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(script); + if (!sourceMap) { + return Promise.resolve([]); + } + + var promises = []; + for (let url of sourceMap.sourceURLs()) { + // take only .cljs or .cljc urls, make sure url params and fragments get matched properly + // examples: + // http://localhost:9977/_compiled/demo/clojure/browser/event.cljs?rel=1463085025939 + // http://localhost:9977/_compiled/demo/dirac_sample/demo.cljs?rel=1463085026941 + const parser = document.createElement('a'); + parser.href = url; + if (!parser.pathname.match(/\.clj.$/)) { + continue; + } + const contentProvider = sourceMap.sourceContentProvider(url, WebInspector.resourceTypes.SourceMapScript); + const namespaceDescriptorPromise = contentProvider.requestContent().then(cljsSourceCode => parseClojureScriptNamespace(url, cljsSourceCode || "")); + promises.push(namespaceDescriptorPromise); + } + + return Promise.all(promises); + } + +// --- changes -------------------------------------------------------------------------------------------------------------- +// this is to reflect dynamically updated files e.g. by Figwheel + + var listeningForWorkspaceChanges = false; + + function invalidateNamespaceSymbolsMatchingUrl(url) { + for (let namespaceName of namespacesSymbolsCache.keys()) { + var matcherFn = prepareUrlMatcher(namespaceName); + if (matcherFn(url)) { + dirac.invalidateNamespaceSymbolsCache(namespaceName); + } + } + } + + function handleSourceCodeAdded(event) { + if (dirac._DEBUG_COMPLETIONS) { + console.log("handleSourceCodeAdded", event); + } + + dirac.invalidateNamespacesCache(); + var uiSourceCode = event.data; + if (uiSourceCode) { + invalidateNamespaceSymbolsMatchingUrl(uiSourceCode.url()); + } + } + + function handleSourceCodeRemoved(event) { + if (dirac._DEBUG_COMPLETIONS) { + console.log("handleSourceCodeRemoved", event); + } + + dirac.invalidateNamespacesCache(); + var uiSourceCode = event.data; + if (uiSourceCode) { + invalidateNamespaceSymbolsMatchingUrl(uiSourceCode.url()); + } + } + + function startListeningForWorkspaceChanges() { + if (listeningForWorkspaceChanges) { + return; + } + + if (dirac._DEBUG_COMPLETIONS) { + console.log("startListeningForWorkspaceChanges"); + } + + var workspace = WebInspector.workspace; + if (!workspace) { + console.error("unable to locate WebInspector.workspace in startListeningForWorkspaceChanges"); + return; + } + + workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, handleSourceCodeAdded, dirac); + workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, handleSourceCodeRemoved, dirac); + + listeningForWorkspaceChanges = true; + } + + function stopListeningForWorkspaceChanges() { + if (!listeningForWorkspaceChanges) { + return; + } + + if (dirac._DEBUG_COMPLETIONS) { + console.log("stopListeningForWorkspaceChanges"); + } + + var workspace = WebInspector.workspace; + if (!workspace) { + console.error("unable to locate WebInspector.workspace in startListeningForWorkspaceChanges"); + return; + } + + workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeAdded, handleSourceCodeAdded, dirac); + workspace.removeEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, handleSourceCodeRemoved, dirac); + + listeningForWorkspaceChanges = false; + } + +// --- namespace symbols ---------------------------------------------------------------------------------------------------- + + /** + * @param {!Array} uiSourceCodes + * @param {function(string)} urlMatcherFn + * @return {!Array} + */ + function findMatchingSourceCodes(uiSourceCodes, urlMatcherFn) { + var matching = []; + for (var i = 0; i < uiSourceCodes.length; i++) { + var uiSourceCode = uiSourceCodes[i]; + if (urlMatcherFn(uiSourceCode.url())) { + matching.push(uiSourceCode); + } + } + return matching; + } + + /** + * @param {!Array} names + * @param {string} namespaceName + * @return {!Array} + */ + function filterNamesForNamespace(names, namespaceName) { + var prefix = namespaceName + "/"; + var prefixLength = prefix.length; + + return names.filter(name => name.startsWith(prefix)).map(name => name.substring(prefixLength)); + } + + /** + * @param {!WebInspector.UISourceCode} uiSourceCode + * @return {?WebInspector.Script} + */ + function getScriptFromSourceCode(uiSourceCode) { + return WebInspector.NetworkProject.getScriptFromSourceCode(uiSourceCode); + } + + function extractNamesFromSourceMap(uiSourceCode, namespaceName) { + const script = getScriptFromSourceCode(uiSourceCode); + if (!script) { + console.error("unable to locate script when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); + return []; + } + const sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(/** @type {!WebInspector.Script} */(script)); + if (!sourceMap) { + console.error("unable to locate sourceMap when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); + return []; + } + const payload = sourceMap.payload(); + if (!payload) { + console.error("unable to locate payload when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); + return []; + } + return payload.names || []; + } + + function extractNamespaceSymbolsAsyncWorker(namespaceName) { + var workspace = WebInspector.workspace; + if (!workspace) { + console.error("unable to locate WebInspector.workspace when extracting symbols for ClojureScript namespace '" + namespaceName + "'"); + return Promise.resolve([]); + } + + return new Promise(resolve => { + var urlMatcherFn = prepareUrlMatcher(namespaceName); + var uiSourceCodes = getRelevantSourceCodes(workspace); + + // not there may be multiple matching sources for given namespaceName + // figwheel reloading is just adding new files and not removing old ones + var matchingSourceCodes = findMatchingSourceCodes(uiSourceCodes, urlMatcherFn); + if (!matchingSourceCodes.length) { + if (dirac._DEBUG_COMPLETIONS) { + console.warn("cannot find any matching source file for ClojureScript namespace '" + namespaceName + "'"); + } + resolve([]); + return; + } + + // we simply extract names from all matching source maps and then we filter then to match our namespace name and + // deduplicate them + var results = []; + for (let uiSourceCode of matchingSourceCodes) { + results.push(extractNamesFromSourceMap(uiSourceCode, namespaceName)); + } + var allNames = [].concat.apply([], results); + var filteredNames = unique(filterNamesForNamespace(allNames, namespaceName)); + + if (dirac._DEBUG_COMPLETIONS) { + console.log("extracted " + filteredNames.length + " symbol names for namespace", namespaceName, matchingSourceCodes.map(i => i.url())); + } + + resolve(filteredNames); + }); + } + + function extractNamespaceSymbolsAsync(namespaceName) { + if (!namespaceName) { + return Promise.resolve([]); + } + if (namespacesSymbolsCache.has(namespaceName)) { + return Promise.resolve(namespacesSymbolsCache.get(namespaceName)); + } + + return new Promise(resolve => { + extractNamespaceSymbolsAsyncWorker(namespaceName).then(result => { + namespacesSymbolsCache.set(namespaceName, result); + startListeningForWorkspaceChanges(); + resolve(result); + }); + }); + } + + function invalidateNamespaceSymbolsCache(namespaceName) { + if (dirac._DEBUG_COMPLETIONS) { + console.log("invalidateNamespaceSymbolsCache", namespaceName); + } + namespacesSymbolsCache.delete(namespaceName); + } + +// --- namespace names ------------------------------------------------------------------------------------------------------ + + function extractNamespacesAsyncWorker() { + var workspace = WebInspector.workspace; + if (!workspace) { + console.error("unable to locate WebInspector.workspace when extracting all ClojureScript namespace names"); + return Promise.resolve([]); + } + + return new Promise(resolve => { + const uiSourceCodes = getRelevantSourceCodes(workspace); + const promises = []; + for (var i = 0; i < uiSourceCodes.length; i++) { + const uiSourceCode = uiSourceCodes[i]; + if (!uiSourceCode) { + continue; + } + const script = getScriptFromSourceCode(uiSourceCode); + if (!script) { + continue; + } + promises.push(parseNamespacesDescriptorsAsync(/** @type {!WebInspector.Script} */(script))); + } + + const concatResults = results => { + return [].concat.apply([], results); + }; + + const extractNamespaceNames = + /** + * + * @param {!Array} namespaceDescriptors + * @return {!Array} + */ + (namespaceDescriptors) => { + return namespaceDescriptors.filter(desc => !!desc).map(desc => desc.name); + }; + + Promise.all(promises).then(concatResults).then(extractNamespaceNames).then(resolve); + }); + } + + function extractNamespacesAsync() { + if (dirac._namespacesCache) { + return Promise.resolve(dirac._namespacesCache); + } + + return new Promise(resolve => { + extractNamespacesAsyncWorker().then(result => { + dirac._namespacesCache = result; + startListeningForWorkspaceChanges(); + resolve(result); + }); + }); + } + + function invalidateNamespacesCache() { + if (dirac._DEBUG_COMPLETIONS) { + console.log("invalidateNamespacesCache"); + } + dirac._namespacesCache = null; + } + +// --- exported interface --------------------------------------------------------------------------------------------------- + + // don't forget to update externs.js too + return { + _lazyLoaded: true, + _namespacesSymbolsCache: namespacesSymbolsCache, + _namespacesCache: null, + startListeningForWorkspaceChanges: startListeningForWorkspaceChanges, + stopListeningForWorkspaceChanges: stopListeningForWorkspaceChanges, + extractScopeInfoFromScopeChainAsync: extractScopeInfoFromScopeChainAsync, + extractNamespaceSymbolsAsync: extractNamespaceSymbolsAsync, + invalidateNamespaceSymbolsCache: invalidateNamespaceSymbolsCache, + extractNamespacesAsync: extractNamespacesAsync, + invalidateNamespacesCache: invalidateNamespacesCache + }; + +})()); \ No newline at end of file diff --git a/resources/unpacked/devtools/front_end/dirac_lazy/module.json b/resources/unpacked/devtools/front_end/dirac_lazy/module.json new file mode 100644 index 0000000000..fbebdd5728 --- /dev/null +++ b/resources/unpacked/devtools/front_end/dirac_lazy/module.json @@ -0,0 +1,13 @@ +{ + "dependencies": [ + "sources", + "dirac" + ], + "scripts": [ + "dirac_lazy.js" + ], + "skip_compilation": [ + ], + "resources": [ + ] +} \ No newline at end of file diff --git a/resources/unpacked/devtools/front_end/inspector.json b/resources/unpacked/devtools/front_end/inspector.json index a267cab0ec..fc47d98e07 100644 --- a/resources/unpacked/devtools/front_end/inspector.json +++ b/resources/unpacked/devtools/front_end/inspector.json @@ -12,6 +12,7 @@ { "name": "bindings", "type": "autostart" }, { "name": "extensions", "type": "autostart" }, { "name": "dirac", "type": "autostart" }, + { "name": "dirac_lazy" }, { "name": "ui_lazy" }, { "name": "components_lazy" }, { "name": "elements", "condition": "!v8only" },